mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-06 00:12:21 +00:00
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:
Binary file not shown.
Binary file not shown.
@@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
#import "RNSVGCustomFilter.h"
|
||||
|
||||
@interface RNSVGCompositeXor : RNSVGCustomFilter {
|
||||
CIImage *inputImage2;
|
||||
}
|
||||
|
||||
@end
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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.
@@ -0,0 +1,9 @@
|
||||
#import <CoreImage/CoreImage.h>
|
||||
|
||||
@interface RNSVGCustomFilter : CIFilter {
|
||||
CIImage *inputImage1;
|
||||
}
|
||||
|
||||
- (CIColorKernel *)getWithName:(NSString *)name;
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user