diff --git a/RNSVG.podspec b/RNSVG.podspec index 878dfb42..b3e0ca3a 100644 --- a/RNSVG.podspec +++ b/RNSVG.podspec @@ -12,13 +12,18 @@ Pod::Spec.new do |s| s.homepage = package['homepage'] s.authors = 'Horcrux Chen' s.source = { :git => 'https://github.com/react-native-community/react-native-svg.git', :tag => "v#{s.version}" } - s.source_files = 'apple/**/*.{h,m,mm}' + s.source_files = 'apple/**/*.{h,m,mm,metal}' s.ios.exclude_files = '**/*.macos.{h,m,mm}' s.tvos.exclude_files = '**/*.macos.{h,m,mm}' s.visionos.exclude_files = '**/*.macos.{h,m,mm}' if s.respond_to?(:visionos) s.osx.exclude_files = '**/*.ios.{h,m,mm}' - s.requires_arc = true + s.requires_arc = true s.platforms = { :osx => "10.14", :ios => "12.4", :tvos => "12.4", :visionos => "1.0" } + + s.osx.resource_bundles = {'RNSVGFilters' => ['apple/**/*.macosx.metallib']} + s.ios.resource_bundles = {'RNSVGFilters' => ['apple/**/*.iphoneos.metallib']} + s.tvos.resource_bundles = {'RNSVGFilters' => ['apple/**/*.appletvos.metallib']} + s.visionos.resource_bundles = {'RNSVGFilters' => ['apple/**/*.xros.metallib']} if fabric_enabled install_modules_dependencies(s) diff --git a/android/src/main/java/com/horcrux/svg/FeCompositeView.java b/android/src/main/java/com/horcrux/svg/FeCompositeView.java new file mode 100644 index 00000000..eee8514d --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FeCompositeView.java @@ -0,0 +1,132 @@ +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 FeCompositeView extends FilterPrimitiveView { + String mIn1; + String mIn2; + float mK1; + float mK2; + float mK3; + float mK4; + FilterProperties.FeCompositeOperator mOperator; + + public FeCompositeView(ReactContext reactContext) { + super(reactContext); + } + + public void setIn1(String in1) { + this.mIn1 = in1; + invalidate(); + } + + public void setIn2(String in2) { + this.mIn2 = in2; + invalidate(); + } + + public void setK1(Float value) { + this.mK1 = value; + invalidate(); + } + + public void setK2(Float value) { + this.mK2 = value; + invalidate(); + } + + public void setK3(Float value) { + this.mK3 = value; + invalidate(); + } + + public void setK4(Float value) { + this.mK4 = value; + invalidate(); + } + + public void setOperator(String operator) { + this.mOperator = FilterProperties.FeCompositeOperator.getEnum(operator); + invalidate(); + } + + @Override + public Bitmap applyFilter(HashMap resultsMap, Bitmap prevResult) { + Bitmap in1 = getSource(resultsMap, prevResult, this.mIn1); + Bitmap in2 = getSource(resultsMap, prevResult, this.mIn2); + 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.mOperator) { + case OVER -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); + } + case IN -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); + } + case OUT -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); + } + case ATOP -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); + } + case XOR -> { + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); + } + case ARITHMETIC -> { + // result = k1*i1*i2 + k2*i1 + k3*i2 + k4 + int nPixels = result.getWidth() * result.getHeight(); + int[] pixels1 = new int[nPixels]; + int[] pixels2 = new int[nPixels]; + result.getPixels( + pixels1, 0, result.getWidth(), 0, 0, result.getWidth(), result.getHeight()); + + for (int i = 0; i < nPixels; i++) { + int color1 = pixels1[i]; + int color2 = pixels2[i]; + + int r1 = (color1 >> 16) & 0xFF; + int g1 = (color1 >> 8) & 0xFF; + int b1 = color1 & 0xFF; + int a1 = (color1 >>> 24); + int r2 = (color2 >> 16) & 0xFF; + int g2 = (color2 >> 8) & 0xFF; + int b2 = color2 & 0xFF; + int a2 = (color2 >>> 24); + + int rResult = (int) (mK1 * r1 * r2 + mK2 * r1 + mK3 * r2 + mK4); + int gResult = (int) (mK1 * g1 * g2 + mK2 * g1 + mK3 * g2 + mK4); + int bResult = (int) (mK1 * b1 * b2 + mK2 * b1 + mK3 * b2 + mK4); + int aResult = (int) (mK1 * a1 * a2 + mK2 * a1 + mK3 * a2 + mK4); + + rResult = Math.min(255, Math.max(0, rResult)); + gResult = Math.min(255, Math.max(0, gResult)); + bResult = Math.min(255, Math.max(0, bResult)); + aResult = Math.min(255, Math.max(0, aResult)); + + int pixel = (aResult << 24) | (rResult << 16) | (gResult << 8) | bResult; + pixels1[i] = pixel; + } + + result.setPixels( + pixels1, 0, result.getWidth(), 0, 0, result.getWidth(), result.getHeight()); + } + } + + if (this.mOperator != FilterProperties.FeCompositeOperator.ARITHMETIC) { + 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 ff672251..fbd17c05 100644 --- a/android/src/main/java/com/horcrux/svg/FilterProperties.java +++ b/android/src/main/java/com/horcrux/svg/FilterProperties.java @@ -144,4 +144,41 @@ class FilterProperties { return type; } } + + enum FeCompositeOperator { + OVER("over"), + IN("in"), + OUT("out"), + ATOP("atop"), + XOR("xor"), + ARITHMETIC("arithmetic"), + ; + + private final String type; + + FeCompositeOperator(String type) { + this.type = type; + } + + static FeCompositeOperator 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 FeCompositeOperator en : FeCompositeOperator.values()) { + typeToEnum.put(en.type, en); + } + } + + @Nonnull + @Override + public String toString() { + return type; + } + } } diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index efd14826..740f3095 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -107,6 +107,8 @@ 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.RNSVGFeCompositeManagerDelegate; +import com.facebook.react.viewmanagers.RNSVGFeCompositeManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeFloodManagerDelegate; import com.facebook.react.viewmanagers.RNSVGFeFloodManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerDelegate; @@ -595,6 +597,7 @@ class VirtualViewManager extends ViewGroupManager extends ViewGroupManager extends VirtualViewManager } } + static class FeCompositeManager extends FilterPrimitiveManager + implements RNSVGFeCompositeManagerInterface { + FeCompositeManager() { + super(SVGClass.RNSVGFeComposite); + mDelegate = new RNSVGFeCompositeManagerDelegate(this); + } + + public static final String REACT_CLASS = "RNSVGFeComposite"; + + @ReactProp(name = "in1") + public void setIn1(FeCompositeView node, String in1) { + node.setIn1(in1); + } + + @ReactProp(name = "in2") + public void setIn2(FeCompositeView node, String in2) { + node.setIn2(in2); + } + + @ReactProp(name = "operator1") + public void setOperator1(FeCompositeView node, String operator) { + node.setOperator(operator); + } + + @ReactProp(name = "k1") + public void setK1(FeCompositeView node, float value) { + node.setK1(value); + } + + @ReactProp(name = "k2") + public void setK2(FeCompositeView node, float value) { + node.setK2(value); + } + + @ReactProp(name = "k3") + public void setK3(FeCompositeView node, float value) { + node.setK3(value); + } + + @ReactProp(name = "k4") + public void setK4(FeCompositeView node, float value) { + node.setK4(value); + } + } + static class FeFloodManager extends FilterPrimitiveManager implements RNSVGFeFloodManagerInterface { FeFloodManager() { diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index 63cb844f..bc116e13 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -232,6 +232,15 @@ public class SvgPackage extends TurboReactPackage implements ViewManagerOnDemand return new FeColorMatrixManager(); } })); + specs.put( + FeCompositeManager.REACT_CLASS, + ModuleSpec.viewManagerSpec( + new Provider() { + @Override + public NativeModule get() { + return new FeCompositeManager(); + } + })); specs.put( FeFloodManager.REACT_CLASS, ModuleSpec.viewManagerSpec( diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeCompositeManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeCompositeManagerDelegate.java new file mode 100644 index 00000000..d1eb60be --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeCompositeManagerDelegate.java @@ -0,0 +1,65 @@ +/** +* 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 RNSVGFeCompositeManagerDelegate & RNSVGFeCompositeManagerInterface> extends BaseViewManagerDelegate { + public RNSVGFeCompositeManagerDelegate(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 "operator1": + mViewManager.setOperator1(view, (String) value); + break; + case "k1": + mViewManager.setK1(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "k2": + mViewManager.setK2(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "k3": + mViewManager.setK3(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "k4": + mViewManager.setK4(view, value == null ? 0f : ((Double) value).floatValue()); + break; + default: + super.setProperty(view, propName, value); + } + } +} diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeCompositeManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeCompositeManagerInterface.java new file mode 100644 index 00000000..95c92597 --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeCompositeManagerInterface.java @@ -0,0 +1,29 @@ +/** +* 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 RNSVGFeCompositeManagerInterface { + 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 setOperator1(T view, @Nullable String value); + void setK1(T view, float value); + void setK2(T view, float value); + void setK3(T view, float value); + void setK4(T view, float value); +} diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.appletvos.air b/apple/Filters/MetalCI/RNSVGArithmeticFilter.appletvos.air new file mode 100644 index 00000000..0f89836e Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.appletvos.air differ diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.appletvos.metallib b/apple/Filters/MetalCI/RNSVGArithmeticFilter.appletvos.metallib new file mode 100644 index 00000000..de067b1c Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.appletvos.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.h b/apple/Filters/MetalCI/RNSVGArithmeticFilter.h new file mode 100644 index 00000000..7c78724c --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGArithmeticFilter.h @@ -0,0 +1,11 @@ +#import "RNSVGCustomFilter.h" + +@interface RNSVGArithmeticFilter : RNSVGCustomFilter { + CIImage *inputImage2; + NSNumber *inputK1; + NSNumber *inputK2; + NSNumber *inputK3; + NSNumber *inputK4; +} + +@end diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.iphoneos.air b/apple/Filters/MetalCI/RNSVGArithmeticFilter.iphoneos.air new file mode 100644 index 00000000..abe4386d Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.iphoneos.air differ diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.iphoneos.metallib b/apple/Filters/MetalCI/RNSVGArithmeticFilter.iphoneos.metallib new file mode 100644 index 00000000..47668d1a Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.iphoneos.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.macosx.air b/apple/Filters/MetalCI/RNSVGArithmeticFilter.macosx.air new file mode 100644 index 00000000..15b5c064 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.macosx.air differ diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.macosx.metallib b/apple/Filters/MetalCI/RNSVGArithmeticFilter.macosx.metallib new file mode 100644 index 00000000..ef6bbee1 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.macosx.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.metal b/apple/Filters/MetalCI/RNSVGArithmeticFilter.metal new file mode 100644 index 00000000..0eecde74 --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGArithmeticFilter.metal @@ -0,0 +1,16 @@ +#include +using namespace metal; +#include + + +extern "C" float4 RNSVGArithmeticFilter(coreimage::sample_t in1, coreimage::sample_t in2, float k1, float k2, float k3, float k4) +{ + float4 arithmeticResult; + arithmeticResult.rgb = k1 * in1.rgb * in2.rgb + k2 * in1.rgb + k3 * in2.rgb + k4; + arithmeticResult.a = k1 * in1.a * in2.a + k2 * in1.a + k3 * in2.a + k4; + + arithmeticResult.rgb = clamp(arithmeticResult.rgb, 0.0, 1.0); + arithmeticResult.a = clamp(arithmeticResult.a, 0.0, 1.0); + + return arithmeticResult; +} diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.mm b/apple/Filters/MetalCI/RNSVGArithmeticFilter.mm new file mode 100644 index 00000000..bbc4c15d --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGArithmeticFilter.mm @@ -0,0 +1,66 @@ +#import "RNSVGArithmeticFilter.h" + +static CIColorKernel *arithmeticFilter = nil; + +@implementation RNSVGArithmeticFilter + +- (id)init +{ + if (arithmeticFilter == nil) { + arithmeticFilter = [super getWithName:@"RNSVGArithmeticFilter"]; + } + return [super init]; +} + +- (NSDictionary *)customAttributes +{ + return @{ + @"inputImage1" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"CIImage", + kCIAttributeDisplayName : @"in1", + kCIAttributeType : kCIAttributeTypeImage + }, + @"inputImage2" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"CIImage", + kCIAttributeDisplayName : @"in2", + kCIAttributeType : kCIAttributeTypeImage + }, + @"inputK1" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"NSNumber", + kCIAttributeDisplayName : @"k1", + kCIAttributeType : kCIAttributeTypeScalar + }, + @"inputK2" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"NSNumber", + kCIAttributeDisplayName : @"k2", + kCIAttributeType : kCIAttributeTypeScalar + }, + @"inputK3" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"NSNumber", + kCIAttributeDisplayName : @"k3", + kCIAttributeType : kCIAttributeTypeScalar + }, + @"inputK4" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"NSNumber", + kCIAttributeDisplayName : @"k4", + kCIAttributeType : kCIAttributeTypeScalar + }, + }; +} + +- (CIImage *)outputImage +{ + CISampler *in1 = [CISampler samplerWithImage:inputImage1]; + CISampler *in2 = [CISampler samplerWithImage:inputImage2]; + + return [arithmeticFilter applyWithExtent:inputImage1.extent + arguments:@[ in1, in2, inputK1, inputK2, inputK3, inputK4 ]]; +} + +@end diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.xros.air b/apple/Filters/MetalCI/RNSVGArithmeticFilter.xros.air new file mode 100644 index 00000000..48c39168 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.xros.air differ diff --git a/apple/Filters/MetalCI/RNSVGArithmeticFilter.xros.metallib b/apple/Filters/MetalCI/RNSVGArithmeticFilter.xros.metallib new file mode 100644 index 00000000..461f55d2 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGArithmeticFilter.xros.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.appletvos.air b/apple/Filters/MetalCI/RNSVGCompositeXor.appletvos.air new file mode 100644 index 00000000..c43b92f8 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.appletvos.air differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.appletvos.metallib b/apple/Filters/MetalCI/RNSVGCompositeXor.appletvos.metallib new file mode 100644 index 00000000..1ef43f41 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.appletvos.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.h b/apple/Filters/MetalCI/RNSVGCompositeXor.h new file mode 100644 index 00000000..33a2fc09 --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGCompositeXor.h @@ -0,0 +1,7 @@ +#import "RNSVGCustomFilter.h" + +@interface RNSVGCompositeXor : RNSVGCustomFilter { + CIImage *inputImage2; +} + +@end diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.iphoneos.air b/apple/Filters/MetalCI/RNSVGCompositeXor.iphoneos.air new file mode 100644 index 00000000..744947f4 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.iphoneos.air differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.iphoneos.metallib b/apple/Filters/MetalCI/RNSVGCompositeXor.iphoneos.metallib new file mode 100644 index 00000000..0d0f4064 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.iphoneos.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.macosx.air b/apple/Filters/MetalCI/RNSVGCompositeXor.macosx.air new file mode 100644 index 00000000..f44e52b1 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.macosx.air differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.macosx.metallib b/apple/Filters/MetalCI/RNSVGCompositeXor.macosx.metallib new file mode 100644 index 00000000..8819212d Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.macosx.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.metal b/apple/Filters/MetalCI/RNSVGCompositeXor.metal new file mode 100644 index 00000000..6823054f --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGCompositeXor.metal @@ -0,0 +1,13 @@ +#include +using namespace metal; +#include + +extern "C" float4 RNSVGCompositeXor(coreimage::sample_t in1, coreimage::sample_t in2) +{ + float4 result; + + result.rgb = in1.rgb * (1.0 - in2.a) + in2.rgb * (1.0 - in1.a); + result.a = in1.a + in2.a - 2.0 * in1.a * in2.a; + + return result; +} diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.mm b/apple/Filters/MetalCI/RNSVGCompositeXor.mm new file mode 100644 index 00000000..486d92af --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGCompositeXor.mm @@ -0,0 +1,41 @@ +#import "RNSVGCompositeXor.h" + +static CIColorKernel *compositeXor = nil; + +@implementation RNSVGCompositeXor + +- (id)init +{ + if (compositeXor == nil) { + compositeXor = [super getWithName:@"RNSVGCompositeXor"]; + } + return [super init]; +} + +- (NSDictionary *)customAttributes +{ + return @{ + @"inputImage1" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"CIImage", + kCIAttributeDisplayName : @"in1", + kCIAttributeType : kCIAttributeTypeImage + }, + @"inputImage2" : @{ + kCIAttributeIdentity : @0, + kCIAttributeClass : @"CIImage", + kCIAttributeDisplayName : @"in2", + kCIAttributeType : kCIAttributeTypeImage + }, + }; +} + +- (CIImage *)outputImage +{ + CISampler *in1 = [CISampler samplerWithImage:inputImage1]; + CISampler *in2 = [CISampler samplerWithImage:inputImage2]; + + return [compositeXor applyWithExtent:inputImage1.extent arguments:@[ in1, in2 ]]; +} + +@end diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.xros.air b/apple/Filters/MetalCI/RNSVGCompositeXor.xros.air new file mode 100644 index 00000000..cce61976 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.xros.air differ diff --git a/apple/Filters/MetalCI/RNSVGCompositeXor.xros.metallib b/apple/Filters/MetalCI/RNSVGCompositeXor.xros.metallib new file mode 100644 index 00000000..7ea8a370 Binary files /dev/null and b/apple/Filters/MetalCI/RNSVGCompositeXor.xros.metallib differ diff --git a/apple/Filters/MetalCI/RNSVGCustomFilter.h b/apple/Filters/MetalCI/RNSVGCustomFilter.h new file mode 100644 index 00000000..baf27ad2 --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGCustomFilter.h @@ -0,0 +1,9 @@ +#import + +@interface RNSVGCustomFilter : CIFilter { + CIImage *inputImage1; +} + +- (CIColorKernel *)getWithName:(NSString *)name; + +@end diff --git a/apple/Filters/MetalCI/RNSVGCustomFilter.mm b/apple/Filters/MetalCI/RNSVGCustomFilter.mm new file mode 100644 index 00000000..2f43bcaf --- /dev/null +++ b/apple/Filters/MetalCI/RNSVGCustomFilter.mm @@ -0,0 +1,35 @@ +#import "RNSVGCustomFilter.h" + +#if TARGET_OS_OSX +#define extension @"macosx.metallib" +#elif TARGET_OS_IOS +#define extension @"iphoneos.metallib" +#elif TARGET_OS_TV +#define extension @"tvos.metallib" +#elif TARGET_OS_VISION +#define extension @"xros.metallib" +#endif + +@implementation RNSVGCustomFilter + +- (CIColorKernel *)getWithName:(NSString *)name +{ + NSBundle *frameworkBundle = [NSBundle bundleForClass:[self class]]; + NSURL *bundleUrl = [frameworkBundle.resourceURL URLByAppendingPathComponent:@"RNSVGFilters.bundle"]; + NSBundle *bundle = [NSBundle bundleWithURL:bundleUrl]; + NSURL *url = [bundle URLForResource:name withExtension:extension]; + + if (url != nil) { + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfURL:url options:0 error:&error]; + + @try { + return [CIColorKernel kernelWithFunctionName:name fromMetalLibraryData:data error:&error]; + } @catch (NSException *exception) { + NSLog(@"RNSVG CustomFilter exception: %@", exception); + } + } + return nil; +} + +@end diff --git a/apple/Filters/RNSVGCompositeOperator.h b/apple/Filters/RNSVGCompositeOperator.h new file mode 100644 index 00000000..62f2dc40 --- /dev/null +++ b/apple/Filters/RNSVGCompositeOperator.h @@ -0,0 +1,9 @@ +typedef CF_ENUM(int32_t, RNSVGCompositeOperator) { + SVG_FECOMPOSITE_OPERATOR_UNKNOWN, + SVG_FECOMPOSITE_OPERATOR_OVER, + SVG_FECOMPOSITE_OPERATOR_IN, + SVG_FECOMPOSITE_OPERATOR_OUT, + SVG_FECOMPOSITE_OPERATOR_ATOP, + SVG_FECOMPOSITE_OPERATOR_XOR, + SVG_FECOMPOSITE_OPERATOR_ARITHMETIC +}; diff --git a/apple/Filters/RNSVGFeComposite.h b/apple/Filters/RNSVGFeComposite.h new file mode 100644 index 00000000..6b22a51c --- /dev/null +++ b/apple/Filters/RNSVGFeComposite.h @@ -0,0 +1,16 @@ +#import "RNSVGArithmeticFilter.h" +#import "RNSVGCompositeOperator.h" +#import "RNSVGCompositeXor.h" +#import "RNSVGFilterPrimitive.h" + +@interface RNSVGFeComposite : RNSVGFilterPrimitive + +@property (nonatomic, strong) NSString *in1; +@property (nonatomic, strong) NSString *in2; +@property (nonatomic, assign) RNSVGCompositeOperator operator1; +@property (nonatomic, strong) NSNumber *k1; +@property (nonatomic, strong) NSNumber *k2; +@property (nonatomic, strong) NSNumber *k3; +@property (nonatomic, strong) NSNumber *k4; + +@end diff --git a/apple/Filters/RNSVGFeComposite.mm b/apple/Filters/RNSVGFeComposite.mm new file mode 100644 index 00000000..4aa9dc1f --- /dev/null +++ b/apple/Filters/RNSVGFeComposite.mm @@ -0,0 +1,201 @@ +#import "RNSVGFeComposite.h" +#import "RNSVGArithmeticFilter.h" +#import "RNSVGCompositeXor.h" + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#import +#import +#import "RNSVGConvert.h" +#import "RNSVGFabricConversions.h" +#endif // RCT_NEW_ARCH_ENABLED + +static CIColorKernel *thresholdKernel; + +@implementation RNSVGFeComposite + +#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.k1 = [NSNumber numberWithFloat:newProps.k1]; + self.k2 = [NSNumber numberWithFloat:newProps.k2]; + self.k3 = [NSNumber numberWithFloat:newProps.k3]; + self.k4 = [NSNumber numberWithFloat:newProps.k4]; + self.operator1 = [RNSVGConvert RNSVGRNSVGCompositeOperatorFromCppEquivalent:newProps.operator1]; + + setCommonFilterProps(newProps, self); + _props = std::static_pointer_cast(props); +} + +- (void)prepareForRecycle +{ + [super prepareForRecycle]; + _in1 = nil; + _in2 = nil; + _k1 = nil; + _k2 = nil; + _k3 = nil; + _k4 = nil; + _operator1 = RNSVGCompositeOperator::SVG_FECOMPOSITE_OPERATOR_UNKNOWN; +} +#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)setK1:(NSNumber *)k1 +{ + if (k1 == _k1) { + return; + } + + _k1 = k1; + [self invalidate]; +} + +- (void)setK2:(NSNumber *)k2 +{ + if (k2 == _k2) { + return; + } + + _k2 = k2; + [self invalidate]; +} + +- (void)setK3:(NSNumber *)k3 +{ + if (k3 == _k3) { + return; + } + + _k3 = k3; + [self invalidate]; +} + +- (void)setK4:(NSNumber *)k4 +{ + if (k4 == _k4) { + return; + } + + _k4 = k4; + [self invalidate]; +} + +- (void)setOperator1:(RNSVGCompositeOperator)operator1 +{ + if (operator1 == _operator1) { + return; + } + + _operator1 = operator1; + [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.operator1) { + case SVG_FECOMPOSITE_OPERATOR_OVER: + filter = [CIFilter filterWithName:@"CISourceOverCompositing"]; + break; + case SVG_FECOMPOSITE_OPERATOR_IN: + filter = [CIFilter filterWithName:@"CISourceInCompositing"]; + break; + case SVG_FECOMPOSITE_OPERATOR_OUT: + filter = [CIFilter filterWithName:@"CISourceOutCompositing"]; + break; + case SVG_FECOMPOSITE_OPERATOR_ATOP: + filter = [CIFilter filterWithName:@"CISourceAtopCompositing"]; + break; + case SVG_FECOMPOSITE_OPERATOR_XOR: + filter = [[RNSVGCompositeXor alloc] init]; + break; + case SVG_FECOMPOSITE_OPERATOR_ARITHMETIC: + filter = [[RNSVGArithmeticFilter alloc] init]; + break; + default: + return nil; + } + + [filter setDefaults]; + + if (self.operator1 == SVG_FECOMPOSITE_OPERATOR_XOR) { + [filter setValue:inputImage1 forKey:@"inputImage1"]; + [filter setValue:inputImage2 forKey:@"inputImage2"]; + } else if (self.operator1 == SVG_FECOMPOSITE_OPERATOR_ARITHMETIC) { + [filter setValue:inputImage1 forKey:@"inputImage1"]; + [filter setValue:inputImage2 forKey:@"inputImage2"]; + [filter setValue:(self.k1 != nil ? self.k1 : 0) forKey:@"inputK1"]; + [filter setValue:(self.k2 != nil ? self.k2 : 0) forKey:@"inputK2"]; + [filter setValue:(self.k3 != nil ? self.k3 : 0) forKey:@"inputK3"]; + [filter setValue:(self.k4 != nil ? self.k4 : 0) forKey:@"inputK4"]; + } else { + [filter setValue:inputImage1 forKey:@"inputImage"]; + [filter setValue:inputImage2 forKey:@"inputBackgroundImage"]; + } + + return [filter valueForKey:@"outputImage"]; +} + +#ifdef RCT_NEW_ARCH_ENABLED +Class RNSVGFeCompositeCls(void) +{ + return RNSVGFeComposite.class; +} +#endif // RCT_NEW_ARCH_ENABLED + +@end diff --git a/apple/Utils/RCTConvert+RNSVG.h b/apple/Utils/RCTConvert+RNSVG.h index befd7285..5f835292 100644 --- a/apple/Utils/RCTConvert+RNSVG.h +++ b/apple/Utils/RCTConvert+RNSVG.h @@ -13,6 +13,7 @@ #import "RNSVGBlendMode.h" #import "RNSVGCGFCRule.h" #import "RNSVGColorMatrixType.h" +#import "RNSVGCompositeOperator.h" #import "RNSVGEdgeMode.h" #import "RNSVGLength.h" #import "RNSVGMaskType.h" diff --git a/apple/Utils/RCTConvert+RNSVG.mm b/apple/Utils/RCTConvert+RNSVG.mm index bbf2e83b..63da345c 100644 --- a/apple/Utils/RCTConvert+RNSVG.mm +++ b/apple/Utils/RCTConvert+RNSVG.mm @@ -85,6 +85,19 @@ RCT_ENUM_CONVERTER( SVG_FEBLEND_MODE_UNKNOWN, intValue) +RCT_ENUM_CONVERTER( + RNSVGCompositeOperator, + (@{ + @"over" : @(SVG_FECOMPOSITE_OPERATOR_OVER), + @"in" : @(SVG_FECOMPOSITE_OPERATOR_IN), + @"out" : @(SVG_FECOMPOSITE_OPERATOR_OUT), + @"atop" : @(SVG_FECOMPOSITE_OPERATOR_ATOP), + @"xor" : @(SVG_FECOMPOSITE_OPERATOR_XOR), + @"arithmetic" : @(SVG_FECOMPOSITE_OPERATOR_ARITHMETIC), + }), + SVG_FECOMPOSITE_OPERATOR_UNKNOWN, + intValue) + + (RNSVGBrush *)RNSVGBrush:(id)json { if ([json isKindOfClass:[NSNumber class]]) { diff --git a/apple/Utils/RNSVGConvert.h b/apple/Utils/RNSVGConvert.h index 339441a4..320007c4 100644 --- a/apple/Utils/RNSVGConvert.h +++ b/apple/Utils/RNSVGConvert.h @@ -2,6 +2,7 @@ #import #import "RNSVGBlendMode.h" #import "RNSVGColorMatrixType.h" +#import "RNSVGCompositeOperator.h" #import "RNSVGEdgeMode.h" #import "RNSVGUnits.h" @@ -13,6 +14,7 @@ namespace react = facebook::react; + (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimitiveUnits)svgUnits; + (RNSVGBlendMode)RNSVGBlendModeFromCppEquivalent:(react::RNSVGFeBlendMode)mode; + (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type; ++ (RNSVGCompositeOperator)RNSVGRNSVGCompositeOperatorFromCppEquivalent:(react::RNSVGFeCompositeOperator1)operator1; + (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeMode)edgeMode; @end diff --git a/apple/Utils/RNSVGConvert.mm b/apple/Utils/RNSVGConvert.mm index 45978217..96752149 100644 --- a/apple/Utils/RNSVGConvert.mm +++ b/apple/Utils/RNSVGConvert.mm @@ -55,6 +55,24 @@ } } ++ (RNSVGCompositeOperator)RNSVGRNSVGCompositeOperatorFromCppEquivalent:(react::RNSVGFeCompositeOperator1)operator1 +{ + switch (operator1) { + case react::RNSVGFeCompositeOperator1::Over: + return SVG_FECOMPOSITE_OPERATOR_OVER; + case react::RNSVGFeCompositeOperator1::In: + return SVG_FECOMPOSITE_OPERATOR_IN; + case react::RNSVGFeCompositeOperator1::Out: + return SVG_FECOMPOSITE_OPERATOR_OUT; + case react::RNSVGFeCompositeOperator1::Atop: + return SVG_FECOMPOSITE_OPERATOR_ATOP; + case react::RNSVGFeCompositeOperator1::Xor: + return SVG_FECOMPOSITE_OPERATOR_XOR; + case react::RNSVGFeCompositeOperator1::Arithmetic: + return SVG_FECOMPOSITE_OPERATOR_ARITHMETIC; + } +} + + (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeMode)edgeMode { switch (edgeMode) { diff --git a/apple/ViewManagers/RNSVGFeCompositeManager.h b/apple/ViewManagers/RNSVGFeCompositeManager.h new file mode 100644 index 00000000..761115a9 --- /dev/null +++ b/apple/ViewManagers/RNSVGFeCompositeManager.h @@ -0,0 +1,5 @@ +#import "RNSVGFilterPrimitiveManager.h" + +@interface RNSVGFeCompositeManager : RNSVGFilterPrimitiveManager + +@end diff --git a/apple/ViewManagers/RNSVGFeCompositeManager.mm b/apple/ViewManagers/RNSVGFeCompositeManager.mm new file mode 100644 index 00000000..a2d4c96f --- /dev/null +++ b/apple/ViewManagers/RNSVGFeCompositeManager.mm @@ -0,0 +1,22 @@ +#import "RNSVGFeCompositeManager.h" +#import "RNSVGCompositeOperator.h" +#import "RNSVGFeComposite.h" + +@implementation RNSVGFeCompositeManager + +RCT_EXPORT_MODULE() + +- (RNSVGFeComposite *)node +{ + return [RNSVGFeComposite new]; +} + +RCT_EXPORT_VIEW_PROPERTY(in1, NSString) +RCT_EXPORT_VIEW_PROPERTY(in2, NSString) +RCT_EXPORT_VIEW_PROPERTY(operator1, RNSVGCompositeOperator) +RCT_EXPORT_VIEW_PROPERTY(k1, NSNumber *) +RCT_EXPORT_VIEW_PROPERTY(k2, NSNumber *) +RCT_EXPORT_VIEW_PROPERTY(k3, NSNumber *) +RCT_EXPORT_VIEW_PROPERTY(k4, NSNumber *) + +@end diff --git a/apps/examples/src/examples/Filters/FeComposite.tsx b/apps/examples/src/examples/Filters/FeComposite.tsx new file mode 100644 index 00000000..e7485b61 --- /dev/null +++ b/apps/examples/src/examples/Filters/FeComposite.tsx @@ -0,0 +1,424 @@ +import {Image, Text as RNText, View} from 'react-native'; +import { + Defs, + FeComposite, + FeFlood, + FeMerge, + FeMergeNode, + Filter, + G, + Path, + Rect, + Svg, + Text, + Use, +} from 'react-native-svg'; + +import React from 'react'; + +QuickTestExample.title = 'Quick Test Example'; +function QuickTestExample() { + return ( + + + + + + + + ); +} + +ReferenceExample.title = 'FeComposite W3 reference'; +function ReferenceExample() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + opacity 1.0 + + + (with FeFlood) + + + opacity 0.5 + + + (with FeFlood) + + + + + + + over + + + + + + + in + + + + + + + out + + + + + + + atop + + + + + + + xor + + + + + + + arithmetic + + + + + + opacity 1.0 + + + (without FeFlood) + + + opacity 0.5 + + + (without FeFlood) + + + + + + + over + + + + + + + in + + + + + + + out + + + + + + + atop + + + + + + + xor + + + + + + + arithmetic + + + + + + W3 Reference + + + ); +} + +const icon = ( + + + + + + + +); +const samples = [QuickTestExample, ReferenceExample]; + +export {icon, samples}; diff --git a/apps/examples/src/examples/Filters/feComposite.png b/apps/examples/src/examples/Filters/feComposite.png new file mode 100644 index 00000000..d1605daf Binary files /dev/null and b/apps/examples/src/examples/Filters/feComposite.png differ diff --git a/apps/examples/src/examples/Filters/index.tsx b/apps/examples/src/examples/Filters/index.tsx index 870030e8..131cb942 100644 --- a/apps/examples/src/examples/Filters/index.tsx +++ b/apps/examples/src/examples/Filters/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import Svg, {Circle} from 'react-native-svg'; import * as FeBlend from './FeBlend'; import * as FeColorMatrix from './FeColorMatrix'; +import * as FeComposite from './FeComposite'; import * as FeFlood from './FeFlood'; import * as FeGaussianBlur from './FeGaussianBlur'; import * as FeMerge from './FeMerge'; @@ -11,6 +12,7 @@ import * as ReanimatedFeColorMatrix from './ReanimatedFeColorMatrix'; const samples = { FeBlend, FeColorMatrix, + FeComposite, FeFlood, FeGaussianBlur, FeMerge, diff --git a/package.json b/package.json index 9b8944fb..138788bc 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,8 @@ "e2e": "jest e2e", "generateE2eReferences": "ts-node e2e/generateReferences.ts", "check-archs-consistency": "node ./scripts/codegen-check-consistency.js", - "sync-archs": "node ./scripts/codegen-sync-archs.js" + "sync-archs": "node ./scripts/codegen-sync-archs.js", + "metal-to-ci": "node ./scripts/metal.js" }, "peerDependencies": { "react": "*", diff --git a/react-native.config.js b/react-native.config.js index 66aeb24b..7709799f 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -19,6 +19,7 @@ module.exports = { 'RNSVGDefsComponentDescriptor', 'RNSVGFeBlendComponentDescriptor', 'RNSVGFeColorMatrixComponentDescriptor', + 'RNSVGFeCompositeComponentDescriptor', 'RNSVGFeFloodComponentDescriptor', 'RNSVGFeGaussianBlurComponentDescriptor', 'RNSVGFeMergeComponentDescriptor', diff --git a/scripts/metal.js b/scripts/metal.js new file mode 100644 index 00000000..41b661be --- /dev/null +++ b/scripts/metal.js @@ -0,0 +1,56 @@ +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const ROOT_DIR = path.resolve(__dirname, '..'); +const FILTERS_DIR = path.resolve(ROOT_DIR, 'apple/Filters/MetalCI'); + +function exec(command) { + execSync(command); +} + +function clearGeneratedFiles() { + const files = fs.readdirSync(FILTERS_DIR); + console.log('Removing generated files...'); + files.forEach((file) => { + const filePath = path.join(FILTERS_DIR, file); + const fileExtension = path.extname(file); + if (fileExtension === '.air' || fileExtension === '.metallib') { + exec(`rm -rf ${filePath}`); + } + }); + console.log('Generated files removed.'); +} + +function compileMetalFile(file, sdk) { + const filePath = path.join(FILTERS_DIR, file); + const fileName = path.basename(filePath).replace('.metal', ''); + const filePathWithoutExt = path.join(FILTERS_DIR, file).replace('.metal', ''); + console.log('* for ' + sdk); + exec( + `xcrun -sdk ${sdk} metal -fcikernel -c ${filePathWithoutExt}.metal -o ${filePathWithoutExt}.${sdk}.air` + ); + console.log(` ├─ ${fileName}.${sdk}.air`); + exec( + `xcrun -sdk ${sdk} metallib -cikernel ${filePathWithoutExt}.${sdk}.air -o ${filePathWithoutExt}.${sdk}.metallib` + ); + console.log(` └─ ${fileName}.${sdk}.metallib`); +} + +function generateMetallib() { + const files = fs.readdirSync(FILTERS_DIR); + files.forEach((file) => { + const fileExtension = path.extname(file); + if (fileExtension === '.metal') { + const fileName = path.basename(file).replace('.metal', ''); + console.log('Compiling:', fileName + '.metal'); + compileMetalFile(file, 'macosx'); + compileMetalFile(file, 'iphoneos'); + compileMetalFile(file, 'appletvos'); + compileMetalFile(file, 'xros'); + } + }); +} + +clearGeneratedFiles(); +generateMetallib(); diff --git a/src/ReactNativeSVG.ts b/src/ReactNativeSVG.ts index 4aca02df..67d8ba3d 100644 --- a/src/ReactNativeSVG.ts +++ b/src/ReactNativeSVG.ts @@ -26,6 +26,7 @@ import { RNSVGDefs, RNSVGEllipse, RNSVGFeColorMatrix, + RNSVGFeComposite, RNSVGFeGaussianBlur, RNSVGFeMerge, RNSVGFeOffset, @@ -125,6 +126,7 @@ export { RNSVGDefs, RNSVGEllipse, RNSVGFeColorMatrix, + RNSVGFeComposite, RNSVGFeGaussianBlur, RNSVGFeMerge, RNSVGFeOffset, diff --git a/src/elements/filters/FeComposite.tsx b/src/elements/filters/FeComposite.tsx index 324a2c60..269650f8 100644 --- a/src/elements/filters/FeComposite.tsx +++ b/src/elements/filters/FeComposite.tsx @@ -1,5 +1,10 @@ +import { NativeMethods } from 'react-native'; +import RNSVGFeComposite from '../../fabric/FeCompositeNativeComponent'; +import { + extractFeComposite, + extractFilter, +} from '../../lib/extract/extractFilter'; import { NumberProp } from '../../lib/extract/types'; -import { warnUnimplementedFilter } from '../../lib/util'; import FilterPrimitive from './FilterPrimitive'; type FeCompositeOperator = @@ -28,7 +33,14 @@ export default class FeComposite extends FilterPrimitive { }; render() { - warnUnimplementedFilter(); - return null; + return ( + + this.refMethod(ref as (FeComposite & NativeMethods) | null) + } + {...extractFilter(this.props)} + {...extractFeComposite(this.props)} + /> + ); } } diff --git a/src/fabric/FeCompositeNativeComponent.ts b/src/fabric/FeCompositeNativeComponent.ts new file mode 100644 index 00000000..bdf2041a --- /dev/null +++ b/src/fabric/FeCompositeNativeComponent.ts @@ -0,0 +1,34 @@ +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 FeCompositeOperator = + | 'over' + | 'in' + | 'out' + | 'atop' + | 'xor' + | 'arithmetic'; + +interface FilterPrimitiveCommonProps { + x?: UnsafeMixed; + y?: UnsafeMixed; + width?: UnsafeMixed; + height?: UnsafeMixed; + result?: string; +} + +export interface NativeProps extends ViewProps, FilterPrimitiveCommonProps { + in1?: string; + in2?: string; + operator1?: WithDefault; + k1?: Float; + k2?: Float; + k3?: Float; + k4?: Float; +} + +export default codegenNativeComponent('RNSVGFeComposite'); diff --git a/src/fabric/index.ts b/src/fabric/index.ts index 0d3db886..71147c7d 100644 --- a/src/fabric/index.ts +++ b/src/fabric/index.ts @@ -23,6 +23,7 @@ import RNSVGUse from './UseNativeComponent'; import RNSVGFilter from './FilterNativeComponent'; import RNSVGFeBlend from './FeBlendNativeComponent'; import RNSVGFeColorMatrix from './FeColorMatrixNativeComponent'; +import RNSVGFeComposite from './FeCompositeNativeComponent'; import RNSVGFeFlood from './FeFloodNativeComponent'; import RNSVGFeGaussianBlur from './FeGaussianBlurNativeComponent'; import RNSVGFeMerge from './FeMergeNativeComponent'; @@ -54,6 +55,7 @@ export { RNSVGFilter, RNSVGFeBlend, RNSVGFeColorMatrix, + RNSVGFeComposite, RNSVGFeFlood, RNSVGFeGaussianBlur, RNSVGFeMerge, diff --git a/src/lib/extract/extractFilter.ts b/src/lib/extract/extractFilter.ts index 7f60ccd6..e921f6c5 100644 --- a/src/lib/extract/extractFilter.ts +++ b/src/lib/extract/extractFilter.ts @@ -2,11 +2,13 @@ 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 { FeCompositeProps as FeCompositeComponentProps } from '../../elements/filters/FeComposite'; 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 FeCompositeNativeProps } from '../../fabric/FeCompositeNativeComponent'; import { NativeProps as FeFloodNativeProps } from '../../fabric/FeFloodNativeComponent'; import { NativeProps as FeGaussianBlurNativeProps } from '../../fabric/FeGaussianBlurNativeComponent'; import { NativeProps as FeMergeNativeProps } from '../../fabric/FeMergeNativeComponent'; @@ -89,6 +91,24 @@ export const extractFeColorMatrix = ( return extracted; }; +export const extractFeComposite = ( + props: FeCompositeComponentProps +): FeCompositeNativeProps => { + const extracted: FeCompositeNativeProps = { + in1: props.in || '', + in2: props.in2 || '', + operator1: props.operator || 'over', + }; + + (['k1', 'k2', 'k3', 'k4'] as const).forEach((key) => { + if (props[key] !== undefined) { + extracted[key] = Number(props[key]) || 0; + } + }); + + return extracted; +}; + const defaultFill = { type: 0, payload: processColor('black') as ColorValue }; export default function extractFeFlood( props: FeFloodComponentProps