diff --git a/USAGE.md b/USAGE.md index a0d8fe0dc..504e85b3b 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1300,6 +1300,7 @@ Filter effects are a way of processing an element’s rendering before it is dis The following filters have been implemented: +- FeBlend - FeColorMatrix - FeFlood - FeGaussianBlur @@ -1308,7 +1309,6 @@ The following filters have been implemented: Not supported yet: -- FeBlend - FeComponentTransfer - FeComposite - FeConvolveMatrix diff --git a/android/src/main/java/com/horcrux/svg/CustomFilter.java b/android/src/main/java/com/horcrux/svg/CustomFilter.java new file mode 100644 index 000000000..44b9e7a91 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/CustomFilter.java @@ -0,0 +1,61 @@ +package com.horcrux.svg; + +import android.graphics.Bitmap; + +interface CustomFilterFunction { + float[] execute(float[] src, float[] dst); +} + +public class CustomFilter { + public static Bitmap apply(Bitmap srcBmp, Bitmap dstBmp, CustomFilterFunction func) { + int width = srcBmp.getWidth(); + int height = srcBmp.getHeight(); + int[] srcPixels = new int[width * height]; + int[] dstPixels = new int[width * height]; + int[] resPixels = new int[width * height]; + int srcArgb = 0; + float[] src = new float[] {0, 0, 0, 0}; + int dstArgb = 0; + float[] dst = new float[] {0, 0, 0, 0}; + + try { + srcBmp.getPixels(srcPixels, 0, width, 0, 0, width, height); + dstBmp.getPixels(dstPixels, 0, width, 0, 0, width, height); + } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException ignored) { + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + srcArgb = srcPixels[y * width + x]; + src[0] = ((srcArgb >> 24) & 0xff) / 255f; + src[1] = ((srcArgb >> 16) & 0xff) / 255f; + src[2] = ((srcArgb >> 8) & 0xff) / 255f; + src[3] = (srcArgb & 0xff) / 255f; + + dstArgb = dstPixels[y * width + x]; + dst[0] = ((dstArgb >> 24) & 0xff) / 255f; + dst[1] = ((dstArgb >> 16) & 0xff) / 255f; + dst[2] = ((dstArgb >> 8) & 0xff) / 255f; + dst[3] = (dstArgb & 0xff) / 255f; + + resPixels[y * width + x] = normalizeFromFloats(func.execute(src, dst)); + } + } + + return Bitmap.createBitmap(resPixels, width, height, Bitmap.Config.ARGB_8888); + } + + public static int normalizeFromFloat(float c) { + return Math.min(255, Math.max(0, Math.round(c * 255))); + } + + public static int normalizeFromFloats(float[] res) { + if (res.length < 4 || normalizeFromFloat(res[0]) <= 0) { + return 0; + } + return (normalizeFromFloat(res[0]) << 24) + | (normalizeFromFloat(res[1] / res[0]) << 16) + | (normalizeFromFloat(res[2] / res[0]) << 8) + | normalizeFromFloat(res[3] / res[0]); + } +} diff --git a/android/src/main/java/com/horcrux/svg/FeBlendView.java b/android/src/main/java/com/horcrux/svg/FeBlendView.java new file mode 100644 index 000000000..636d0f4e4 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FeBlendView.java @@ -0,0 +1,92 @@ +package com.horcrux.svg; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import com.facebook.react.bridge.ReactContext; +import java.util.HashMap; + +@SuppressLint("ViewConstructor") +class FeBlendView extends FilterPrimitiveView { + String mIn1; + String mIn2; + FilterProperties.FeBlendMode mMode; + + public FeBlendView(ReactContext reactContext) { + super(reactContext); + super.mFilterSubregion.mX = new SVGLength(0); + super.mFilterSubregion.mY = new SVGLength(0); + super.mFilterSubregion.mW = new SVGLength("100%"); + super.mFilterSubregion.mH = new SVGLength("100%"); + } + + public void setIn1(String in1) { + this.mIn1 = in1; + invalidate(); + } + + public void setIn2(String in2) { + this.mIn2 = in2; + invalidate(); + } + + public void setMode(String mode) { + this.mMode = FilterProperties.FeBlendMode.getEnum(mode); + invalidate(); + } + + @Override + public Bitmap applyFilter(HashMap resultsMap, Bitmap prevResult) { + Bitmap in1 = getSource(resultsMap, prevResult, this.mIn1); + Bitmap in2 = getSource(resultsMap, prevResult, this.mIn2); + + if (this.mMode == FilterProperties.FeBlendMode.MULTIPLY) { + CustomFilterFunction multiply = + (src, dst) -> { + float[] res = new float[4]; + res[0] = 1f - (1f - src[0]) * (1f - dst[0]); + res[1] = + src[1] * src[0] * (1f - dst[0]) + + dst[1] * dst[0] * (1f - src[0]) + + src[1] * src[0] * dst[1] * dst[0]; + res[2] = + src[2] * src[0] * (1f - dst[0]) + + dst[2] * dst[0] * (1f - src[0]) + + src[2] * src[0] * dst[2] * dst[0]; + res[3] = + src[3] * src[0] * (1f - dst[0]) + + dst[3] * dst[0] * (1f - src[0]) + + src[3] * src[0] * dst[3] * dst[0]; + return res; + }; + return CustomFilter.apply(in1, in2, multiply); + } + + Bitmap result = Bitmap.createBitmap(in1.getWidth(), in1.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(result); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + canvas.drawBitmap(in1, 0, 0, paint); + + switch (this.mMode) { + case NORMAL -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); + } + case SCREEN -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN)); + } + case LIGHTEN -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN)); + } + case DARKEN -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN)); + } + case MULTIPLY -> {} + } + canvas.drawBitmap(in2, 0, 0, paint); + + return result; + } +} diff --git a/android/src/main/java/com/horcrux/svg/FilterProperties.java b/android/src/main/java/com/horcrux/svg/FilterProperties.java index 73d3b647b..ff6722516 100644 --- a/android/src/main/java/com/horcrux/svg/FilterProperties.java +++ b/android/src/main/java/com/horcrux/svg/FilterProperties.java @@ -73,6 +73,43 @@ public String toString() { } } + enum FeBlendMode { + UNKNOWN("unknown"), + NORMAL("normal"), + MULTIPLY("multiply"), + SCREEN("screen"), + DARKEN("darken"), + LIGHTEN("lighten"), + ; + + private final String mode; + + FeBlendMode(String mode) { + this.mode = mode; + } + + static FeBlendMode getEnum(String strVal) { + if (!typeToEnum.containsKey(strVal)) { + throw new IllegalArgumentException("Unknown String Value: " + strVal); + } + return typeToEnum.get(strVal); + } + + private static final Map typeToEnum = new HashMap<>(); + + static { + for (final FeBlendMode en : FeBlendMode.values()) { + typeToEnum.put(en.mode, en); + } + } + + @Nonnull + @Override + public String toString() { + return mode; + } + } + enum FeColorMatrixType { MATRIX("matrix"), SATURATE("saturate"), diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index 9dd8838cc..efd148268 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -103,6 +103,8 @@ import com.facebook.react.viewmanagers.RNSVGDefsManagerInterface; import com.facebook.react.viewmanagers.RNSVGEllipseManagerDelegate; import com.facebook.react.viewmanagers.RNSVGEllipseManagerInterface; +import com.facebook.react.viewmanagers.RNSVGFeBlendManagerDelegate; +import com.facebook.react.viewmanagers.RNSVGFeBlendManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerDelegate; import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeFloodManagerDelegate; @@ -591,6 +593,7 @@ protected enum SVGClass { RNSVGPattern, RNSVGMask, RNSVGFilter, + RNSVGFeBlend, RNSVGFeColorMatrix, RNSVGFeFlood, RNSVGFeGaussianBlur, @@ -642,6 +645,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex return new MaskView(reactContext); case RNSVGFilter: return new FilterView(reactContext); + case RNSVGFeBlend: + return new FeBlendView(reactContext); case RNSVGFeColorMatrix: return new FeColorMatrixView(reactContext); case RNSVGFeFlood: @@ -1585,6 +1590,31 @@ public void setResult(T node, String result) { } } + static class FeBlendManager extends FilterPrimitiveManager + implements RNSVGFeBlendManagerInterface { + FeBlendManager() { + super(SVGClass.RNSVGFeBlend); + mDelegate = new RNSVGFeBlendManagerDelegate(this); + } + + public static final String REACT_CLASS = "RNSVGFeBlend"; + + @ReactProp(name = "in1") + public void setIn1(FeBlendView node, String in1) { + node.setIn1(in1); + } + + @ReactProp(name = "in2") + public void setIn2(FeBlendView node, String in2) { + node.setIn2(in2); + } + + @ReactProp(name = "mode") + public void setMode(FeBlendView node, String mode) { + node.setMode(mode); + } + } + static class FeColorMatrixManager extends FilterPrimitiveManager implements RNSVGFeColorMatrixManagerInterface { FeColorMatrixManager() { diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 5d8edfc40..63cb844fb 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -214,6 +214,15 @@ public NativeModule get() { return new FilterManager(); } })); + specs.put( + FeBlendManager.REACT_CLASS, + ModuleSpec.viewManagerSpec( + new Provider() { + @Override + public NativeModule get() { + return new FeBlendManager(); + } + })); specs.put( FeColorMatrixManager.REACT_CLASS, ModuleSpec.viewManagerSpec( diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeBlendManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeBlendManagerDelegate.java new file mode 100644 index 000000000..3708e5c51 --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeBlendManagerDelegate.java @@ -0,0 +1,53 @@ +/** +* 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 RNSVGFeBlendManagerDelegate & RNSVGFeBlendManagerInterface> extends BaseViewManagerDelegate { + public RNSVGFeBlendManagerDelegate(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 "in2": + mViewManager.setIn2(view, value == null ? null : (String) value); + break; + case "mode": + mViewManager.setMode(view, (String) value); + break; + default: + super.setProperty(view, propName, value); + } + } +} diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeBlendManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeBlendManagerInterface.java new file mode 100644 index 000000000..fb29eedd3 --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeBlendManagerInterface.java @@ -0,0 +1,25 @@ +/** +* 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 RNSVGFeBlendManagerInterface { + 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 setIn2(T view, @Nullable String value); + void setMode(T view, @Nullable String value); +} diff --git a/apple/Filters/RNSVGBlendMode.h b/apple/Filters/RNSVGBlendMode.h new file mode 100644 index 000000000..9937888d2 --- /dev/null +++ b/apple/Filters/RNSVGBlendMode.h @@ -0,0 +1,8 @@ +typedef CF_ENUM(int32_t, RNSVGBlendMode) { + SVG_FEBLEND_MODE_UNKNOWN, + SVG_FEBLEND_MODE_NORMAL, + SVG_FEBLEND_MODE_MULTIPLY, + SVG_FEBLEND_MODE_SCREEN, + SVG_FEBLEND_MODE_DARKEN, + SVG_FEBLEND_MODE_LIGHTEN, +}; diff --git a/apple/Filters/RNSVGFeBlend.h b/apple/Filters/RNSVGFeBlend.h new file mode 100644 index 000000000..3817ada6c --- /dev/null +++ b/apple/Filters/RNSVGFeBlend.h @@ -0,0 +1,10 @@ +#import "RNSVGBlendMode.h" +#import "RNSVGFilterPrimitive.h" + +@interface RNSVGFeBlend : RNSVGFilterPrimitive + +@property (nonatomic, strong) NSString *in1; +@property (nonatomic, strong) NSString *in2; +@property (nonatomic, assign) RNSVGBlendMode mode; + +@end diff --git a/apple/Filters/RNSVGFeBlend.mm b/apple/Filters/RNSVGFeBlend.mm new file mode 100644 index 000000000..00928a402 --- /dev/null +++ b/apple/Filters/RNSVGFeBlend.mm @@ -0,0 +1,132 @@ +#import "RNSVGFeBlend.h" + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#import +#import +#import "RNSVGConvert.h" +#import "RNSVGFabricConversions.h" +#endif // RCT_NEW_ARCH_ENABLED + +@implementation RNSVGFeBlend + +#ifdef RCT_NEW_ARCH_ENABLED +using namespace facebook::react; + +// Needed because of this: https://github.com/facebook/react-native/pull/37274 ++ (void)load +{ + [super load]; +} + +- (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.in2 = RCTNSStringFromStringNilIfEmpty(newProps.in2); + self.mode = [RNSVGConvert RNSVGBlendModeFromCppEquivalent:newProps.mode]; + + setCommonFilterProps(newProps, self); + _props = std::static_pointer_cast(props); +} + +- (void)prepareForRecycle +{ + [super prepareForRecycle]; + _in1 = nil; + _in2 = nil; + _mode = RNSVGBlendMode::SVG_FEBLEND_MODE_NORMAL; +} +#endif // RCT_NEW_ARCH_ENABLED + +- (void)setIn1:(NSString *)in1 +{ + if ([in1 isEqualToString:_in1]) { + return; + } + + _in1 = in1; + [self invalidate]; +} + +- (void)setIn2:(NSString *)in2 +{ + if ([in2 isEqualToString:_in2]) { + return; + } + + _in2 = in2; + [self invalidate]; +} + +- (void)setMode:(RNSVGBlendMode)mode +{ + if (mode == _mode) { + return; + } + _mode = mode; + [self invalidate]; +} + +- (CIImage *)applyFilter:(NSMutableDictionary *)results previousFilterResult:(CIImage *)previous +{ + CIImage *inResults1 = self.in1 ? [results objectForKey:self.in1] : nil; + CIImage *inResults2 = self.in2 ? [results objectForKey:self.in2] : nil; + CIImage *inputImage1 = inResults1 ? inResults1 : previous; + CIImage *inputImage2 = inResults2 ? inResults2 : previous; + + CIFilter *filter = nil; + + switch (self.mode) { + case SVG_FEBLEND_MODE_NORMAL: + filter = [CIFilter filterWithName:@"CISourceOverCompositing"]; + break; + case SVG_FEBLEND_MODE_MULTIPLY: + filter = [CIFilter filterWithName:@"CIMultiplyBlendMode"]; + break; + case SVG_FEBLEND_MODE_SCREEN: + filter = [CIFilter filterWithName:@"CIScreenBlendMode"]; + break; + case SVG_FEBLEND_MODE_DARKEN: + filter = [CIFilter filterWithName:@"CIDarkenBlendMode"]; + break; + case SVG_FEBLEND_MODE_LIGHTEN: + filter = [CIFilter filterWithName:@"CILightenBlendMode"]; + break; + default: + return nil; + } + + [filter setDefaults]; + [filter setValue:inputImage1 forKey:@"inputImage"]; + [filter setValue:inputImage2 forKey:@"inputBackgroundImage"]; + + return [filter valueForKey:@"outputImage"]; +} + +#ifdef RCT_NEW_ARCH_ENABLED +Class RNSVGFeBlendCls(void) +{ + return RNSVGFeBlend.class; +} +#endif // RCT_NEW_ARCH_ENABLED + +@end diff --git a/apple/Utils/RCTConvert+RNSVG.h b/apple/Utils/RCTConvert+RNSVG.h index d0714ac92..befd7285f 100644 --- a/apple/Utils/RCTConvert+RNSVG.h +++ b/apple/Utils/RCTConvert+RNSVG.h @@ -10,6 +10,7 @@ #import #import #import "RCTConvert+RNSVG.h" +#import "RNSVGBlendMode.h" #import "RNSVGCGFCRule.h" #import "RNSVGColorMatrixType.h" #import "RNSVGEdgeMode.h" diff --git a/apple/Utils/RCTConvert+RNSVG.mm b/apple/Utils/RCTConvert+RNSVG.mm index 863cb7255..bbf2e83b0 100644 --- a/apple/Utils/RCTConvert+RNSVG.mm +++ b/apple/Utils/RCTConvert+RNSVG.mm @@ -72,6 +72,19 @@ @implementation RCTConvert (RNSVG) SVG_FECOLORMATRIX_TYPE_UNKNOWN, intValue) +RCT_ENUM_CONVERTER( + RNSVGBlendMode, + (@{ + @"unknown" : @(SVG_FEBLEND_MODE_UNKNOWN), + @"normal" : @(SVG_FEBLEND_MODE_NORMAL), + @"multiply" : @(SVG_FEBLEND_MODE_MULTIPLY), + @"screen" : @(SVG_FEBLEND_MODE_SCREEN), + @"darken" : @(SVG_FEBLEND_MODE_DARKEN), + @"lighten" : @(SVG_FEBLEND_MODE_LIGHTEN), + }), + SVG_FEBLEND_MODE_UNKNOWN, + intValue) + + (RNSVGBrush *)RNSVGBrush:(id)json { if ([json isKindOfClass:[NSNumber class]]) { diff --git a/apple/Utils/RNSVGConvert.h b/apple/Utils/RNSVGConvert.h index ee83027b4..339441a4c 100644 --- a/apple/Utils/RNSVGConvert.h +++ b/apple/Utils/RNSVGConvert.h @@ -1,5 +1,6 @@ #ifdef RCT_NEW_ARCH_ENABLED #import +#import "RNSVGBlendMode.h" #import "RNSVGColorMatrixType.h" #import "RNSVGEdgeMode.h" #import "RNSVGUnits.h" @@ -10,6 +11,7 @@ namespace react = facebook::react; + (RNSVGUnits)RNSVGUnitsFromFilterUnitsCppEquivalent:(react::RNSVGFilterFilterUnits)svgUnits; + (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimitiveUnits)svgUnits; ++ (RNSVGBlendMode)RNSVGBlendModeFromCppEquivalent:(react::RNSVGFeBlendMode)mode; + (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type; + (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeMode)edgeMode; diff --git a/apple/Utils/RNSVGConvert.mm b/apple/Utils/RNSVGConvert.mm index b557ff543..459782177 100644 --- a/apple/Utils/RNSVGConvert.mm +++ b/apple/Utils/RNSVGConvert.mm @@ -23,6 +23,24 @@ + (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimi } } ++ (RNSVGBlendMode)RNSVGBlendModeFromCppEquivalent:(react::RNSVGFeBlendMode)mode +{ + switch (mode) { + case react::RNSVGFeBlendMode::Unknown: + return SVG_FEBLEND_MODE_UNKNOWN; + case react::RNSVGFeBlendMode::Normal: + return SVG_FEBLEND_MODE_NORMAL; + case react::RNSVGFeBlendMode::Multiply: + return SVG_FEBLEND_MODE_MULTIPLY; + case react::RNSVGFeBlendMode::Screen: + return SVG_FEBLEND_MODE_SCREEN; + case react::RNSVGFeBlendMode::Darken: + return SVG_FEBLEND_MODE_DARKEN; + case react::RNSVGFeBlendMode::Lighten: + return SVG_FEBLEND_MODE_LIGHTEN; + } +} + + (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type { switch (type) { diff --git a/apple/ViewManagers/RNSVGFeBlendManager.h b/apple/ViewManagers/RNSVGFeBlendManager.h new file mode 100644 index 000000000..d8cbf7314 --- /dev/null +++ b/apple/ViewManagers/RNSVGFeBlendManager.h @@ -0,0 +1,5 @@ +#import "RNSVGFilterPrimitiveManager.h" + +@interface RNSVGFeBlendManager : RNSVGFilterPrimitiveManager + +@end diff --git a/apple/ViewManagers/RNSVGFeBlendManager.mm b/apple/ViewManagers/RNSVGFeBlendManager.mm new file mode 100644 index 000000000..4ce585b01 --- /dev/null +++ b/apple/ViewManagers/RNSVGFeBlendManager.mm @@ -0,0 +1,18 @@ +#import "RNSVGFeBlendManager.h" +#import "RNSVGBlendMode.h" +#import "RNSVGFeBlend.h" + +@implementation RNSVGFeBlendManager + +RCT_EXPORT_MODULE() + +- (RNSVGFeBlend *)node +{ + return [RNSVGFeBlend new]; +} + +RCT_EXPORT_VIEW_PROPERTY(in1, NSString) +RCT_EXPORT_VIEW_PROPERTY(in2, NSString) +RCT_EXPORT_VIEW_PROPERTY(mode, RNSVGBlendMode) + +@end diff --git a/apps/examples/src/examples/Filters/FeBlend.tsx b/apps/examples/src/examples/Filters/FeBlend.tsx new file mode 100644 index 000000000..2acb6dfce --- /dev/null +++ b/apps/examples/src/examples/Filters/FeBlend.tsx @@ -0,0 +1,155 @@ +import React, {Component} from 'react'; +import { + Circle, + FeBlend, + FeFlood, + Filter, + G, + Image, + LinearGradient, + Rect, + Stop, + Svg, + Text, +} from 'react-native-svg'; + +class W3Blend extends Component { + static title = 'W3 FeBlend example'; + render() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Normal + + + Multiply + + + Screen + + + Darken + + + Lighten + + + + + ); + } +} + +class SimpleExample extends Component { + static title = 'MDN example'; + render() { + return ( + + + + + + + + + ); + } +} + +class MDNExample extends Component { + static title = 'MDN example'; + render() { + return ( + + + + + + + + ); + } +} + +const icon = ( + + + + + + + + +); + +const samples = [W3Blend, SimpleExample, MDNExample]; +export {icon, samples}; diff --git a/apps/examples/src/examples/Filters/examples.tsx b/apps/examples/src/examples/Filters/examples.tsx index 9aed81701..26d196881 100644 --- a/apps/examples/src/examples/Filters/examples.tsx +++ b/apps/examples/src/examples/Filters/examples.tsx @@ -1,3 +1,4 @@ +import * as FeBlend from './FeBlend'; import * as FeColorMatrix from './FeColorMatrix'; import * as FeFlood from './FeFlood'; import * as FeGaussianBlur from './FeGaussianBlur'; @@ -5,6 +6,7 @@ import * as FeMerge from './FeMerge'; import * as FeOffset from './FeOffset'; import * as ReanimatedFeColorMatrix from './ReanimatedFeColorMatrix'; export { + FeBlend, FeColorMatrix, FeFlood, FeGaussianBlur, diff --git a/react-native.config.js b/react-native.config.js index bf02f021b..66aeb24b2 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -17,6 +17,7 @@ module.exports = { 'RNSVGCircleComponentDescriptor', 'RNSVGClipPathComponentDescriptor', 'RNSVGDefsComponentDescriptor', + 'RNSVGFeBlendComponentDescriptor', 'RNSVGFeColorMatrixComponentDescriptor', 'RNSVGFeFloodComponentDescriptor', 'RNSVGFeGaussianBlurComponentDescriptor', diff --git a/src/elements/filters/FeBlend.tsx b/src/elements/filters/FeBlend.tsx index d65e0bf53..71f8b1bd8 100644 --- a/src/elements/filters/FeBlend.tsx +++ b/src/elements/filters/FeBlend.tsx @@ -1,4 +1,10 @@ -import { warnUnimplementedFilter } from '../../lib/util'; +import { NativeMethods } from 'react-native'; +import { + extractFeBlend, + extractFilter, + extractIn, +} from '../../lib/extract/extractFilter'; +import RNSVGFeBlend from '../../fabric/FeBlendNativeComponent'; import FilterPrimitive from './FilterPrimitive'; type BlendMode = 'normal' | 'multiply' | 'screen' | 'darken' | 'lighten'; @@ -17,7 +23,13 @@ export default class FeBlend extends FilterPrimitive { }; render() { - warnUnimplementedFilter(); - return null; + return ( + this.refMethod(ref as (FeBlend & NativeMethods) | null)} + {...extractFilter(this.props)} + {...extractIn(this.props)} + {...extractFeBlend(this.props)} + /> + ); } } diff --git a/src/fabric/FeBlendNativeComponent.ts b/src/fabric/FeBlendNativeComponent.ts new file mode 100644 index 000000000..67c0d722a --- /dev/null +++ b/src/fabric/FeBlendNativeComponent.ts @@ -0,0 +1,29 @@ +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import { NumberProp } from '../lib/extract/types'; +import type { UnsafeMixed } from './codegenUtils'; +import type { ViewProps } from './utils'; +import { WithDefault } from 'react-native/Libraries/Types/CodegenTypes'; + +interface FilterPrimitiveCommonProps { + x?: UnsafeMixed; + y?: UnsafeMixed; + width?: UnsafeMixed; + height?: UnsafeMixed; + result?: string; +} + +type BlendMode = + | 'unknown' + | 'normal' + | 'multiply' + | 'screen' + | 'darken' + | 'lighten'; + +export interface NativeProps extends ViewProps, FilterPrimitiveCommonProps { + in1?: string; + in2?: string; + mode?: WithDefault; +} + +export default codegenNativeComponent('RNSVGFeBlend'); diff --git a/src/fabric/index.ts b/src/fabric/index.ts index a89fbdee6..0d3db8868 100644 --- a/src/fabric/index.ts +++ b/src/fabric/index.ts @@ -21,6 +21,7 @@ import RNSVGTextPath from './TextPathNativeComponent'; import RNSVGTSpan from './TSpanNativeComponent'; import RNSVGUse from './UseNativeComponent'; import RNSVGFilter from './FilterNativeComponent'; +import RNSVGFeBlend from './FeBlendNativeComponent'; import RNSVGFeColorMatrix from './FeColorMatrixNativeComponent'; import RNSVGFeFlood from './FeFloodNativeComponent'; import RNSVGFeGaussianBlur from './FeGaussianBlurNativeComponent'; @@ -51,6 +52,7 @@ export { RNSVGTSpan, RNSVGUse, RNSVGFilter, + RNSVGFeBlend, RNSVGFeColorMatrix, RNSVGFeFlood, RNSVGFeGaussianBlur, diff --git a/src/lib/extract/extractFilter.ts b/src/lib/extract/extractFilter.ts index e198df22f..7f60ccd6b 100644 --- a/src/lib/extract/extractFilter.ts +++ b/src/lib/extract/extractFilter.ts @@ -1,9 +1,11 @@ import React from 'react'; import { ColorValue, processColor } from 'react-native'; +import { FeBlendProps as FeBlendComponentProps } from '../../elements/filters/FeBlend'; import { FeColorMatrixProps as FeColorMatrixComponentProps } from '../../elements/filters/FeColorMatrix'; import { FeFloodProps as FeFloodComponentProps } from '../../elements/filters/FeFlood'; import { FeGaussianBlurProps as FeGaussianBlurComponentProps } from '../../elements/filters/FeGaussianBlur'; import { FeMergeProps as FeMergeComponentProps } from '../../elements/filters/FeMerge'; +import { NativeProps as FeBlendNativeProps } from '../../fabric/FeBlendNativeComponent'; import { NativeProps as FeColorMatrixNativeProps } from '../../fabric/FeColorMatrixNativeComponent'; import { NativeProps as FeFloodNativeProps } from '../../fabric/FeFloodNativeComponent'; import { NativeProps as FeGaussianBlurNativeProps } from '../../fabric/FeGaussianBlurNativeComponent'; @@ -44,6 +46,21 @@ export const extractIn = (props: { in?: string }) => { return {}; }; +export const extractFeBlend = ( + props: FeBlendComponentProps +): FeBlendNativeProps => { + const extracted: FeBlendNativeProps = {}; + + if (props.in2) { + extracted.in2 = props.in2; + } + if (props.mode) { + extracted.mode = props.mode; + } + + return extracted; +}; + export const extractFeColorMatrix = ( props: FeColorMatrixComponentProps ): FeColorMatrixNativeProps => {