feat: implement filter region lengths directly on RNSVGFilterRegion (#2485)

# Summary

[apple] Use filter region directly instead of creating new one on every
rerender.
[android] rename some variables and add temporary fix for null lengths
This commit is contained in:
Jakub Grzywacz
2024-10-14 14:47:44 +02:00
committed by GitHub
parent 2b905c4b41
commit 3aae632d1f
10 changed files with 85 additions and 77 deletions

View File

@@ -9,30 +9,30 @@ import java.util.HashMap;
@SuppressLint("ViewConstructor")
class FilterPrimitiveView extends DefinitionView {
private String mResult;
public final FilterRegion mFilterRegion;
public final FilterRegion mFilterSubregion;
public FilterPrimitiveView(ReactContext reactContext) {
super(reactContext);
mFilterRegion = new FilterRegion();
mFilterSubregion = new FilterRegion();
}
public void setX(Dynamic x) {
mFilterRegion.setX(x);
mFilterSubregion.setX(x);
invalidate();
}
public void setY(Dynamic y) {
mFilterRegion.setY(y);
mFilterSubregion.setY(y);
invalidate();
}
public void setWidth(Dynamic width) {
mFilterRegion.setWidth(width);
mFilterSubregion.setWidth(width);
invalidate();
}
public void setHeight(Dynamic height) {
mFilterRegion.setHeight(height);
mFilterSubregion.setHeight(height);
invalidate();
}

View File

@@ -26,24 +26,24 @@ public class FilterRegion {
mH = SVGLength.from(height);
}
public Rect getCropRect(VirtualView view, FilterProperties.Units units, RectF renderableBounds) {
public Rect getCropRect(VirtualView view, FilterProperties.Units units, RectF bounds) {
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());
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));
} 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());
x = view.relativeOnFraction(this.mX, bounds.width());
y = view.relativeOnFraction(this.mY, bounds.height());
width = view.relativeOnFraction(this.mW, bounds.width());
height = view.relativeOnFraction(this.mH, bounds.height());
return new Rect(
(int) (renderableBounds.left + x),
(int) (renderableBounds.top + y),
(int) (renderableBounds.left + x + width),
(int) (renderableBounds.top + y + height));
(int) (bounds.left + x),
(int) (bounds.top + y),
(int) (bounds.left + x + width),
(int) (bounds.top + y + height));
}
}
}

View File

@@ -55,6 +55,10 @@ class FilterView extends DefinitionView {
invalidate();
}
public FilterRegion getFilterRegion() {
return mFilterRegion;
}
@Override
void saveDefinition() {
if (mName != null) {
@@ -75,6 +79,8 @@ class FilterView extends DefinitionView {
Bitmap res = source;
Bitmap resultBitmap = Bitmap.createBitmap(res.getWidth(), res.getHeight(), res.getConfig());
Canvas canvas = new Canvas(resultBitmap);
Rect filterRegionRect =
this.mFilterRegion.getCropRect(this, this.mFilterUnits, renderableBounds);
Rect cropRect;
for (int i = 0; i < getChildCount(); i++) {
@@ -83,7 +89,7 @@ class FilterView extends DefinitionView {
FilterPrimitiveView currentFilter = (FilterPrimitiveView) node;
resultBitmap.eraseColor(Color.TRANSPARENT);
cropRect =
currentFilter.mFilterRegion.getCropRect(
currentFilter.mFilterSubregion.getCropRect(
currentFilter, this.mPrimitiveUnits, renderableBounds);
canvas.drawBitmap(currentFilter.applyFilter(mResultsMap, res), cropRect, cropRect, null);
res = resultBitmap.copy(Bitmap.Config.ARGB_8888, true);
@@ -98,8 +104,8 @@ class FilterView extends DefinitionView {
// crop Bitmap to filter coordinates
resultBitmap.eraseColor(Color.TRANSPARENT);
cropRect = this.mFilterRegion.getCropRect(this, this.mFilterUnits, renderableBounds);
canvas.drawBitmap(res, cropRect, cropRect, null);
canvas.drawBitmap(res, filterRegionRect, filterRegionRect, null);
return resultBitmap;
}
}

View File

@@ -3,12 +3,9 @@
@interface RNSVGFilter : RNSVGNode
@property (nonatomic, strong) RNSVGLength *x;
@property (nonatomic, strong) RNSVGLength *y;
@property (nonatomic, strong) RNSVGLength *width;
@property (nonatomic, strong) RNSVGLength *height;
@property (nonatomic, assign) RNSVGUnits filterUnits;
@property (nonatomic, assign) RNSVGUnits primitiveUnits;
@property (nonatomic, strong) RNSVGFilterRegion *filterRegion;
- (CIImage *)applyFilter:(CIImage *)img
backgroundImg:(CIImage *)backgroundImg
@@ -18,5 +15,9 @@
- (CGContext *)openContext:(CGSize)size;
- (void)endContext:(CGContext *)context;
- (CIImage *)getMaskFromRect:(CGContext *)context rect:(CGRect)rect ctm:(CGAffineTransform)ctm;
- (void)setX:(RNSVGLength *)x;
- (void)setY:(RNSVGLength *)y;
- (void)setWidth:(RNSVGLength *)width;
- (void)setHeight:(RNSVGLength *)height;
@end

View File

@@ -70,12 +70,9 @@ using namespace facebook::react;
- (void)prepareForRecycle
{
[super prepareForRecycle];
_x = nil;
_y = nil;
_width = nil;
_height = nil;
_filterUnits = kRNSVGUnitsObjectBoundingBox;
_primitiveUnits = kRNSVGUnitsUserSpaceOnUse;
_filterRegion = nil;
}
#endif // RCT_NEW_ARCH_ENABLED
@@ -83,6 +80,7 @@ using namespace facebook::react;
{
if (self = [super init]) {
resultsMap = [NSMutableDictionary dictionary];
_filterRegion = [[RNSVGFilterRegion alloc] init];
}
return self;
}
@@ -112,12 +110,9 @@ using namespace facebook::react;
for (RNSVGNode *node in self.subviews) {
if ([node isKindOfClass:[RNSVGFilterPrimitive class]]) {
currentFilter = (RNSVGFilterPrimitive *)node;
cropRect = [[RNSVGFilterRegion regionWithX:currentFilter.x
y:currentFilter.y
width:currentFilter.width
height:currentFilter.height] getCropRect:currentFilter
units:self.primitiveUnits
renderableBounds:renderableBounds];
cropRect = [currentFilter.filterSubregion getCropRect:currentFilter
units:self.primitiveUnits
bounds:renderableBounds];
mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm];
[cropFilter setValue:[currentFilter applyFilter:resultsMap previousFilterResult:result ctm:ctm]
forKey:@"inputImage"];
@@ -136,10 +131,7 @@ using namespace facebook::react;
}
}
cropRect = [[RNSVGFilterRegion regionWithX:self.x y:self.y width:self.width
height:self.height] getCropRect:self
units:self.filterUnits
renderableBounds:renderableBounds];
cropRect = [currentFilter.filterSubregion getCropRect:self units:self.filterUnits bounds:renderableBounds];
mask = [self getMaskFromRect:cropContext rect:cropRect ctm:ctm];
[cropFilter setValue:result forKey:@"inputImage"];
[cropFilter setValue:mask forKey:@"inputMaskImage"];
@@ -212,41 +204,41 @@ static CIImage *applySourceAlphaFilter(CIImage *inputImage)
- (void)setX:(RNSVGLength *)x
{
if ([x isEqualTo:_x]) {
if ([x isEqualTo:_filterRegion.x]) {
return;
}
_x = x;
[_filterRegion setX:x];
[self invalidate];
}
- (void)setY:(RNSVGLength *)y
{
if ([y isEqualTo:_y]) {
if ([y isEqualTo:_filterRegion.y]) {
return;
}
_y = y;
[_filterRegion setY:y];
[self invalidate];
}
- (void)setWidth:(RNSVGLength *)width
{
if ([width isEqualTo:_width]) {
if ([width isEqualTo:_filterRegion.width]) {
return;
}
_width = width;
[_filterRegion setWidth:width];
[self invalidate];
}
- (void)setHeight:(RNSVGLength *)height
{
if ([height isEqualTo:_height]) {
if ([height isEqualTo:_filterRegion.height]) {
return;
}
_height = height;
[_filterRegion setHeight:height];
[self invalidate];
}

View File

@@ -3,15 +3,16 @@
@interface RNSVGFilterPrimitive : RNSVGNode
@property (nonatomic, strong) RNSVGLength *x;
@property (nonatomic, strong) RNSVGLength *y;
@property (nonatomic, strong) RNSVGLength *width;
@property (nonatomic, strong) RNSVGLength *height;
@property (nonatomic, strong) NSString *result;
@property (nonatomic, strong) RNSVGFilterRegion *filterSubregion;
- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results previousFilterResult:(CIImage *)previous;
- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results
previousFilterResult:(CIImage *)previous
ctm:(CGAffineTransform)ctm;
- (void)setX:(RNSVGLength *)x;
- (void)setY:(RNSVGLength *)y;
- (void)setWidth:(RNSVGLength *)width;
- (void)setHeight:(RNSVGLength *)height;
@end

View File

@@ -1,5 +1,6 @@
#import <RNSVGFilterPrimitive.h>
#import <RNSVGNode.h>
#import "RNSVGFilter.h"
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTConversions.h>
@@ -15,14 +16,20 @@
- (void)prepareForRecycle
{
[super prepareForRecycle];
_x = nil;
_y = nil;
_width = nil;
_height = nil;
_filterSubregion = nil;
_result = nil;
}
#endif // RCT_NEW_ARCH_ENABLED
- (instancetype)init
{
self = [super init];
if (self) {
_filterSubregion = [[RNSVGFilterRegion alloc] init];
}
return self;
}
- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
@@ -40,41 +47,41 @@
- (void)setX:(RNSVGLength *)x
{
if ([x isEqualTo:_x]) {
if ([x isEqualTo:_filterSubregion.x]) {
return;
}
_x = x;
[_filterSubregion setX:x];
[self invalidate];
}
- (void)setY:(RNSVGLength *)y
{
if ([y isEqualTo:_y]) {
if ([y isEqualTo:_filterSubregion.y]) {
return;
}
_y = y;
[_filterSubregion setY:y];
[self invalidate];
}
- (void)setWidth:(RNSVGLength *)width
{
if ([width isEqualTo:_width]) {
if ([width isEqualTo:_filterSubregion.width]) {
return;
}
_width = width;
[_filterSubregion setWidth:width];
[self invalidate];
}
- (void)setHeight:(RNSVGLength *)height
{
if ([height isEqualTo:_height]) {
if ([height isEqualTo:_filterSubregion.height]) {
return;
}
_height = height;
[_filterSubregion setHeight:height];
[self invalidate];
}

View File

@@ -12,7 +12,7 @@
@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;
- (CGRect)getCropRect:(RNSVGNode *)node units:(RNSVGUnits)units bounds:(CGRect)bounds;
@end

View File

@@ -5,12 +5,6 @@
- (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;
}
@@ -44,15 +38,15 @@
_height = height;
}
- (CGRect)getCropRect:(RNSVGNode *)node units:(RNSVGUnits)units renderableBounds:(CGRect)renderableBounds
- (CGRect)getCropRect:(RNSVGNode *)node units:(RNSVGUnits)units bounds:(CGRect)bounds
{
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);
x = [node relativeOnFraction:self.x relative:bounds.size.width];
y = [node relativeOnFraction:self.y relative:bounds.size.height];
width = [node relativeOnFraction:self.width relative:bounds.size.width];
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];

View File

@@ -141,4 +141,11 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
- (void)clearPath;
/**
* get canvas dimensions
*/
- (CGFloat)getCanvasWidth;
- (CGFloat)getCanvasHeight;
@end