From 20cfa5f3d16d25824ea403e96cdf6f192cfe0cc4 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Mon, 15 Jul 2024 10:27:00 +0200 Subject: [PATCH 01/12] feat: introduce FeGaussianBlur component --- react-native.config.js | 1 + src/ReactNativeSVG.ts | 5 +++ src/elements/filters/FeGaussianBlur.tsx | 40 +++++++++++++++++++++ src/fabric/FeGaussianBlurNativeComponent.ts | 27 ++++++++++++++ src/fabric/index.ts | 2 ++ src/lib/extract/extractFilter.ts | 26 ++++++++++++++ src/xml.tsx | 2 ++ 7 files changed, 103 insertions(+) create mode 100644 src/elements/filters/FeGaussianBlur.tsx create mode 100644 src/fabric/FeGaussianBlurNativeComponent.ts diff --git a/react-native.config.js b/react-native.config.js index 3d2f58f8f..f4c94a1f2 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -18,6 +18,7 @@ module.exports = { 'RNSVGClipPathComponentDescriptor', 'RNSVGDefsComponentDescriptor', 'RNSVGFeColorMatrixComponentDescriptor', + 'RNSVGFeGaussianBlurComponentDescriptor', 'RNSVGFilterComponentDescriptor', 'RNSVGEllipseComponentDescriptor', 'RNSVGForeignObjectComponentDescriptor', diff --git a/src/ReactNativeSVG.ts b/src/ReactNativeSVG.ts index e7dc7d1e0..c86002ac9 100644 --- a/src/ReactNativeSVG.ts +++ b/src/ReactNativeSVG.ts @@ -25,6 +25,7 @@ import Marker from './elements/Marker'; import ForeignObject from './elements/ForeignObject'; import Filter from './elements/filters/Filter'; import FeColorMatrix from './elements/filters/FeColorMatrix'; +import FeGaussianBlur from './elements/filters/FeGaussianBlur'; import { parse, @@ -71,6 +72,7 @@ import { RNSVGUse, RNSVGFilter, RNSVGFeColorMatrix, + RNSVGFeGaussianBlur, } from './fabric'; export { @@ -109,6 +111,7 @@ export type { MarkerProps } from './elements/Marker'; export type { ForeignObjectProps } from './elements/ForeignObject'; export type { FilterProps } from './elements/filters/Filter'; export type { FeColorMatrixProps } from './elements/filters/FeColorMatrix'; +export type { FeGaussianBlurProps } from './elements/filters/FeGaussianBlur'; export type { FilterPrimitiveCommonProps } from './elements/filters/FilterPrimitive'; export * from './lib/extract/types'; @@ -149,6 +152,7 @@ export { Shape, Filter, FeColorMatrix, + FeGaussianBlur, RNSVGMarker, RNSVGMask, RNSVGPattern, @@ -173,6 +177,7 @@ export { RNSVGForeignObject, RNSVGFilter, RNSVGFeColorMatrix, + RNSVGFeGaussianBlur, }; export type { diff --git a/src/elements/filters/FeGaussianBlur.tsx b/src/elements/filters/FeGaussianBlur.tsx new file mode 100644 index 000000000..6ae09725d --- /dev/null +++ b/src/elements/filters/FeGaussianBlur.tsx @@ -0,0 +1,40 @@ +import * as React from 'react'; +import { NativeMethods } from 'react-native'; +import RNSVGFeGaussianBlur from '../../fabric/FeGaussianBlurNativeComponent'; +import { + extractFeGaussianBlur, + extractFilter, +} from '../../lib/extract/extractFilter'; +import { FilterEdgeMode, NumberProp } from '../../lib/extract/types'; +import FilterPrimitive from './FilterPrimitive'; + +export interface FeGaussianBlurProps { + in?: string; + stdDeviation?: NumberProp; + // edgeMode is hard to implement and not supported by any + // browser except safari, so it's not implemented for now + // https://caniuse.com/mdn-api_svgfegaussianblurelement_edgemode + edgeMode?: FilterEdgeMode; +} + +export default class FeGaussianBlur extends FilterPrimitive { + static displayName = 'FeGaussianBlur'; + + static defaultProps = { + ...this.defaultPrimitiveProps, + stdDeviation: 0, + edgeMode: 'none', + }; + + render() { + return ( + + this.refMethod(ref as (FeGaussianBlur & NativeMethods) | null) + } + {...extractFilter(this.props)} + {...extractFeGaussianBlur(this.props)} + /> + ); + } +} diff --git a/src/fabric/FeGaussianBlurNativeComponent.ts b/src/fabric/FeGaussianBlurNativeComponent.ts new file mode 100644 index 000000000..fe25107e4 --- /dev/null +++ b/src/fabric/FeGaussianBlurNativeComponent.ts @@ -0,0 +1,27 @@ +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import type { ViewProps } from './utils'; + +import { NumberProp } from '../lib/extract/types'; +import type { UnsafeMixed } from './codegenUtils'; +import { Float, WithDefault } from 'react-native/Libraries/Types/CodegenTypes'; + +type FilterEdgeMode = 'duplicate' | 'wrap' | 'none'; + +interface FilterPrimitiveStandardAttributes { + x?: UnsafeMixed; + y?: UnsafeMixed; + width?: UnsafeMixed; + height?: UnsafeMixed; + result?: string; +} + +export interface NativeProps + extends ViewProps, + FilterPrimitiveStandardAttributes { + in1?: string; + stdDeviationX?: Float; + stdDeviationY?: Float; + edgeMode?: WithDefault; +} + +export default codegenNativeComponent('RNSVGFeGaussianBlur'); diff --git a/src/fabric/index.ts b/src/fabric/index.ts index e7f395049..40e53e645 100644 --- a/src/fabric/index.ts +++ b/src/fabric/index.ts @@ -22,6 +22,7 @@ import RNSVGTSpan from './TSpanNativeComponent'; import RNSVGUse from './UseNativeComponent'; import RNSVGFilter from './FilterNativeComponent'; import RNSVGFeColorMatrix from './FeColorMatrixNativeComponent'; +import RNSVGFeGaussianBlur from './FeGaussianBlurNativeComponent'; export { RNSVGCircle, @@ -48,4 +49,5 @@ export { RNSVGUse, RNSVGFilter, RNSVGFeColorMatrix, + RNSVGFeGaussianBlur, }; diff --git a/src/lib/extract/extractFilter.ts b/src/lib/extract/extractFilter.ts index 63a8e4bc3..9c64f5be5 100644 --- a/src/lib/extract/extractFilter.ts +++ b/src/lib/extract/extractFilter.ts @@ -1,5 +1,7 @@ import { FeColorMatrixProps as FeColorMatrixComponentProps } from '../../elements/filters/FeColorMatrix'; +import { FeGaussianBlurProps as FeGaussianBlurComponentProps } from '../../elements/filters/FeGaussianBlur'; import { NativeProps as FeColorMatrixNativeProps } from '../../fabric/FeColorMatrixNativeComponent'; +import { NativeProps as FeGaussianBlurNativeProps } from '../../fabric/FeGaussianBlurNativeComponent'; import { NumberProp } from './types'; const spaceReg = /\s+/; @@ -53,3 +55,27 @@ export const extractFeColorMatrix = ( return extracted; }; + +export const extractFeGaussianBlur = ( + props: FeGaussianBlurComponentProps +): FeGaussianBlurNativeProps => { + const extracted: FeGaussianBlurNativeProps = {}; + + if (props.in) extracted.in1 = props.in; + if (props.stdDeviation !== undefined) { + if ( + typeof props.stdDeviation === 'number' || + (typeof props.stdDeviation === 'string' && + !props.stdDeviation.match(spaceReg)) + ) { + extracted.stdDeviationX = Number(props.stdDeviation) || 0; + extracted.stdDeviationY = Number(props.stdDeviation) || 0; + } else { + const stdDeviation = props.stdDeviation.split(spaceReg); + extracted.stdDeviationX = Number(stdDeviation[0]) || 0; + extracted.stdDeviationY = Number(stdDeviation[1]) || 0; + } + } + if (props.edgeMode) extracted.edgeMode = props.edgeMode; + return extracted; +}; diff --git a/src/xml.tsx b/src/xml.tsx index 77e49c9a2..42c3caeb6 100644 --- a/src/xml.tsx +++ b/src/xml.tsx @@ -27,6 +27,7 @@ import Mask from './elements/Mask'; import Marker from './elements/Marker'; import Filter from './elements/filters/Filter'; import FeColorMatrix from './elements/filters/FeColorMatrix'; +import FeGaussianBlur from './elements/filters/FeGaussianBlur'; export const tags: { [tag: string]: ComponentType } = { svg: Svg, @@ -54,6 +55,7 @@ export const tags: { [tag: string]: ComponentType } = { marker: Marker, filter: Filter, feColorMatrix: FeColorMatrix, + feGaussianBlur: FeGaussianBlur, }; function missingTag() { From c497a443cba2608822bf0bb0715d07faccedac61 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Mon, 15 Jul 2024 10:30:15 +0200 Subject: [PATCH 02/12] feat: implement FeGaussianBlur on apple --- .../{RNSVGEdgeModeTypes.h => RNSVGEdgeMode.h} | 2 +- apple/Filters/RNSVGFeGaussianBlur.h | 11 ++ apple/Filters/RNSVGFeGaussianBlur.mm | 129 ++++++++++++++++++ apple/Utils/RCTConvert+RNSVG.h | 2 +- apple/Utils/RCTConvert+RNSVG.mm | 4 +- apple/Utils/RNSVGConvert.h | 3 +- apple/Utils/RNSVGConvert.mm | 16 ++- .../ViewManagers/RNSVGFeGaussianBlurManager.h | 5 + .../RNSVGFeGaussianBlurManager.mm | 19 +++ 9 files changed, 185 insertions(+), 6 deletions(-) rename apple/Filters/{RNSVGEdgeModeTypes.h => RNSVGEdgeMode.h} (66%) create mode 100644 apple/Filters/RNSVGFeGaussianBlur.h create mode 100644 apple/Filters/RNSVGFeGaussianBlur.mm create mode 100644 apple/ViewManagers/RNSVGFeGaussianBlurManager.h create mode 100644 apple/ViewManagers/RNSVGFeGaussianBlurManager.mm diff --git a/apple/Filters/RNSVGEdgeModeTypes.h b/apple/Filters/RNSVGEdgeMode.h similarity index 66% rename from apple/Filters/RNSVGEdgeModeTypes.h rename to apple/Filters/RNSVGEdgeMode.h index 8f6ae4dff..a39a38780 100644 --- a/apple/Filters/RNSVGEdgeModeTypes.h +++ b/apple/Filters/RNSVGEdgeMode.h @@ -1,4 +1,4 @@ -typedef CF_ENUM(int32_t, RNSVGEdgeModeTypes) { +typedef CF_ENUM(int32_t, RNSVGEdgeMode) { SVG_EDGEMODE_UNKNOWN, SVG_EDGEMODE_DUPLICATE, SVG_EDGEMODE_WRAP, diff --git a/apple/Filters/RNSVGFeGaussianBlur.h b/apple/Filters/RNSVGFeGaussianBlur.h new file mode 100644 index 000000000..f0cea5f1a --- /dev/null +++ b/apple/Filters/RNSVGFeGaussianBlur.h @@ -0,0 +1,11 @@ +#import "RNSVGEdgeMode.h" +#import "RNSVGFilterPrimitive.h" + +@interface RNSVGFeGaussianBlur : RNSVGFilterPrimitive + +@property (nonatomic, strong) NSString *in1; +@property (nonatomic, strong) NSNumber *stdDeviationX; +@property (nonatomic, strong) NSNumber *stdDeviationY; +@property (nonatomic, assign) RNSVGEdgeMode edgeMode; + +@end diff --git a/apple/Filters/RNSVGFeGaussianBlur.mm b/apple/Filters/RNSVGFeGaussianBlur.mm new file mode 100644 index 000000000..850d82a4e --- /dev/null +++ b/apple/Filters/RNSVGFeGaussianBlur.mm @@ -0,0 +1,129 @@ +#import "RNSVGFeGaussianBlur.h" + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#import +#import +#import "RNSVGConvert.h" +#import "RNSVGFabricConversions.h" +#endif // RCT_NEW_ARCH_ENABLED + +@implementation RNSVGFeGaussianBlur + +#ifdef RCT_NEW_ARCH_ENABLED +using namespace facebook::react; + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + } + return self; +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + const auto &newProps = static_cast(*props); + + self.in1 = RCTNSStringFromStringNilIfEmpty(newProps.in1); + self.stdDeviationX = [NSNumber numberWithFloat:newProps.stdDeviationX]; + self.stdDeviationY = [NSNumber numberWithFloat:newProps.stdDeviationY]; + self.edgeMode = [RNSVGConvert RNSVGEdgeModeFromCppEquivalent:newProps.edgeMode]; + + setCommonFilterProps(newProps, self); + _props = std::static_pointer_cast(props); +} + +- (void)prepareForRecycle +{ + [super prepareForRecycle]; + _in1 = nil; + _stdDeviationX = nil; + _stdDeviationY = nil; + _edgeMode = SVG_EDGEMODE_UNKNOWN; +} +#endif // RCT_NEW_ARCH_ENABLED + +- (CIImage *)applyFilter:(NSMutableDictionary *)results previousFilterResult:(CIImage *)previous +{ + CIImage *inResults = self.in1 ? [results objectForKey:self.in1] : nil; + CIImage *inputImage = inResults ? inResults : previous; + + if (!inputImage) { + return nil; + } + + CIFilter *filter = [CIFilter filterWithName:(_stdDeviationX == _stdDeviationY ? @"CIGaussianBlur" : @"CIMotionBlur")]; + [filter setDefaults]; + [filter setValue:inputImage forKey:@"inputImage"]; + [filter setValue:_stdDeviationX forKey:@"inputRadius"]; + + if (_stdDeviationX != _stdDeviationY) { + // X axis + [filter setValue:[NSNumber numberWithFloat:0] forKey:@"inputAngle"]; + // Y axis + [filter setValue:[filter valueForKey:@"outputImage"] forKey:@"inputImage"]; + [filter setValue:_stdDeviationY forKey:@"inputRadius"]; + [filter setValue:[NSNumber numberWithFloat:M_PI_2] forKey:@"inputAngle"]; + } + + return [filter valueForKey:@"outputImage"]; +} + +- (void)setIn1:(NSString *)in1 +{ + if ([in1 isEqualToString:_in1]) { + return; + } + + _in1 = in1; + [self invalidate]; +} + +- (void)setStdDeviationX:(NSNumber *)stdDeviationX +{ + if (stdDeviationX == _stdDeviationX) { + return; + } + + _stdDeviationX = stdDeviationX; + [self invalidate]; +} + +- (void)setStdDeviationY:(NSNumber *)stdDeviationY +{ + if (stdDeviationY == _stdDeviationY) { + return; + } + + _stdDeviationY = stdDeviationY; + [self invalidate]; +} + +- (void)setEdgeMode:(RNSVGEdgeMode)edgeMode +{ + if (edgeMode == _edgeMode) { + return; + } + + _edgeMode = edgeMode; + [self invalidate]; +} + +#ifdef RCT_NEW_ARCH_ENABLED +Class RNSVGFeGaussianBlurCls(void) +{ + return RNSVGFeGaussianBlur.class; +} +#endif // RCT_NEW_ARCH_ENABLED + +@end diff --git a/apple/Utils/RCTConvert+RNSVG.h b/apple/Utils/RCTConvert+RNSVG.h index ef8acda63..d0714ac92 100644 --- a/apple/Utils/RCTConvert+RNSVG.h +++ b/apple/Utils/RCTConvert+RNSVG.h @@ -12,7 +12,7 @@ #import "RCTConvert+RNSVG.h" #import "RNSVGCGFCRule.h" #import "RNSVGColorMatrixType.h" -#import "RNSVGEdgeModeTypes.h" +#import "RNSVGEdgeMode.h" #import "RNSVGLength.h" #import "RNSVGMaskType.h" #import "RNSVGPathParser.h" diff --git a/apple/Utils/RCTConvert+RNSVG.mm b/apple/Utils/RCTConvert+RNSVG.mm index 5fa3ab615..863cb7255 100644 --- a/apple/Utils/RCTConvert+RNSVG.mm +++ b/apple/Utils/RCTConvert+RNSVG.mm @@ -52,13 +52,13 @@ @implementation RCTConvert (RNSVG) intValue) RCT_ENUM_CONVERTER( - RNSVGEdgeModeTypes, + RNSVGEdgeMode, (@{ @"duplicate" : @(SVG_EDGEMODE_DUPLICATE), @"wrap" : @(SVG_EDGEMODE_WRAP), @"none" : @(SVG_EDGEMODE_NONE), }), - SVG_FECOLORMATRIX_TYPE_UNKNOWN, + SVG_EDGEMODE_UNKNOWN, intValue) RCT_ENUM_CONVERTER( diff --git a/apple/Utils/RNSVGConvert.h b/apple/Utils/RNSVGConvert.h index c40403560..ee83027b4 100644 --- a/apple/Utils/RNSVGConvert.h +++ b/apple/Utils/RNSVGConvert.h @@ -1,7 +1,7 @@ #ifdef RCT_NEW_ARCH_ENABLED #import #import "RNSVGColorMatrixType.h" -#import "RNSVGEdgeModeTypes.h" +#import "RNSVGEdgeMode.h" #import "RNSVGUnits.h" namespace react = facebook::react; @@ -11,6 +11,7 @@ namespace react = facebook::react; + (RNSVGUnits)RNSVGUnitsFromFilterUnitsCppEquivalent:(react::RNSVGFilterFilterUnits)svgUnits; + (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimitiveUnits)svgUnits; + (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type; ++ (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeMode)edgeMode; @end diff --git a/apple/Utils/RNSVGConvert.mm b/apple/Utils/RNSVGConvert.mm index 80548b8e0..b557ff543 100644 --- a/apple/Utils/RNSVGConvert.mm +++ b/apple/Utils/RNSVGConvert.mm @@ -23,7 +23,7 @@ + (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimi } } -+ (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type; ++ (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type { switch (type) { case react::RNSVGFeColorMatrixType::Matrix: @@ -37,6 +37,20 @@ + (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeCol } } ++ (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeMode)edgeMode +{ + switch (edgeMode) { + case react::RNSVGFeGaussianBlurEdgeMode::Duplicate: + return SVG_EDGEMODE_DUPLICATE; + case react::RNSVGFeGaussianBlurEdgeMode::Wrap: + return SVG_EDGEMODE_WRAP; + case react::RNSVGFeGaussianBlurEdgeMode::None: + return SVG_EDGEMODE_NONE; + default: + return SVG_EDGEMODE_UNKNOWN; + } +} + @end #endif // RCT_NEW_ARCH_ENABLED diff --git a/apple/ViewManagers/RNSVGFeGaussianBlurManager.h b/apple/ViewManagers/RNSVGFeGaussianBlurManager.h new file mode 100644 index 000000000..a86b8abfc --- /dev/null +++ b/apple/ViewManagers/RNSVGFeGaussianBlurManager.h @@ -0,0 +1,5 @@ +#import "RNSVGFilterPrimitiveManager.h" + +@interface RNSVGFeGaussianBlurManager : RNSVGFilterPrimitiveManager + +@end diff --git a/apple/ViewManagers/RNSVGFeGaussianBlurManager.mm b/apple/ViewManagers/RNSVGFeGaussianBlurManager.mm new file mode 100644 index 000000000..d9712a22b --- /dev/null +++ b/apple/ViewManagers/RNSVGFeGaussianBlurManager.mm @@ -0,0 +1,19 @@ +#import "RNSVGFeGaussianBlurManager.h" +#import "RNSVGEdgeMode.h" +#import "RNSVGFeGaussianBlur.h" + +@implementation RNSVGFeGaussianBlurManager + +RCT_EXPORT_MODULE() + +- (RNSVGFeGaussianBlur *)node +{ + return [RNSVGFeGaussianBlur new]; +} + +RCT_EXPORT_VIEW_PROPERTY(in1, NSString) +RCT_EXPORT_VIEW_PROPERTY(stdDeviationX, NSNumber) +RCT_EXPORT_VIEW_PROPERTY(stdDeviationY, NSNumber) +RCT_EXPORT_VIEW_PROPERTY(edgeMode, RNSVGEdgeMode) + +@end From 1f3fc111d9b13b3ca7c57f53f2b11e28d420e15d Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Mon, 15 Jul 2024 11:21:20 +0200 Subject: [PATCH 03/12] feat: implement FeGaussianBlur on android with limited support by RenderScript --- .../com/horcrux/svg/FeGaussianBlurView.java | 74 +++++++++++++++++++ .../horcrux/svg/RenderableViewManager.java | 60 +++++++++++++++ .../main/java/com/horcrux/svg/SvgPackage.java | 9 +++ 3 files changed, 143 insertions(+) create mode 100644 android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java diff --git a/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java b/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java new file mode 100644 index 000000000..1999eaeb3 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java @@ -0,0 +1,74 @@ +package com.horcrux.svg; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import com.facebook.react.bridge.ReactContext; +import java.util.HashMap; + +@SuppressLint("ViewConstructor") +class FeGaussianBlurView extends FilterPrimitiveView { + String mIn1; + float mStdDeviationX; + float mStdDeviationY; + FilterProperties.EdgeMode mEdgeMode; + + public FeGaussianBlurView(ReactContext reactContext) { + super(reactContext); + } + + public void setIn1(String in1) { + this.mIn1 = in1; + invalidate(); + } + + public void setStdDeviationX(float stdDeviationX) { + this.mStdDeviationX = stdDeviationX; + invalidate(); + } + + public void setStdDeviationY(float stdDeviationY) { + this.mStdDeviationY = stdDeviationY; + invalidate(); + } + + public void setEdgeMode(String edgeMode) { + this.mEdgeMode = FilterProperties.EdgeMode.getEnum(edgeMode); + invalidate(); + } + + @Override + public Bitmap applyFilter(HashMap resultsMap, Bitmap prevResult) { + Bitmap source = getSource(resultsMap, prevResult, this.mIn1); + return blur(getContext(), source); + } + + private Bitmap blur(Context context, Bitmap bitmap) { + float stdDeviation = Math.max(mStdDeviationX, mStdDeviationY); + if (stdDeviation <= 0) return bitmap; + final float maxRadius = 25.0f; + float radius = Math.min(stdDeviation, maxRadius); + + Bitmap outputBitmap = Bitmap.createBitmap(bitmap); + + RenderScript rs = RenderScript.create(context); + ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + + Allocation tmpIn = Allocation.createFromBitmap(rs, bitmap); + Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap); + + blurScript.setRadius(radius); + blurScript.setInput(tmpIn); + blurScript.forEach(tmpOut); + + tmpOut.copyTo(outputBitmap); + + rs.destroy(); + + return Bitmap.createScaledBitmap(outputBitmap, bitmap.getWidth(), bitmap.getHeight(), false); + } +} diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index bf9de5d33..35af96bc8 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -105,6 +105,8 @@ import com.facebook.react.viewmanagers.RNSVGEllipseManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerDelegate; import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface; +import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerDelegate; +import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerInterface; import com.facebook.react.viewmanagers.RNSVGFilterManagerDelegate; import com.facebook.react.viewmanagers.RNSVGFilterManagerInterface; import com.facebook.react.viewmanagers.RNSVGForeignObjectManagerDelegate; @@ -584,6 +586,7 @@ protected enum SVGClass { RNSVGMask, RNSVGFilter, RNSVGFeColorMatrix, + RNSVGFeGaussianBlur, RNSVGMarker, RNSVGForeignObject, } @@ -632,6 +635,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex return new FilterView(reactContext); case RNSVGFeColorMatrix: return new FeColorMatrixView(reactContext); + case RNSVGFeGaussianBlur: + return new FeGaussianBlurView(reactContext); case RNSVGMarker: return new MarkerView(reactContext); case RNSVGForeignObject: @@ -1387,6 +1392,61 @@ public void setValues(FeColorMatrixView node, @Nullable ReadableArray values) { } } + static class FeGaussianBlurManager extends VirtualViewManager + implements RNSVGFeGaussianBlurManagerInterface { + FeGaussianBlurManager() { + super(SVGClass.RNSVGFeGaussianBlur); + mDelegate = new RNSVGFeGaussianBlurManagerDelegate(this); + } + + public static final String REACT_CLASS = "RNSVGFeGaussianBlur"; + + @ReactProp(name = "x") + public void setX(FeGaussianBlurView node, Dynamic x) { + node.setX(x); + } + + @ReactProp(name = "y") + public void setY(FeGaussianBlurView node, Dynamic y) { + node.setY(y); + } + + @ReactProp(name = "width") + public void setWidth(FeGaussianBlurView node, Dynamic width) { + node.setWidth(width); + } + + @ReactProp(name = "height") + public void setHeight(FeGaussianBlurView node, Dynamic height) { + node.setHeight(height); + } + + @ReactProp(name = "result") + public void setResult(FeGaussianBlurView node, String result) { + node.setResult(result); + } + + @ReactProp(name = "in1") + public void setIn1(FeGaussianBlurView node, String in1) { + node.setIn1(in1); + } + + @ReactProp(name = "stdDeviationX") + public void setStdDeviationX(FeGaussianBlurView node, float stdDeviationX) { + node.setStdDeviationX(stdDeviationX); + } + + @ReactProp(name = "stdDeviationY") + public void setStdDeviationY(FeGaussianBlurView node, float stdDeviationY) { + node.setStdDeviationY(stdDeviationY); + } + + @ReactProp(name = "values") + public void setEdgeMode(FeGaussianBlurView node, String edgeMode) { + node.setEdgeMode(edgeMode); + } + } + static class ForeignObjectManager extends GroupViewManagerAbstract implements RNSVGForeignObjectManagerInterface { ForeignObjectManager() { diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index bab495487..d33f275f4 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -223,6 +223,15 @@ public NativeModule get() { return new FeColorMatrixManager(); } })); + specs.put( + FeGaussianBlurManager.REACT_CLASS, + ModuleSpec.viewManagerSpec( + new Provider() { + @Override + public NativeModule get() { + return new FeGaussianBlurManager(); + } + })); specs.put( ForeignObjectManager.REACT_CLASS, ModuleSpec.viewManagerSpec( From 616b1ef78c606eed93e2810a44c77c399fbae40b Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Wed, 17 Jul 2024 16:21:00 +0200 Subject: [PATCH 04/12] feat: scale up Android blur radius for FeGaussianBlurView --- android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java b/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java index 1999eaeb3..c4d552b9f 100644 --- a/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java +++ b/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java @@ -48,7 +48,8 @@ public Bitmap applyFilter(HashMap resultsMap, Bitmap prevResult) } private Bitmap blur(Context context, Bitmap bitmap) { - float stdDeviation = Math.max(mStdDeviationX, mStdDeviationY); + // Android blur radius is much weaker than SVG's, so we need to scale it up. + float stdDeviation = Math.max(mStdDeviationX, mStdDeviationY) * 2; if (stdDeviation <= 0) return bitmap; final float maxRadius = 25.0f; float radius = Math.min(stdDeviation, maxRadius); From 1169ba0d9801a74630522749e12d4eec61895532 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Wed, 17 Jul 2024 16:29:17 +0200 Subject: [PATCH 05/12] refactor: remove unnecessary code in RNSVGFeColorMatrix --- apple/Filters/RNSVGFeColorMatrix.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/apple/Filters/RNSVGFeColorMatrix.mm b/apple/Filters/RNSVGFeColorMatrix.mm index 701657780..216fc5693 100644 --- a/apple/Filters/RNSVGFeColorMatrix.mm +++ b/apple/Filters/RNSVGFeColorMatrix.mm @@ -160,8 +160,6 @@ - (CIImage *)applyFilter:(NSMutableDictionary *)results p [filter setValue:inputImage forKey:@"inputImage"]; return [filter valueForKey:@"outputImage"]; - - return nil; } #ifdef RCT_NEW_ARCH_ENABLED From d0cf320bebff487f6fc3a3660dbfd087cd061ca2 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Wed, 17 Jul 2024 16:53:04 +0200 Subject: [PATCH 06/12] feat: add FeGaussianBlur examples --- .../src/examples/Filters/FeGaussianBlur.tsx | 92 +++++++++++++++++++ .../src/examples/Filters/examples.tsx | 3 +- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 apps/examples/src/examples/Filters/FeGaussianBlur.tsx diff --git a/apps/examples/src/examples/Filters/FeGaussianBlur.tsx b/apps/examples/src/examples/Filters/FeGaussianBlur.tsx new file mode 100644 index 000000000..e3653ed17 --- /dev/null +++ b/apps/examples/src/examples/Filters/FeGaussianBlur.tsx @@ -0,0 +1,92 @@ +import React, {Component} from 'react'; +import {Circle, FeGaussianBlur, Filter, G, Svg} from 'react-native-svg'; + +class StdDeviation5Example extends Component { + static title = 'stdDeviation="5"'; + render() { + return ( + + + + + + + + + + + ); + } +} +class StdDeviation20Example extends Component { + static title = 'stdDeviation="20"'; + render() { + return ( + + + + + + + + + + + ); + } +} +class StdDeviation250Example extends Component { + static title = 'stdDeviation="25 0"'; + render() { + return ( + + + + + + + + + + + ); + } +} +class StdDeviation050Example extends Component { + static title = 'stdDeviation="0 50"'; + render() { + return ( + + + + + + + + + + + ); + } +} + +const icon = ( + + + + + + + + + + +); + +const samples = [ + StdDeviation5Example, + StdDeviation20Example, + StdDeviation250Example, + StdDeviation050Example, +]; +export {icon, samples}; diff --git a/apps/examples/src/examples/Filters/examples.tsx b/apps/examples/src/examples/Filters/examples.tsx index 4d239a51a..71071acbc 100644 --- a/apps/examples/src/examples/Filters/examples.tsx +++ b/apps/examples/src/examples/Filters/examples.tsx @@ -1,3 +1,4 @@ import * as FeColorMatrix from './FeColorMatrix'; +import * as FeGaussianBlur from './FeGaussianBlur'; import * as ReanimatedFeColorMatrix from './ReanimatedFeColorMatrix'; -export {FeColorMatrix, ReanimatedFeColorMatrix}; +export {FeColorMatrix, FeGaussianBlur, ReanimatedFeColorMatrix}; From 9167725b1ed9654798c7d66880eb00a63adef598 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Wed, 17 Jul 2024 16:53:20 +0200 Subject: [PATCH 07/12] feat: add FeGaussianBlur support to FilterImage component --- src/filter-image/FilterImage.tsx | 4 +++- src/filter-image/types.ts | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/filter-image/FilterImage.tsx b/src/filter-image/FilterImage.tsx index beee12418..d4b596de6 100644 --- a/src/filter-image/FilterImage.tsx +++ b/src/filter-image/FilterImage.tsx @@ -1,5 +1,5 @@ import { ImageProps, Image as RNImage, StyleSheet, View } from 'react-native'; -import { FeColorMatrix, Filter, Image, Svg } from '../index'; +import { FeColorMatrix, FeGaussianBlur, Filter, Image, Svg } from '../index'; import { Filters } from './types'; import { extractResizeMode } from './extractImage'; @@ -13,6 +13,8 @@ const getFilters = (filters: FilterImageProps['filters']) => { switch (name) { case 'colorMatrix': return ; + case 'gaussianBlur': + return ; default: return null; } diff --git a/src/filter-image/types.ts b/src/filter-image/types.ts index 1dc7803d3..2bbb790e4 100644 --- a/src/filter-image/types.ts +++ b/src/filter-image/types.ts @@ -1,7 +1,10 @@ import { FilterPrimitiveCommonProps } from '../elements/filters/FilterPrimitive'; -import { FeColorMatrixProps } from '../index'; +import { FeColorMatrixProps, FeGaussianBlurProps } from '../index'; -export type FilterElement = ({ name: 'colorMatrix' } & FeColorMatrixProps) & +export type FilterElement = ( + | ({ name: 'colorMatrix' } & FeColorMatrixProps) + | ({ name: 'gaussianBlur' } & FeGaussianBlurProps) +) & FilterPrimitiveCommonProps; export type Filters = Array; From 2860354282e24b48133a8f6e0ff7edba32545abb Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Wed, 17 Jul 2024 16:55:24 +0200 Subject: [PATCH 08/12] chore: add codegen files on paper --- .../RNSVGFeGaussianBlurManagerDelegate.java | 56 +++++++++++++++++++ .../RNSVGFeGaussianBlurManagerInterface.java | 26 +++++++++ 2 files changed, 82 insertions(+) create mode 100644 android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerDelegate.java create mode 100644 android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerInterface.java diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerDelegate.java new file mode 100644 index 000000000..f46db4fbc --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerDelegate.java @@ -0,0 +1,56 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaDelegate.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.DynamicFromObject; +import com.facebook.react.uimanager.BaseViewManagerDelegate; +import com.facebook.react.uimanager.BaseViewManagerInterface; + +public class RNSVGFeGaussianBlurManagerDelegate & RNSVGFeGaussianBlurManagerInterface> extends BaseViewManagerDelegate { + public RNSVGFeGaussianBlurManagerDelegate(U viewManager) { + super(viewManager); + } + @Override + public void setProperty(T view, String propName, @Nullable Object value) { + switch (propName) { + case "x": + mViewManager.setX(view, new DynamicFromObject(value)); + break; + case "y": + mViewManager.setY(view, new DynamicFromObject(value)); + break; + case "width": + mViewManager.setWidth(view, new DynamicFromObject(value)); + break; + case "height": + mViewManager.setHeight(view, new DynamicFromObject(value)); + break; + case "result": + mViewManager.setResult(view, value == null ? null : (String) value); + break; + case "in1": + mViewManager.setIn1(view, value == null ? null : (String) value); + break; + case "stdDeviationX": + mViewManager.setStdDeviationX(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "stdDeviationY": + mViewManager.setStdDeviationY(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "edgeMode": + mViewManager.setEdgeMode(view, (String) value); + break; + default: + super.setProperty(view, propName, value); + } + } +} diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerInterface.java new file mode 100644 index 000000000..089eb3d17 --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeGaussianBlurManagerInterface.java @@ -0,0 +1,26 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaInterface.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.Dynamic; + +public interface RNSVGFeGaussianBlurManagerInterface { + void setX(T view, Dynamic value); + void setY(T view, Dynamic value); + void setWidth(T view, Dynamic value); + void setHeight(T view, Dynamic value); + void setResult(T view, @Nullable String value); + void setIn1(T view, @Nullable String value); + void setStdDeviationX(T view, float value); + void setStdDeviationY(T view, float value); + void setEdgeMode(T view, @Nullable String value); +} From b64394d703f3f23c577a734f03581be2c9e86edc Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Wed, 17 Jul 2024 17:25:15 +0200 Subject: [PATCH 09/12] docs: add FeGaussianBlur to supported filters --- USAGE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/USAGE.md b/USAGE.md index 4735fb8c2..1d71e0cbd 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1260,6 +1260,7 @@ Filter effects are a way of processing an element’s rendering before it is dis Currently supported\* filters are: - FeColorMatrix +- FeGaussianBlur \*_More filters are coming soon_ From 28c51da80be90639dc299531dcd366aa7cffcdaf Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Mon, 22 Jul 2024 11:33:13 +0200 Subject: [PATCH 10/12] chore: update Podfile.lock --- macos-example/macos/Podfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macos-example/macos/Podfile.lock b/macos-example/macos/Podfile.lock index 9d329ebd0..76e207454 100644 --- a/macos-example/macos/Podfile.lock +++ b/macos-example/macos/Podfile.lock @@ -1053,7 +1053,7 @@ PODS: - React-jsi (= 0.73.32) - React-logger (= 0.73.32) - React-perflogger (= 0.73.32) - - RNReanimated (3.9.0): + - RNReanimated (3.9.0-nightly-20240421-474f6983d): - glog - RCT-Folly (= 2022.05.16.00) - React-Core @@ -1281,7 +1281,7 @@ SPEC CHECKSUMS: React-runtimescheduler: ec5d328861896387a906e38d3b8a5e03dc9570e6 React-utils: 52afeb88f90c5a40fa42a145df7877595302f6cf ReactCommon: 41305560b33e9d8f059560e65a283d40c6885fa0 - RNReanimated: e4276a6afc57c02056daa5ea1afb41a5ec497d9b + RNReanimated: 15c09dc3d57e26de3895d5306d283503c9eddeac RNSVG: cb24fb322de8c1ebf59904e7aca0447bb8dbed5a SocketRocket: f6c6249082c011e6de2de60ed641ef8bbe0cfac9 Yoga: b734a95cb36e2b7e9e7b5733ce679a97e4f37da4 From 410859e5b26418060716c2cd5f9e8161cfca6c13 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Tue, 23 Jul 2024 15:02:56 +0200 Subject: [PATCH 11/12] fix: rename StandardAttributes to CommonProps --- src/fabric/FeGaussianBlurNativeComponent.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/fabric/FeGaussianBlurNativeComponent.ts b/src/fabric/FeGaussianBlurNativeComponent.ts index fe25107e4..440a0bba0 100644 --- a/src/fabric/FeGaussianBlurNativeComponent.ts +++ b/src/fabric/FeGaussianBlurNativeComponent.ts @@ -7,7 +7,7 @@ import { Float, WithDefault } from 'react-native/Libraries/Types/CodegenTypes'; type FilterEdgeMode = 'duplicate' | 'wrap' | 'none'; -interface FilterPrimitiveStandardAttributes { +interface FilterPrimitiveCommonProps { x?: UnsafeMixed; y?: UnsafeMixed; width?: UnsafeMixed; @@ -15,9 +15,7 @@ interface FilterPrimitiveStandardAttributes { result?: string; } -export interface NativeProps - extends ViewProps, - FilterPrimitiveStandardAttributes { +export interface NativeProps extends ViewProps, FilterPrimitiveCommonProps { in1?: string; stdDeviationX?: Float; stdDeviationY?: Float; From 8ffa742efeb7dac6511943813a2b38e4a2fae0b7 Mon Sep 17 00:00:00 2001 From: Jakub Grzywacz Date: Thu, 25 Jul 2024 09:54:09 +0200 Subject: [PATCH 12/12] refactor: small changes after cr --- .../com/horcrux/svg/FeGaussianBlurView.java | 7 +++- src/lib/extract/extractFilter.ts | 35 +++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java b/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java index c4d552b9f..0705ed807 100644 --- a/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java +++ b/android/src/main/java/com/horcrux/svg/FeGaussianBlurView.java @@ -56,18 +56,23 @@ private Bitmap blur(Context context, Bitmap bitmap) { Bitmap outputBitmap = Bitmap.createBitmap(bitmap); + // Create a RenderScript with blur RenderScript rs = RenderScript.create(context); ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + // Allocate memory for Renderscript to work with Allocation tmpIn = Allocation.createFromBitmap(rs, bitmap); Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap); + // Set the radius of the blur, allocation input, and output blurScript.setRadius(radius); blurScript.setInput(tmpIn); blurScript.forEach(tmpOut); + // Copy the allocation output to the output bitmap and release memory tmpOut.copyTo(outputBitmap); - + tmpIn.destroy(); + tmpOut.destroy(); rs.destroy(); return Bitmap.createScaledBitmap(outputBitmap, bitmap.getWidth(), bitmap.getHeight(), false); diff --git a/src/lib/extract/extractFilter.ts b/src/lib/extract/extractFilter.ts index 9c64f5be5..7867bbdb4 100644 --- a/src/lib/extract/extractFilter.ts +++ b/src/lib/extract/extractFilter.ts @@ -61,21 +61,26 @@ export const extractFeGaussianBlur = ( ): FeGaussianBlurNativeProps => { const extracted: FeGaussianBlurNativeProps = {}; - if (props.in) extracted.in1 = props.in; - if (props.stdDeviation !== undefined) { - if ( - typeof props.stdDeviation === 'number' || - (typeof props.stdDeviation === 'string' && - !props.stdDeviation.match(spaceReg)) - ) { - extracted.stdDeviationX = Number(props.stdDeviation) || 0; - extracted.stdDeviationY = Number(props.stdDeviation) || 0; - } else { - const stdDeviation = props.stdDeviation.split(spaceReg); - extracted.stdDeviationX = Number(stdDeviation[0]) || 0; - extracted.stdDeviationY = Number(stdDeviation[1]) || 0; - } + if (props.in) { + extracted.in1 = props.in; + } + if ( + typeof props.stdDeviation === 'string' && + props.stdDeviation.match(spaceReg) + ) { + const stdDeviation = props.stdDeviation.split(spaceReg); + extracted.stdDeviationX = Number(stdDeviation[0]) || 0; + extracted.stdDeviationY = Number(stdDeviation[1]) || 0; + } else if ( + typeof props.stdDeviation === 'number' || + (typeof props.stdDeviation === 'string' && + !props.stdDeviation.match(spaceReg)) + ) { + extracted.stdDeviationX = Number(props.stdDeviation) || 0; + extracted.stdDeviationY = Number(props.stdDeviation) || 0; + } + if (props.edgeMode) { + extracted.edgeMode = props.edgeMode; } - if (props.edgeMode) extracted.edgeMode = props.edgeMode; return extracted; };