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

View File

@@ -0,0 +1,43 @@
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableType;
import java.util.HashMap;
@SuppressLint("ViewConstructor")
class FeMergeView extends FilterPrimitiveView {
private ReadableArray mNodes;
public FeMergeView(ReactContext reactContext) {
super(reactContext);
}
public void setNodes(ReadableArray nodes) {
this.mNodes = nodes;
invalidate();
}
@Override
public Bitmap applyFilter(HashMap<String, Bitmap> resultsMap, Bitmap prevResult) {
Bitmap result =
Bitmap.createBitmap(prevResult.getWidth(), prevResult.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
int nodesSize = this.mNodes.size();
for (int i = 0; i < nodesSize; i++) {
Bitmap sourceFromResults =
this.mNodes.getType(i) == ReadableType.String
? resultsMap.get(this.mNodes.getString(i))
: prevResult;
if (sourceFromResults != null) {
canvas.drawBitmap(sourceFromResults, 0, 0, new Paint());
}
}
return result;
}
}

View File

@@ -107,6 +107,8 @@ import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeMergeManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeMergeManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeOffsetManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeOffsetManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFilterManagerDelegate;
@@ -589,6 +591,7 @@ class VirtualViewManager<V extends VirtualView> extends ViewGroupManager<Virtual
RNSVGFilter,
RNSVGFeColorMatrix,
RNSVGFeGaussianBlur,
RNSVGFeMerge,
RNSVGFeOffset,
RNSVGMarker,
RNSVGForeignObject,
@@ -640,6 +643,8 @@ class VirtualViewManager<V extends VirtualView> extends ViewGroupManager<Virtual
return new FeColorMatrixView(reactContext);
case RNSVGFeGaussianBlur:
return new FeGaussianBlurView(reactContext);
case RNSVGFeMerge:
return new FeMergeView(reactContext);
case RNSVGFeOffset:
return new FeOffsetView(reactContext);
case RNSVGMarker:
@@ -1630,6 +1635,21 @@ class RenderableViewManager<T extends RenderableView> extends VirtualViewManager
}
}
static class FeMergeManager extends FilterPrimitiveManager<FeMergeView>
implements RNSVGFeMergeManagerInterface<FeMergeView> {
FeMergeManager() {
super(SVGClass.RNSVGFeMerge);
mDelegate = new RNSVGFeMergeManagerDelegate(this);
}
public static final String REACT_CLASS = "RNSVGFeMerge";
@ReactProp(name = "nodes")
public void setNodes(FeMergeView node, ReadableArray nodes) {
node.setNodes(nodes);
}
}
static class FeOffsetManager extends FilterPrimitiveManager<FeOffsetView>
implements RNSVGFeOffsetManagerInterface<FeOffsetView> {
FeOffsetManager() {

View File

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

View File

@@ -0,0 +1,48 @@
/**
* 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.bridge.ReadableArray;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.BaseViewManagerInterface;
public class RNSVGFeMergeManagerDelegate<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFeMergeManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNSVGFeMergeManagerDelegate(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 "nodes":
mViewManager.setNodes(view, (ReadableArray) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,24 @@
/**
* 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;
import com.facebook.react.bridge.ReadableArray;
public interface RNSVGFeMergeManagerInterface<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 setNodes(T view, @Nullable ReadableArray value);
}