mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
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:
@@ -26,24 +26,29 @@ public class FilterRegion {
|
||||
mH = SVGLength.from(height);
|
||||
}
|
||||
|
||||
private double getRelativeOrDefault(
|
||||
VirtualView view, SVGLength value, float relativeOn, double defaultValue) {
|
||||
if (value == null || value.unit == SVGLength.UnitType.UNKNOWN) {
|
||||
return defaultValue;
|
||||
}
|
||||
return view.relativeOn(value, relativeOn);
|
||||
}
|
||||
|
||||
public Rect getCropRect(VirtualView view, FilterProperties.Units units, RectF bounds) {
|
||||
double x, y, width, height;
|
||||
if (units == FilterProperties.Units.USER_SPACE_ON_USE) {
|
||||
x = this.mX == null ? 0 : view.relativeOn(this.mX, view.getSvgView().getCanvasWidth());
|
||||
y = this.mY == null ? 0 : view.relativeOn(this.mY, view.getSvgView().getCanvasHeight());
|
||||
width = this.mW == null ? 0 : view.relativeOn(this.mW, view.getSvgView().getCanvasWidth());
|
||||
height = this.mH == null ? 0 : view.relativeOn(this.mH, view.getSvgView().getCanvasHeight());
|
||||
return new Rect((int) x, (int) y, (int) (x + width), (int) (y + height));
|
||||
float canvasWidth = view.getSvgView().getCanvasWidth();
|
||||
float canvasHeight = view.getSvgView().getCanvasHeight();
|
||||
x = getRelativeOrDefault(view, mX, canvasWidth, bounds.left);
|
||||
y = getRelativeOrDefault(view, mY, canvasHeight, bounds.top);
|
||||
width = getRelativeOrDefault(view, mW, canvasWidth, bounds.width());
|
||||
height = getRelativeOrDefault(view, mH, canvasHeight, bounds.height());
|
||||
} else { // FilterProperties.Units.OBJECT_BOUNDING_BOX
|
||||
x = view.relativeOnFraction(this.mX, bounds.width());
|
||||
y = view.relativeOnFraction(this.mY, bounds.height());
|
||||
x = bounds.left + view.relativeOnFraction(this.mX, bounds.width());
|
||||
y = bounds.top + view.relativeOnFraction(this.mY, bounds.height());
|
||||
width = view.relativeOnFraction(this.mW, bounds.width());
|
||||
height = view.relativeOnFraction(this.mH, bounds.height());
|
||||
return new Rect(
|
||||
(int) (bounds.left + x),
|
||||
(int) (bounds.top + y),
|
||||
(int) (bounds.left + x + width),
|
||||
(int) (bounds.top + y + height));
|
||||
}
|
||||
return new Rect((int) x, (int) y, (int) (x + width), (int) (y + height));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,11 @@ class FilterView extends DefinitionView {
|
||||
resultBitmap.eraseColor(Color.TRANSPARENT);
|
||||
cropRect =
|
||||
currentFilter.mFilterSubregion.getCropRect(
|
||||
currentFilter, this.mPrimitiveUnits, renderableBounds);
|
||||
currentFilter,
|
||||
this.mPrimitiveUnits,
|
||||
this.mFilterUnits == FilterProperties.Units.USER_SPACE_ON_USE
|
||||
? new RectF(filterRegionRect)
|
||||
: renderableBounds);
|
||||
canvas.drawBitmap(currentFilter.applyFilter(mResultsMap, res), cropRect, cropRect, null);
|
||||
res = resultBitmap.copy(Bitmap.Config.ARGB_8888, true);
|
||||
String resultName = currentFilter.getResult();
|
||||
@@ -104,7 +108,6 @@ class FilterView extends DefinitionView {
|
||||
|
||||
// crop Bitmap to filter coordinates
|
||||
resultBitmap.eraseColor(Color.TRANSPARENT);
|
||||
|
||||
canvas.drawBitmap(res, filterRegionRect, filterRegionRect, null);
|
||||
return resultBitmap;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,8 @@ export default class FilterPrimitive<P> extends Component<
|
||||
[x: string]: unknown;
|
||||
root: (FilterPrimitive<P> & NativeMethods) | null = null;
|
||||
|
||||
static defaultPrimitiveProps: React.ComponentProps<typeof FilterPrimitive> = {
|
||||
x: '0%',
|
||||
y: '0%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
};
|
||||
static defaultPrimitiveProps: React.ComponentProps<typeof FilterPrimitive> =
|
||||
{};
|
||||
|
||||
refMethod: (instance: (FilterPrimitive<P> & NativeMethods) | null) => void = (
|
||||
instance: (FilterPrimitive<P> & NativeMethods) | null
|
||||
|
||||
Reference in New Issue
Block a user