feat: properly implement filter region unit USER_SPACE_ON_USE (#2486)

# Summary

After deep dive into the specification, I found out that the default
filter subregion is not equal to `0% 0% 100% 100%`, rather the size of
the parent filter region.

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |          |
| MacOS   |          |
| Android |          |
This commit is contained in:
Jakub Grzywacz
2024-10-14 15:35:16 +02:00
committed by GitHub
parent 3aae632d1f
commit 8fed77476b
5 changed files with 57 additions and 29 deletions
+6 -5
View File
@@ -105,14 +105,16 @@ using namespace facebook::react;
CGContext *cropContext = [self openContext:canvasBounds.size];
CIImage *mask;
CGRect filterRegionRect = [self.filterRegion getCropRect:self units:self.filterUnits bounds:renderableBounds];
CIImage *result = img;
RNSVGFilterPrimitive *currentFilter;
for (RNSVGNode *node in self.subviews) {
if ([node isKindOfClass:[RNSVGFilterPrimitive class]]) {
currentFilter = (RNSVGFilterPrimitive *)node;
cropRect = [currentFilter.filterSubregion getCropRect:currentFilter
units:self.primitiveUnits
bounds:renderableBounds];
cropRect = [currentFilter.filterSubregion
getCropRect:currentFilter
units:self.primitiveUnits
bounds:self.primitiveUnits == kRNSVGUnitsUserSpaceOnUse ? filterRegionRect : renderableBounds];
mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm];
[cropFilter setValue:[currentFilter applyFilter:resultsMap previousFilterResult:result ctm:ctm]
forKey:@"inputImage"];
@@ -131,8 +133,7 @@ using namespace facebook::react;
}
}
cropRect = [currentFilter.filterSubregion getCropRect:self units:self.filterUnits bounds:renderableBounds];
mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm];
mask = [self getMaskFromRect:cropContext rect:filterRegionRect ctm:ctm];
[cropFilter setValue:result forKey:@"inputImage"];
[cropFilter setValue:mask forKey:@"inputMaskImage"];
[self endContext:cropContext];
+27 -4
View File
@@ -38,6 +38,17 @@
_height = height;
}
+ (CGFloat)getRelativeOrDefault:(RNSVGNode *)node
value:(RNSVGLength *)value
relativeOn:(CGFloat)relativeOn
defaultValue:(CGFloat)defaultValue
{
if (value == nil || value.unit == SVG_LENGTHTYPE_UNKNOWN) {
return defaultValue;
}
return [node relativeOn:value relative:relativeOn];
}
- (CGRect)getCropRect:(RNSVGNode *)node units:(RNSVGUnits)units bounds:(CGRect)bounds
{
CGFloat x, y, width, height;
@@ -48,10 +59,22 @@
height = [node relativeOnFraction:self.height relative:bounds.size.height];
return CGRectMake(bounds.origin.x + x, bounds.origin.y + y, width, height);
} else { // kRNSVGUnitsUserSpaceOnUse
x = [node relativeOnWidth:self.x];
y = [node relativeOnHeight:self.y];
width = [node relativeOnWidth:self.width];
height = [node relativeOnHeight:self.height];
x = [RNSVGFilterRegion getRelativeOrDefault:node
value:self.x
relativeOn:[node getCanvasWidth]
defaultValue:bounds.origin.x];
y = [RNSVGFilterRegion getRelativeOrDefault:node
value:self.y
relativeOn:[node getCanvasHeight]
defaultValue:bounds.origin.y];
width = [RNSVGFilterRegion getRelativeOrDefault:node
value:self.width
relativeOn:[node getCanvasWidth]
defaultValue:bounds.size.width];
height = [RNSVGFilterRegion getRelativeOrDefault:node
value:self.height
relativeOn:[node getCanvasHeight]
defaultValue:bounds.size.height];
return CGRectMake(x, y, width, height);
}
}