diff --git a/android/src/main/java/com/horcrux/svg/FeOffsetView.java b/android/src/main/java/com/horcrux/svg/FeOffsetView.java index c583c5c4a..8dac0a37b 100644 --- a/android/src/main/java/com/horcrux/svg/FeOffsetView.java +++ b/android/src/main/java/com/horcrux/svg/FeOffsetView.java @@ -3,7 +3,7 @@ import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Paint; +import android.graphics.RectF; import com.facebook.react.bridge.Dynamic; import com.facebook.react.bridge.ReactContext; import java.util.HashMap; @@ -42,8 +42,10 @@ public Bitmap applyFilter(HashMap resultsMap, Bitmap prevResult) float dx = this.mDx != null ? (float) this.relativeOnWidth(this.mDx) : 0; float dy = this.mDy != null ? (float) this.relativeOnHeight(this.mDy) : 0; + RectF frame = new RectF(0, 0, dx, dy); + this.getSvgView().getCtm().mapRect(frame); - canvas.drawBitmap(source, dx, dy, new Paint()); + canvas.drawBitmap(source, frame.width(), frame.height(), null); return result; } diff --git a/android/src/main/java/com/horcrux/svg/FilterPrimitiveView.java b/android/src/main/java/com/horcrux/svg/FilterPrimitiveView.java index 42000fa8d..e4985e8a0 100644 --- a/android/src/main/java/com/horcrux/svg/FilterPrimitiveView.java +++ b/android/src/main/java/com/horcrux/svg/FilterPrimitiveView.java @@ -8,33 +8,31 @@ @SuppressLint("ViewConstructor") class FilterPrimitiveView extends DefinitionView { - SVGLength mX; - SVGLength mY; - SVGLength mW; - SVGLength mH; private String mResult; + public final FilterRegion mFilterRegion; public FilterPrimitiveView(ReactContext reactContext) { super(reactContext); + mFilterRegion = new FilterRegion(); } public void setX(Dynamic x) { - mX = SVGLength.from(x); + mFilterRegion.setX(x); invalidate(); } public void setY(Dynamic y) { - mY = SVGLength.from(y); + mFilterRegion.setY(y); invalidate(); } public void setWidth(Dynamic width) { - mW = SVGLength.from(width); + mFilterRegion.setWidth(width); invalidate(); } public void setHeight(Dynamic height) { - mH = SVGLength.from(height); + mFilterRegion.setHeight(height); invalidate(); } diff --git a/android/src/main/java/com/horcrux/svg/FilterRegion.java b/android/src/main/java/com/horcrux/svg/FilterRegion.java new file mode 100644 index 000000000..7c59c9ccb --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FilterRegion.java @@ -0,0 +1,49 @@ +package com.horcrux.svg; + +import android.graphics.Rect; +import android.graphics.RectF; +import com.facebook.react.bridge.Dynamic; + +public class FilterRegion { + SVGLength mX; + SVGLength mY; + SVGLength mW; + SVGLength mH; + + public void setX(Dynamic x) { + mX = SVGLength.from(x); + } + + public void setY(Dynamic y) { + mY = SVGLength.from(y); + } + + public void setWidth(Dynamic width) { + mW = SVGLength.from(width); + } + + public void setHeight(Dynamic height) { + mH = SVGLength.from(height); + } + + public Rect getCropRect(VirtualView view, FilterProperties.Units units, RectF renderableBounds) { + double x, y, width, height; + if (units == FilterProperties.Units.USER_SPACE_ON_USE) { + x = view.relativeOn(this.mX, view.getSvgView().getCanvasWidth()); + y = view.relativeOn(this.mY, view.getSvgView().getCanvasHeight()); + width = view.relativeOn(this.mW, view.getSvgView().getCanvasWidth()); + height = view.relativeOn(this.mH, view.getSvgView().getCanvasHeight()); + return new Rect((int) x, (int) y, (int) (x + width), (int) (y + height)); + } else { // FilterProperties.Units.OBJECT_BOUNDING_BOX + x = view.relativeOnFraction(this.mX, renderableBounds.width()); + y = view.relativeOnFraction(this.mY, renderableBounds.height()); + width = view.relativeOnFraction(this.mW, renderableBounds.width()); + height = view.relativeOnFraction(this.mH, renderableBounds.height()); + return new Rect( + (int) (renderableBounds.left + x), + (int) (renderableBounds.top + y), + (int) (renderableBounds.left + x + width), + (int) (renderableBounds.top + y + height)); + } + } +} diff --git a/android/src/main/java/com/horcrux/svg/FilterView.java b/android/src/main/java/com/horcrux/svg/FilterView.java index 528f4d1bd..cb85a5c70 100644 --- a/android/src/main/java/com/horcrux/svg/FilterView.java +++ b/android/src/main/java/com/horcrux/svg/FilterView.java @@ -3,7 +3,9 @@ import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Rect; +import android.graphics.RectF; import android.util.Log; import android.view.View; import com.facebook.react.bridge.Dynamic; @@ -14,37 +16,32 @@ class FilterView extends DefinitionView { private final HashMap mResultsMap = new HashMap<>(); - SVGLength mX; - SVGLength mY; - SVGLength mW; - SVGLength mH; - private FilterProperties.Units mFilterUnits; - - @SuppressWarnings({"FieldCanBeLocal", "unused"}) private FilterProperties.Units mPrimitiveUnits; + private final FilterRegion mFilterRegion; public FilterView(ReactContext reactContext) { super(reactContext); + mFilterRegion = new FilterRegion(); } public void setX(Dynamic x) { - mX = SVGLength.from(x); + mFilterRegion.setX(x); invalidate(); } public void setY(Dynamic y) { - mY = SVGLength.from(y); + mFilterRegion.setY(y); invalidate(); } public void setWidth(Dynamic width) { - mW = SVGLength.from(width); + mFilterRegion.setWidth(width); invalidate(); } public void setHeight(Dynamic height) { - mH = SVGLength.from(height); + mFilterRegion.setHeight(height); invalidate(); } @@ -68,8 +65,7 @@ void saveDefinition() { } } - public Bitmap applyFilter( - Bitmap source, Bitmap background, Rect renderableBounds, Rect canvasBounds) { + public Bitmap applyFilter(Bitmap source, Bitmap background, RectF renderableBounds) { mResultsMap.clear(); mResultsMap.put("SourceGraphic", source); mResultsMap.put("SourceAlpha", FilterUtils.applySourceAlphaFilter(source)); @@ -77,12 +73,20 @@ public Bitmap applyFilter( mResultsMap.put("BackgroundAlpha", FilterUtils.applySourceAlphaFilter(background)); Bitmap res = source; + Bitmap resultBitmap = Bitmap.createBitmap(res.getWidth(), res.getHeight(), res.getConfig()); + Canvas canvas = new Canvas(resultBitmap); + Rect cropRect; for (int i = 0; i < getChildCount(); i++) { View node = getChildAt(i); if (node instanceof FilterPrimitiveView) { FilterPrimitiveView currentFilter = (FilterPrimitiveView) node; - res = currentFilter.applyFilter(mResultsMap, res); + resultBitmap.eraseColor(Color.TRANSPARENT); + cropRect = + currentFilter.mFilterRegion.getCropRect( + currentFilter, this.mPrimitiveUnits, renderableBounds); + canvas.drawBitmap(currentFilter.applyFilter(mResultsMap, res), cropRect, cropRect, null); + res = resultBitmap.copy(Bitmap.Config.ARGB_8888, true); String resultName = currentFilter.getResult(); if (resultName != null) { mResultsMap.put(resultName, res); @@ -93,21 +97,8 @@ public Bitmap applyFilter( } // crop Bitmap to filter coordinates - int x, y, width, height; - if (this.mFilterUnits == FilterProperties.Units.USER_SPACE_ON_USE) { - x = (int) this.relativeOn(this.mX, canvasBounds.width()); - y = (int) this.relativeOn(this.mY, canvasBounds.height()); - width = (int) this.relativeOn(this.mW, canvasBounds.width()); - height = (int) this.relativeOn(this.mH, canvasBounds.height()); - } else { // FilterProperties.Units.OBJECT_BOUNDING_BOX - x = (int) this.relativeOnFraction(this.mX, renderableBounds.width()); - y = (int) this.relativeOnFraction(this.mY, renderableBounds.height()); - width = (int) this.relativeOnFraction(this.mW, renderableBounds.width()); - height = (int) this.relativeOnFraction(this.mH, renderableBounds.height()); - } - Rect cropRect = new Rect(x, y, x + width, y + height); - Bitmap resultBitmap = Bitmap.createBitmap(res.getWidth(), res.getHeight(), res.getConfig()); - Canvas canvas = new Canvas(resultBitmap); + resultBitmap.eraseColor(Color.TRANSPARENT); + cropRect = this.mFilterRegion.getCropRect(this, this.mFilterUnits, renderableBounds); canvas.drawBitmap(res, cropRect, cropRect, null); return resultBitmap; } diff --git a/android/src/main/java/com/horcrux/svg/RenderableView.java b/android/src/main/java/com/horcrux/svg/RenderableView.java index 7ee5e68bc..a57ea46a7 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableView.java +++ b/android/src/main/java/com/horcrux/svg/RenderableView.java @@ -352,24 +352,30 @@ void render(Canvas canvas, Paint paint, float opacity) { Paint bitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG); canvas.saveLayer(null, bitmapPaint); - Rect canvasBounds = this.getSvgView().getCanvasBounds(); + Bitmap backgroundBitmap = this.getSvgView().getCurrentBitmap(); // draw element to self bitmap Bitmap elementBitmap = - Bitmap.createBitmap( - canvasBounds.width(), canvasBounds.height(), Bitmap.Config.ARGB_8888); + Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888); Canvas elementCanvas = new Canvas(elementBitmap); + elementCanvas.setMatrix(canvas.getMatrix()); draw(elementCanvas, paint, opacity); + // get renderableBounds + this.initBounds(); + RectF clientRect = this.getClientRect(); + if (this instanceof ImageView && clientRect == null) { + return; + } // apply filters - Bitmap backgroundBitmap = this.getSvgView().getCurrentBitmap(); - elementBitmap = - filter.applyFilter( - elementBitmap, backgroundBitmap, elementCanvas.getClipBounds(), canvasBounds); + elementBitmap = filter.applyFilter(elementBitmap, backgroundBitmap, clientRect); - // draw bitmap to canvas + // draw bitmap 1:1 to canvas + int saveCount = canvas.save(); + canvas.setMatrix(null); canvas.drawBitmap(elementBitmap, 0, 0, bitmapPaint); + canvas.restoreToCount(saveCount); } else { canvas.saveLayer(null, paint); draw(canvas, paint, opacity); diff --git a/android/src/main/java/com/horcrux/svg/SvgView.java b/android/src/main/java/com/horcrux/svg/SvgView.java index 35531ad12..27d082b08 100644 --- a/android/src/main/java/com/horcrux/svg/SvgView.java +++ b/android/src/main/java/com/horcrux/svg/SvgView.java @@ -296,6 +296,18 @@ Rect getCanvasBounds() { return mCanvas.getClipBounds(); } + float getCanvasWidth() { + return mCanvas.getWidth(); + } + + float getCanvasHeight() { + return mCanvas.getHeight(); + } + + Matrix getCtm() { + return mCanvas.getMatrix(); + } + synchronized void drawChildren(final Canvas canvas) { mRendered = true; mCanvas = canvas; diff --git a/apple/Elements/RNSVGUse.mm b/apple/Elements/RNSVGUse.mm index 096c1c920..63b987c58 100644 --- a/apple/Elements/RNSVGUse.mm +++ b/apple/Elements/RNSVGUse.mm @@ -168,6 +168,7 @@ - (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect } CGRect bounds = definedTemplate.clientRect; self.clientRect = bounds; + self.pathBounds = definedTemplate.pathBounds; CGAffineTransform current = CGContextGetCTM(context); CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM); diff --git a/apple/Filters/RNSVGFilter.h b/apple/Filters/RNSVGFilter.h index 51a74ad6b..b9686c31a 100644 --- a/apple/Filters/RNSVGFilter.h +++ b/apple/Filters/RNSVGFilter.h @@ -1,3 +1,4 @@ +#import "RNSVGFilterRegion.h" #import "RNSVGNode.h" @interface RNSVGFilter : RNSVGNode @@ -14,5 +15,8 @@ renderableBounds:(CGRect)renderableBounds canvasBounds:(CGRect)canvasBounds ctm:(CGAffineTransform)ctm; +- (CGContext *)openContext:(CGSize)size; +- (void)endContext:(CGContext *)context; +- (CIImage *)getMaskFromRect:(CGContext *)context rect:(CGRect)rect ctm:(CGAffineTransform)ctm; @end diff --git a/apple/Filters/RNSVGFilter.mm b/apple/Filters/RNSVGFilter.mm index 055289fc7..152e869c8 100644 --- a/apple/Filters/RNSVGFilter.mm +++ b/apple/Filters/RNSVGFilter.mm @@ -72,8 +72,8 @@ - (void)prepareForRecycle [super prepareForRecycle]; _x = nil; _y = nil; - _height = nil; _width = nil; + _height = nil; _filterUnits = kRNSVGUnitsObjectBoundingBox; _primitiveUnits = kRNSVGUnitsUserSpaceOnUse; } @@ -99,14 +99,32 @@ - (CIImage *)applyFilter:(CIImage *)img [resultsMap setObject:backgroundImg forKey:@"BackgroundImage"]; [resultsMap setObject:applySourceAlphaFilter(backgroundImg) forKey:@"BackgroundAlpha"]; + // Setup crop filter + CGRect cropRect; + CIFilter *cropFilter = [CIFilter filterWithName:@"CIBlendWithMask"]; + [cropFilter setDefaults]; + [cropFilter setValue:nil forKey:@"inputBackgroundImage"]; + CGContext *cropContext = [self openContext:canvasBounds.size]; + CIImage *mask; + CIImage *result = img; RNSVGFilterPrimitive *currentFilter; for (RNSVGNode *node in self.subviews) { if ([node isKindOfClass:[RNSVGFilterPrimitive class]]) { currentFilter = (RNSVGFilterPrimitive *)node; - CGImageRef cgResult = [[RNSVGRenderUtils sharedCIContext] createCGImage:[currentFilter applyFilter:resultsMap - previousFilterResult:result - ctm:ctm] + cropRect = [[RNSVGFilterRegion regionWithX:currentFilter.x + y:currentFilter.y + width:currentFilter.width + height:currentFilter.height] getCropRect:currentFilter + units:self.primitiveUnits + renderableBounds:renderableBounds]; + mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm]; + [cropFilter setValue:[currentFilter applyFilter:resultsMap previousFilterResult:result ctm:ctm] + forKey:@"inputImage"]; + [cropFilter setValue:mask forKey:@"inputMaskImage"]; + CGContextClearRect(cropContext, canvasBounds); + + CGImageRef cgResult = [[RNSVGRenderUtils sharedCIContext] createCGImage:[cropFilter valueForKey:@"outputImage"] fromRect:[result extent]]; result = [CIImage imageWithCGImage:cgResult]; CGImageRelease(cgResult); @@ -118,8 +136,48 @@ - (CIImage *)applyFilter:(CIImage *)img } } - return result; - // TODO: Crop element to filter's x, y, width, height + cropRect = [[RNSVGFilterRegion regionWithX:self.x y:self.y width:self.width + height:self.height] getCropRect:self + units:self.filterUnits + renderableBounds:renderableBounds]; + mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm]; + [cropFilter setValue:result forKey:@"inputImage"]; + [cropFilter setValue:mask forKey:@"inputMaskImage"]; + [self endContext:cropContext]; + return [cropFilter valueForKey:@"outputImage"]; +} + +- (CGContext *)openContext:(CGSize)size +{ + UIGraphicsBeginImageContextWithOptions(size, NO, 1.0); + CGContextRef cropContext = UIGraphicsGetCurrentContext(); +#if TARGET_OS_OSX + CGFloat scale = [RNSVGRenderUtils getScreenScale]; + CGContextScaleCTM(cropContext, scale, scale); +#else + CGContextTranslateCTM(cropContext, 0, size.height); + CGContextScaleCTM(cropContext, 1, -1); +#endif + return cropContext; +} + +- (void)endContext:(CGContext *)context +{ + UIGraphicsEndImageContext(); +} + +- (CIImage *)getMaskFromRect:(CGContext *)context rect:(CGRect)rect ctm:(CGAffineTransform)ctm +{ + CGPathRef path = CGPathCreateWithRect(rect, nil); + path = CGPathCreateCopyByTransformingPath(path, &ctm); + + CGContextSetRGBFillColor(context, 255, 255, 255, 255); + CGContextAddPath(context, path); + CGContextFillPath(context); + + CGImageRef maskImage = CGBitmapContextCreateImage(context); + + return [CIImage imageWithCGImage:maskImage]; } static CIFilter *sourceAlphaFilter() diff --git a/apple/Filters/RNSVGFilterPrimitive.h b/apple/Filters/RNSVGFilterPrimitive.h index 2adeecf44..97724a538 100644 --- a/apple/Filters/RNSVGFilterPrimitive.h +++ b/apple/Filters/RNSVGFilterPrimitive.h @@ -1,3 +1,4 @@ +#import "RNSVGFilterRegion.h" #import "RNSVGNode.h" @interface RNSVGFilterPrimitive : RNSVGNode @@ -12,6 +13,5 @@ - (CIImage *)applyFilter:(NSMutableDictionary *)results previousFilterResult:(CIImage *)previous ctm:(CGAffineTransform)ctm; -- (CIImage *)cropResult:(CIImage *)result; @end diff --git a/apple/Filters/RNSVGFilterPrimitive.mm b/apple/Filters/RNSVGFilterPrimitive.mm index 6c547c81a..3cf9742cd 100644 --- a/apple/Filters/RNSVGFilterPrimitive.mm +++ b/apple/Filters/RNSVGFilterPrimitive.mm @@ -17,8 +17,8 @@ - (void)prepareForRecycle [super prepareForRecycle]; _x = nil; _y = nil; - _height = nil; _width = nil; + _height = nil; _result = nil; } #endif // RCT_NEW_ARCH_ENABLED @@ -32,6 +32,12 @@ - (void)parseReference { } +- (void)invalidate +{ + self.dirty = false; + [super invalidate]; +} + - (void)setX:(RNSVGLength *)x { if ([x isEqualTo:_x]) { @@ -42,12 +48,6 @@ - (void)setX:(RNSVGLength *)x [self invalidate]; } -- (void)invalidate -{ - self.dirty = false; - [super invalidate]; -} - - (void)setY:(RNSVGLength *)y { if ([y isEqualTo:_y]) { @@ -100,18 +100,4 @@ - (CIImage *)applyFilter:(NSMutableDictionary *)results return [self applyFilter:results previousFilterResult:previous]; } -- (CIImage *)cropResult:(CIImage *)result -{ - CIFilter *filter = [CIFilter filterWithName:@"CICrop"]; - [filter setDefaults]; - [filter setValue:result forKey:@"inputImage"]; - CGFloat x = [self relativeOnWidth:self.x]; - CGFloat y = [self relativeOnHeight:self.y]; - CGFloat width = [self relativeOnWidth:self.width]; - CGFloat height = [self relativeOnHeight:self.height]; - - [filter setValue:[CIVector vectorWithX:x Y:y Z:width W:height] forKey:@"inputRectangle"]; - return [filter valueForKey:@"outputImage"]; -} - @end diff --git a/apple/Filters/RNSVGFilterRegion.h b/apple/Filters/RNSVGFilterRegion.h new file mode 100644 index 000000000..15e55523a --- /dev/null +++ b/apple/Filters/RNSVGFilterRegion.h @@ -0,0 +1,19 @@ +#import "RNSVGNode.h" +#import "RNSVGUnits.h" + +#ifndef RNSVGFilterRegion_h +#define RNSVGFilterRegion_h + +@interface RNSVGFilterRegion : NSObject + +@property (nonatomic, strong) RNSVGLength *x; +@property (nonatomic, strong) RNSVGLength *y; +@property (nonatomic, strong) RNSVGLength *width; +@property (nonatomic, strong) RNSVGLength *height; + ++ (instancetype)regionWithX:(RNSVGLength *)x y:(RNSVGLength *)y width:(RNSVGLength *)width height:(RNSVGLength *)height; +- (CGRect)getCropRect:(RNSVGNode *)node units:(RNSVGUnits)units renderableBounds:(CGRect)renderableBounds; + +@end + +#endif /* RNSVGFilterRegion_h */ diff --git a/apple/Filters/RNSVGFilterRegion.mm b/apple/Filters/RNSVGFilterRegion.mm new file mode 100644 index 000000000..c86f4e674 --- /dev/null +++ b/apple/Filters/RNSVGFilterRegion.mm @@ -0,0 +1,65 @@ +#import + +@implementation RNSVGFilterRegion + +- (instancetype)init +{ + self = [super init]; + if (self) { + _x = [RNSVGLength lengthWithNumber:0]; + _y = [RNSVGLength lengthWithNumber:0]; + _width = [RNSVGLength lengthWithNumber:0]; + _height = [RNSVGLength lengthWithNumber:0]; + } + return self; +} + ++ (instancetype)regionWithX:(RNSVGLength *)x y:(RNSVGLength *)y width:(RNSVGLength *)width height:(RNSVGLength *)height +{ + RNSVGFilterRegion *filterRegion = [[self alloc] init]; + filterRegion.x = x; + filterRegion.y = y; + filterRegion.width = width; + filterRegion.height = height; + return filterRegion; +} + +- (void)setX:(RNSVGLength *)x +{ + _x = x; +} + +- (void)setY:(RNSVGLength *)y +{ + _y = y; +} + +- (void)setWidth:(RNSVGLength *)width +{ + _width = width; +} + +- (void)setHeight:(RNSVGLength *)height +{ + _height = height; +} + +- (CGRect)getCropRect:(RNSVGNode *)node units:(RNSVGUnits)units renderableBounds:(CGRect)renderableBounds +{ + CGFloat x, y, width, height; + if (units == kRNSVGUnitsObjectBoundingBox) { + x = [node relativeOnFraction:self.x relative:renderableBounds.size.width]; + y = [node relativeOnFraction:self.y relative:renderableBounds.size.height]; + width = [node relativeOnFraction:self.width relative:renderableBounds.size.width]; + height = [node relativeOnFraction:self.height relative:renderableBounds.size.height]; + return CGRectMake(renderableBounds.origin.x + x, renderableBounds.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]; + return CGRectMake(x, y, width, height); + } +} + +@end diff --git a/apps/examples/src/examples/Filters/FeOffset.tsx b/apps/examples/src/examples/Filters/FeOffset.tsx index a1dcbc06d..20e07c3ed 100644 --- a/apps/examples/src/examples/Filters/FeOffset.tsx +++ b/apps/examples/src/examples/Filters/FeOffset.tsx @@ -28,7 +28,7 @@ class ReferenceExample extends Component { const icon = ( - + diff --git a/src/elements/filters/Filter.tsx b/src/elements/filters/Filter.tsx index c2d6f8d09..4c516ce09 100644 --- a/src/elements/filters/Filter.tsx +++ b/src/elements/filters/Filter.tsx @@ -3,7 +3,6 @@ import { NativeMethods } from 'react-native'; import RNSVGFilter from '../../fabric/FilterNativeComponent'; import { NumberProp, Units } from '../../lib/extract/types'; import Shape from '../Shape'; -import warnOnce from 'warn-once'; export interface FilterProps { children?: React.ReactNode; @@ -13,7 +12,6 @@ export interface FilterProps { width?: NumberProp; height?: NumberProp; filterUnits?: Units; - // TODO: Implement primitiveUnits?: Units; } @@ -26,22 +24,20 @@ export default class Filter extends Shape { width: '120%', height: '120%', filterUnits: 'objectBoundingBox', - // primitiveUnits: 'userSpaceOnUse', + primitiveUnits: 'userSpaceOnUse', }; render() { const { id, x, y, width, height, filterUnits, primitiveUnits } = this.props; - warnOnce( - !!primitiveUnits, - "WARNING: Filter's `primitiveUnits` prop is not supported yet" - ); + const filterProps = { name: id, x, y, width, height, - filterUnits: filterUnits || 'objectBoundingBox', + filterUnits, + primitiveUnits, }; return (