mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-10 09:49:36 +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);
|
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) {
|
public Rect getCropRect(VirtualView view, FilterProperties.Units units, RectF bounds) {
|
||||||
double x, y, width, height;
|
double x, y, width, height;
|
||||||
if (units == FilterProperties.Units.USER_SPACE_ON_USE) {
|
if (units == FilterProperties.Units.USER_SPACE_ON_USE) {
|
||||||
x = this.mX == null ? 0 : view.relativeOn(this.mX, view.getSvgView().getCanvasWidth());
|
float canvasWidth = view.getSvgView().getCanvasWidth();
|
||||||
y = this.mY == null ? 0 : view.relativeOn(this.mY, view.getSvgView().getCanvasHeight());
|
float canvasHeight = view.getSvgView().getCanvasHeight();
|
||||||
width = this.mW == null ? 0 : view.relativeOn(this.mW, view.getSvgView().getCanvasWidth());
|
x = getRelativeOrDefault(view, mX, canvasWidth, bounds.left);
|
||||||
height = this.mH == null ? 0 : view.relativeOn(this.mH, view.getSvgView().getCanvasHeight());
|
y = getRelativeOrDefault(view, mY, canvasHeight, bounds.top);
|
||||||
return new Rect((int) x, (int) y, (int) (x + width), (int) (y + height));
|
width = getRelativeOrDefault(view, mW, canvasWidth, bounds.width());
|
||||||
|
height = getRelativeOrDefault(view, mH, canvasHeight, bounds.height());
|
||||||
} else { // FilterProperties.Units.OBJECT_BOUNDING_BOX
|
} else { // FilterProperties.Units.OBJECT_BOUNDING_BOX
|
||||||
x = view.relativeOnFraction(this.mX, bounds.width());
|
x = bounds.left + view.relativeOnFraction(this.mX, bounds.width());
|
||||||
y = view.relativeOnFraction(this.mY, bounds.height());
|
y = bounds.top + view.relativeOnFraction(this.mY, bounds.height());
|
||||||
width = view.relativeOnFraction(this.mW, bounds.width());
|
width = view.relativeOnFraction(this.mW, bounds.width());
|
||||||
height = view.relativeOnFraction(this.mH, bounds.height());
|
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);
|
resultBitmap.eraseColor(Color.TRANSPARENT);
|
||||||
cropRect =
|
cropRect =
|
||||||
currentFilter.mFilterSubregion.getCropRect(
|
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);
|
canvas.drawBitmap(currentFilter.applyFilter(mResultsMap, res), cropRect, cropRect, null);
|
||||||
res = resultBitmap.copy(Bitmap.Config.ARGB_8888, true);
|
res = resultBitmap.copy(Bitmap.Config.ARGB_8888, true);
|
||||||
String resultName = currentFilter.getResult();
|
String resultName = currentFilter.getResult();
|
||||||
@@ -104,7 +108,6 @@ class FilterView extends DefinitionView {
|
|||||||
|
|
||||||
// crop Bitmap to filter coordinates
|
// crop Bitmap to filter coordinates
|
||||||
resultBitmap.eraseColor(Color.TRANSPARENT);
|
resultBitmap.eraseColor(Color.TRANSPARENT);
|
||||||
|
|
||||||
canvas.drawBitmap(res, filterRegionRect, filterRegionRect, null);
|
canvas.drawBitmap(res, filterRegionRect, filterRegionRect, null);
|
||||||
return resultBitmap;
|
return resultBitmap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,14 +105,16 @@ using namespace facebook::react;
|
|||||||
CGContext *cropContext = [self openContext:canvasBounds.size];
|
CGContext *cropContext = [self openContext:canvasBounds.size];
|
||||||
CIImage *mask;
|
CIImage *mask;
|
||||||
|
|
||||||
|
CGRect filterRegionRect = [self.filterRegion getCropRect:self units:self.filterUnits bounds:renderableBounds];
|
||||||
CIImage *result = img;
|
CIImage *result = img;
|
||||||
RNSVGFilterPrimitive *currentFilter;
|
RNSVGFilterPrimitive *currentFilter;
|
||||||
for (RNSVGNode *node in self.subviews) {
|
for (RNSVGNode *node in self.subviews) {
|
||||||
if ([node isKindOfClass:[RNSVGFilterPrimitive class]]) {
|
if ([node isKindOfClass:[RNSVGFilterPrimitive class]]) {
|
||||||
currentFilter = (RNSVGFilterPrimitive *)node;
|
currentFilter = (RNSVGFilterPrimitive *)node;
|
||||||
cropRect = [currentFilter.filterSubregion getCropRect:currentFilter
|
cropRect = [currentFilter.filterSubregion
|
||||||
units:self.primitiveUnits
|
getCropRect:currentFilter
|
||||||
bounds:renderableBounds];
|
units:self.primitiveUnits
|
||||||
|
bounds:self.primitiveUnits == kRNSVGUnitsUserSpaceOnUse ? filterRegionRect : renderableBounds];
|
||||||
mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm];
|
mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm];
|
||||||
[cropFilter setValue:[currentFilter applyFilter:resultsMap previousFilterResult:result ctm:ctm]
|
[cropFilter setValue:[currentFilter applyFilter:resultsMap previousFilterResult:result ctm:ctm]
|
||||||
forKey:@"inputImage"];
|
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:filterRegionRect ctm:ctm];
|
||||||
mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm];
|
|
||||||
[cropFilter setValue:result forKey:@"inputImage"];
|
[cropFilter setValue:result forKey:@"inputImage"];
|
||||||
[cropFilter setValue:mask forKey:@"inputMaskImage"];
|
[cropFilter setValue:mask forKey:@"inputMaskImage"];
|
||||||
[self endContext:cropContext];
|
[self endContext:cropContext];
|
||||||
|
|||||||
@@ -38,6 +38,17 @@
|
|||||||
_height = height;
|
_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
|
- (CGRect)getCropRect:(RNSVGNode *)node units:(RNSVGUnits)units bounds:(CGRect)bounds
|
||||||
{
|
{
|
||||||
CGFloat x, y, width, height;
|
CGFloat x, y, width, height;
|
||||||
@@ -48,10 +59,22 @@
|
|||||||
height = [node relativeOnFraction:self.height relative:bounds.size.height];
|
height = [node relativeOnFraction:self.height relative:bounds.size.height];
|
||||||
return CGRectMake(bounds.origin.x + x, bounds.origin.y + y, width, height);
|
return CGRectMake(bounds.origin.x + x, bounds.origin.y + y, width, height);
|
||||||
} else { // kRNSVGUnitsUserSpaceOnUse
|
} else { // kRNSVGUnitsUserSpaceOnUse
|
||||||
x = [node relativeOnWidth:self.x];
|
x = [RNSVGFilterRegion getRelativeOrDefault:node
|
||||||
y = [node relativeOnHeight:self.y];
|
value:self.x
|
||||||
width = [node relativeOnWidth:self.width];
|
relativeOn:[node getCanvasWidth]
|
||||||
height = [node relativeOnHeight:self.height];
|
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);
|
return CGRectMake(x, y, width, height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,8 @@ export default class FilterPrimitive<P> extends Component<
|
|||||||
[x: string]: unknown;
|
[x: string]: unknown;
|
||||||
root: (FilterPrimitive<P> & NativeMethods) | null = null;
|
root: (FilterPrimitive<P> & NativeMethods) | null = null;
|
||||||
|
|
||||||
static defaultPrimitiveProps: React.ComponentProps<typeof FilterPrimitive> = {
|
static defaultPrimitiveProps: React.ComponentProps<typeof FilterPrimitive> =
|
||||||
x: '0%',
|
{};
|
||||||
y: '0%',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
};
|
|
||||||
|
|
||||||
refMethod: (instance: (FilterPrimitive<P> & NativeMethods) | null) => void = (
|
refMethod: (instance: (FilterPrimitive<P> & NativeMethods) | null) => void = (
|
||||||
instance: (FilterPrimitive<P> & NativeMethods) | null
|
instance: (FilterPrimitive<P> & NativeMethods) | null
|
||||||
|
|||||||
Reference in New Issue
Block a user