feat: FeComposite filter (#2433)

# Summary

<img width="324" alt="image"
src="https://github.com/user-attachments/assets/0a9b4a56-d093-49f7-aacd-c198ee00f256">

## Test Plan

Examples app -> Filters -> FeComposite

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |          |
| macOS   |    *      |
| Android |          |
| Web     |          |

_*_ macOS isn't working as:
* `CGBitmapContextCreateImage` always returns null
* FeFlood isn't aligned properly (will be fixed in the following PR)
This commit is contained in:
Jakub Grzywacz
2024-10-25 11:18:07 +02:00
committed by GitHub
parent 525d09ef34
commit 4637dee1e4
51 changed files with 1372 additions and 6 deletions

View File

@@ -12,14 +12,19 @@ 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)

View File

@@ -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<String, Bitmap> 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;
}
}

View File

@@ -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<String, FeCompositeOperator> typeToEnum = new HashMap<>();
static {
for (final FeCompositeOperator en : FeCompositeOperator.values()) {
typeToEnum.put(en.type, en);
}
}
@Nonnull
@Override
public String toString() {
return type;
}
}
}

View File

@@ -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<V extends VirtualView> extends ViewGroupManager<Virtual
RNSVGFilter,
RNSVGFeBlend,
RNSVGFeColorMatrix,
RNSVGFeComposite,
RNSVGFeFlood,
RNSVGFeGaussianBlur,
RNSVGFeMerge,
@@ -649,6 +652,8 @@ class VirtualViewManager<V extends VirtualView> extends ViewGroupManager<Virtual
return new FeBlendView(reactContext);
case RNSVGFeColorMatrix:
return new FeColorMatrixView(reactContext);
case RNSVGFeComposite:
return new FeCompositeView(reactContext);
case RNSVGFeFlood:
return new FeFloodView(reactContext);
case RNSVGFeGaussianBlur:
@@ -1640,6 +1645,51 @@ class RenderableViewManager<T extends RenderableView> extends VirtualViewManager
}
}
static class FeCompositeManager extends FilterPrimitiveManager<FeCompositeView>
implements RNSVGFeCompositeManagerInterface<FeCompositeView> {
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<FeFloodView>
implements RNSVGFeFloodManagerInterface<FeFloodView> {
FeFloodManager() {

View File

@@ -232,6 +232,15 @@ public class SvgPackage extends TurboReactPackage implements ViewManagerOnDemand
return new FeColorMatrixManager();
}
}));
specs.put(
FeCompositeManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FeCompositeManager();
}
}));
specs.put(
FeFloodManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(

View File

@@ -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<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFeCompositeManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
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);
}
}
}

View File

@@ -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<T extends View> {
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);
}

View File

@@ -0,0 +1,11 @@
#import "RNSVGCustomFilter.h"
@interface RNSVGArithmeticFilter : RNSVGCustomFilter {
CIImage *inputImage2;
NSNumber *inputK1;
NSNumber *inputK2;
NSNumber *inputK3;
NSNumber *inputK4;
}
@end

Binary file not shown.

View File

@@ -0,0 +1,16 @@
#include <metal_stdlib>
using namespace metal;
#include <CoreImage/CoreImage.h>
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;
}

View File

@@ -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

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
#import "RNSVGCustomFilter.h"
@interface RNSVGCompositeXor : RNSVGCustomFilter {
CIImage *inputImage2;
}
@end

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
#include <metal_stdlib>
using namespace metal;
#include <CoreImage/CoreImage.h>
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;
}

View File

@@ -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

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,9 @@
#import <CoreImage/CoreImage.h>
@interface RNSVGCustomFilter : CIFilter {
CIImage *inputImage1;
}
- (CIColorKernel *)getWithName:(NSString *)name;
@end

View File

@@ -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

View File

@@ -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
};

View File

@@ -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

View File

@@ -0,0 +1,201 @@
#import "RNSVGFeComposite.h"
#import "RNSVGArithmeticFilter.h"
#import "RNSVGCompositeXor.h"
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTConversions.h>
#import <React/RCTFabricComponentsPlugins.h>
#import <react/renderer/components/rnsvg/ComponentDescriptors.h>
#import <react/renderer/components/view/conversions.h>
#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<const RNSVGFeCompositeProps>();
_props = defaultProps;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNSVGFeCompositeComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = static_cast<const RNSVGFeCompositeProps &>(*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<RNSVGFeCompositeProps const>(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<NSString *, CIImage *> *)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<RCTComponentViewProtocol> RNSVGFeCompositeCls(void)
{
return RNSVGFeComposite.class;
}
#endif // RCT_NEW_ARCH_ENABLED
@end

View File

@@ -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"

View File

@@ -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]]) {

View File

@@ -2,6 +2,7 @@
#import <react/renderer/components/rnsvg/Props.h>
#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

View File

@@ -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) {

View File

@@ -0,0 +1,5 @@
#import "RNSVGFilterPrimitiveManager.h"
@interface RNSVGFeCompositeManager : RNSVGFilterPrimitiveManager
@end

View File

@@ -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

View File

@@ -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 (
<Svg width="200" height="150">
<Filter id="offset" width="180" height="180">
<FeComposite in="SourceGraphic" in2="BackgroundImage" operator="in" />
</Filter>
<Rect x="50" y="50" width="100" height="100" fill="red" />
<Rect
x="0"
y="0"
width="100"
height="100"
fill="blue"
filter="url(#offset)"
/>
</Svg>
);
}
ReferenceExample.title = 'FeComposite W3 reference';
function ReferenceExample() {
return (
<View style={{backgroundColor: '#fff'}}>
<Svg width="330" height="195" viewBox="0 0 1100 650">
<Defs>
<Filter
id="overFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeFlood floodColor="#ffffff" floodOpacity="1" result="flood" />
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="over"
result="comp"
/>
<FeMerge>
<FeMergeNode in="flood" />
<FeMergeNode in="comp" />
</FeMerge>
</Filter>
<Filter
id="inFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeFlood floodColor="#ffffff" floodOpacity="1" result="flood" />
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="in"
result="comp"
/>
<FeMerge>
<FeMergeNode in="flood" />
<FeMergeNode in="comp" />
</FeMerge>
</Filter>
<Filter
id="outFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeFlood floodColor="#ffffff" floodOpacity="1" result="flood" />
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="out"
result="comp"
/>
<FeMerge>
<FeMergeNode in="flood" />
<FeMergeNode in="comp" />
</FeMerge>
</Filter>
<Filter
id="atopFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeFlood floodColor="#ffffff" floodOpacity="1" result="flood" />
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="atop"
result="comp"
/>
<FeMerge>
<FeMergeNode in="flood" />
<FeMergeNode in="comp" />
</FeMerge>
</Filter>
<Filter
id="xorFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeFlood floodColor="#ffffff" floodOpacity="1" result="flood" />
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="xor"
result="comp"
/>
<FeMerge>
<FeMergeNode in="flood" />
<FeMergeNode in="comp" />
</FeMerge>
</Filter>
<Filter
id="arithmeticFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeFlood floodColor="#ffffff" floodOpacity="1" result="flood" />
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
result="comp"
operator="arithmetic"
k1=".5"
k2=".5"
k3=".5"
k4=".5"
/>
<FeMerge>
<FeMergeNode in="flood" />
<FeMergeNode in="comp" />
</FeMerge>
</Filter>
<Filter
id="overNoFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="over"
result="comp"
/>
</Filter>
<Filter
id="inNoFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="in"
result="comp"
/>
</Filter>
<Filter
id="outNoFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="out"
result="comp"
/>
</Filter>
<Filter
id="atopNoFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="atop"
result="comp"
/>
</Filter>
<Filter
id="xorNoFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="xor"
result="comp"
/>
</Filter>
<Filter
id="arithmeticNoFlood"
filterUnits="objectBoundingBox"
x="-5%"
y="-5%"
width="110%"
height="110%">
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
result="comp"
operator="arithmetic"
k1=".5"
k2=".5"
k3=".5"
k4=".5"
/>
</Filter>
<Path id="Blue100" d="M 0 0 L 100 0 L 100 100 z" fill="#00ffff" />
<Path id="Red100" d="M 0 0 L 0 100 L 100 0 z" fill="#ff00ff" />
<Path
id="Blue50"
d="M 0 125 L 100 125 L 100 225 z"
fill="#00ffff"
fillOpacity=".5"
/>
<Path
id="Red50"
d="M 0 125 L 0 225 L 100 125 z"
fill="#ff00ff"
fillOpacity=".5"
/>
<G id="TwoBlueTriangles">
<Use href="#Blue100" />
<Use href="#Blue50" />
</G>
<G id="BlueTriangles">
<Use transform="translate(275,25)" href="#TwoBlueTriangles" />
<Use transform="translate(400,25)" href="#TwoBlueTriangles" />
<Use transform="translate(525,25)" href="#TwoBlueTriangles" />
<Use transform="translate(650,25)" href="#TwoBlueTriangles" />
<Use transform="translate(775,25)" href="#TwoBlueTriangles" />
<Use transform="translate(900,25)" href="#TwoBlueTriangles" />
</G>
</Defs>
<Rect fill="none" stroke="blue" x="1" y="1" width="1098" height="648" />
<G font-family="Verdana" fontSize="40" shape-rendering="crispEdges">
<G enable-background="new">
<Text x="15" y="75">
opacity 1.0
</Text>
<Text x="15" y="115" fontSize="27">
(with FeFlood)
</Text>
<Text x="15" y="200">
opacity 0.5
</Text>
<Text x="15" y="240" fontSize="27">
(with FeFlood)
</Text>
<Use href="#BlueTriangles" />
<G transform="translate(275,25)">
<Use href="#Red100" filter="url(#overFlood)" />
<Use href="#Red50" filter="url(#overFlood)" />
<Text x="5" y="275">
over
</Text>
</G>
<G transform="translate(400,25)">
<Use href="#Red100" filter="url(#inFlood)" />
<Use href="#Red50" filter="url(#inFlood)" />
<Text x="35" y="275">
in
</Text>
</G>
<G transform="translate(525,25)">
<Use href="#Red100" filter="url(#outFlood)" />
<Use href="#Red50" filter="url(#outFlood)" />
<Text x="15" y="275">
out
</Text>
</G>
<G transform="translate(650,25)">
<Use href="#Red100" filter="url(#atopFlood)" />
<Use href="#Red50" filter="url(#atopFlood)" />
<Text x="10" y="275">
atop
</Text>
</G>
<G transform="translate(775,25)">
<Use href="#Red100" filter="url(#xorFlood)" />
<Use href="#Red50" filter="url(#xorFlood)" />
<Text x="15" y="275">
xor
</Text>
</G>
<G transform="translate(900,25)">
<Use href="#Red100" filter="url(#arithmeticFlood)" />
<Use href="#Red50" filter="url(#arithmeticFlood)" />
<Text x="-25" y="275">
arithmetic
</Text>
</G>
</G>
<G transform="translate(0,325)" enable-background="new">
<Text x="15" y="75">
opacity 1.0
</Text>
<Text x="15" y="115" fontSize="27">
(without FeFlood)
</Text>
<Text x="15" y="200">
opacity 0.5
</Text>
<Text x="15" y="240" fontSize="27">
(without FeFlood)
</Text>
<Use href="#BlueTriangles" />
<G transform="translate(275,25)">
<Use href="#Red100" filter="url(#overNoFlood)" />
<Use href="#Red50" filter="url(#overNoFlood)" />
<Text x="5" y="275">
over
</Text>
</G>
<G transform="translate(400,25)">
<Use href="#Red100" filter="url(#inNoFlood)" />
<Use href="#Red50" filter="url(#inNoFlood)" />
<Text x="35" y="275">
in
</Text>
</G>
<G transform="translate(525,25)">
<Use href="#Red100" filter="url(#outNoFlood)" />
<Use href="#Red50" filter="url(#outNoFlood)" />
<Text x="15" y="275">
out
</Text>
</G>
<G transform="translate(650,25)">
<Use href="#Red100" filter="url(#atopNoFlood)" />
<Use href="#Red50" filter="url(#atopNoFlood)" />
<Text x="10" y="275">
atop
</Text>
</G>
<G transform="translate(775,25)">
<Use href="#Red100" filter="url(#xorNoFlood)" />
<Use href="#Red50" filter="url(#xorNoFlood)" />
<Text x="15" y="275">
xor
</Text>
</G>
<G transform="translate(900,25)">
<Use href="#Red100" filter="url(#arithmeticNoFlood)" />
<Use href="#Red50" filter="url(#arithmeticNoFlood)" />
<Text x="-25" y="275">
arithmetic
</Text>
</G>
</G>
</G>
</Svg>
<RNText>W3 Reference</RNText>
<Image source={require('./feComposite.png')} />
</View>
);
}
const icon = (
<Svg height="30" width="30" viewBox="0 0 20 20">
<Filter id="iconCompositeIn" width="200%">
<FeComposite
in="SourceGraphic"
in2="BackgroundImage"
operator="in"
result="comp"
/>
</Filter>
<Path id="Blue100" d="M 0 0 L 20 0 L 20 20 z" fill="#00ffff" />
<Path
id="Red100"
d="M 0 0 L 0 20 L 20 0 z"
fill="#ff00ff"
filter="url(#iconCompositeIn)"
/>
</Svg>
);
const samples = [QuickTestExample, ReferenceExample];
export {icon, samples};

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@@ -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,

View File

@@ -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": "*",

View File

@@ -19,6 +19,7 @@ module.exports = {
'RNSVGDefsComponentDescriptor',
'RNSVGFeBlendComponentDescriptor',
'RNSVGFeColorMatrixComponentDescriptor',
'RNSVGFeCompositeComponentDescriptor',
'RNSVGFeFloodComponentDescriptor',
'RNSVGFeGaussianBlurComponentDescriptor',
'RNSVGFeMergeComponentDescriptor',

56
scripts/metal.js Normal file
View File

@@ -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();

View File

@@ -26,6 +26,7 @@ import {
RNSVGDefs,
RNSVGEllipse,
RNSVGFeColorMatrix,
RNSVGFeComposite,
RNSVGFeGaussianBlur,
RNSVGFeMerge,
RNSVGFeOffset,
@@ -125,6 +126,7 @@ export {
RNSVGDefs,
RNSVGEllipse,
RNSVGFeColorMatrix,
RNSVGFeComposite,
RNSVGFeGaussianBlur,
RNSVGFeMerge,
RNSVGFeOffset,

View File

@@ -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<FeCompositeProps> {
};
render() {
warnUnimplementedFilter();
return null;
return (
<RNSVGFeComposite
ref={(ref) =>
this.refMethod(ref as (FeComposite & NativeMethods) | null)
}
{...extractFilter(this.props)}
{...extractFeComposite(this.props)}
/>
);
}
}

View File

@@ -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<NumberProp>;
y?: UnsafeMixed<NumberProp>;
width?: UnsafeMixed<NumberProp>;
height?: UnsafeMixed<NumberProp>;
result?: string;
}
export interface NativeProps extends ViewProps, FilterPrimitiveCommonProps {
in1?: string;
in2?: string;
operator1?: WithDefault<FeCompositeOperator, 'over'>;
k1?: Float;
k2?: Float;
k3?: Float;
k4?: Float;
}
export default codegenNativeComponent<NativeProps>('RNSVGFeComposite');

View File

@@ -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,

View File

@@ -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