feat: add FeMerge and FeMergeNode filters (#2369)

# Summary

As mentioned in #2362
Introduce new filters:
* `FeMerge`
* `FeMergeNode`

## Example usage

```tsx
<Svg width="200" height="200">
  <Filter id="mergeWithOffset" width="180" height="180">
    <FeOffset dx="50" dy="50" result="test" />
    <FeOffset dx="100" dy="100" in="SourceGraphic" />
    <FeMerge>
      <FeMergeNode in="SourceGraphic" />
      <FeMergeNode in="test" />
      <FeMergeNode />
    </FeMerge>
  </Filter>
  <Rect
    x="0"
    y="0"
    width="100"
    height="100"
    stroke="black"
    fill="red"
    filter="url(#mergeWithOffset)"
  />
</Svg>
```

<img width="207" alt="image"
src="https://github.com/user-attachments/assets/9cb3ded6-f939-4b2b-8ece-df54e64fe898">

## Test Plan

`Example` app -> `Filters` -> `FeMerge`

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |         |
| Android |         |

## Checklist

- [x] I have tested this on a device and a simulator
- [x] I added documentation in `README.md`
- [x] I updated the typed files (typescript)
This commit is contained in:
Jakub Grzywacz
2024-07-31 13:23:53 +02:00
committed by GitHub
parent a2e843bc9c
commit b8b022c31e
25 changed files with 508 additions and 9 deletions
+7
View File
@@ -0,0 +1,7 @@
#import "RNSVGFilterPrimitive.h"
@interface RNSVGFeMerge : RNSVGFilterPrimitive
@property (nonatomic, copy) NSArray<NSString *> *nodes;
@end
+105
View File
@@ -0,0 +1,105 @@
#import "RNSVGFeMerge.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 "RNSVGFabricConversions.h"
#endif // RCT_NEW_ARCH_ENABLED
@implementation RNSVGFeMerge
#ifdef RCT_NEW_ARCH_ENABLED
using namespace facebook::react;
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RNSVGFeMergeProps>();
_props = defaultProps;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNSVGFeMergeComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = static_cast<const RNSVGFeMergeProps &>(*props);
if (newProps.nodes.size() > 0) {
NSMutableArray *nodesArray = [NSMutableArray new];
for (auto node : newProps.nodes) {
id json = RNSVGConvertFollyDynamicToId(node);
if ([json isKindOfClass:[NSString class]]) {
[nodesArray addObject:[json stringValue]];
} else {
[nodesArray addObject:[NSNull null]];
}
}
self.nodes = nodesArray;
}
setCommonFilterProps(newProps, self);
_props = std::static_pointer_cast<RNSVGFeMergeProps const>(props);
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_nodes = nil;
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)setNodes:(NSArray<NSString *> *)nodes
{
if (nodes == _nodes) {
return;
}
_nodes = nodes;
[self invalidate];
}
- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results
previousFilterResult:(CIImage *)previous
ctm:(CGAffineTransform)ctm
{
CIFilter *filter = [CIFilter filterWithName:@"CISourceOverCompositing"];
[filter setDefaults];
CIImage *result;
for (int i = 0; i < [self.nodes count]; i++) {
NSString *nodeKey = [self.nodes objectAtIndex:i];
CIImage *inputImage =
[nodeKey isEqual:[NSNull null]] ? previous : [results objectForKey:[self.nodes objectAtIndex:i]];
if (inputImage == nil) {
continue;
}
if (result == nil) {
result = inputImage;
continue;
}
[filter setValue:result forKey:@"inputBackgroundImage"];
[filter setValue:inputImage forKey:@"inputImage"];
result = [filter valueForKey:@"outputImage"];
}
return result;
}
#ifdef RCT_NEW_ARCH_ENABLED
Class<RCTComponentViewProtocol> RNSVGFeMergeCls(void)
{
return RNSVGFeMerge.class;
}
#endif // RCT_NEW_ARCH_ENABLED
@end