Skip to content

Commit 4637dee

Browse files
authored
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)
1 parent 525d09e commit 4637dee

File tree

51 files changed

+1372
-6
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1372
-6
lines changed

RNSVG.podspec

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ Pod::Spec.new do |s|
1212
s.homepage = package['homepage']
1313
s.authors = 'Horcrux Chen'
1414
s.source = { :git => 'https://github.com/react-native-community/react-native-svg.git', :tag => "v#{s.version}" }
15-
s.source_files = 'apple/**/*.{h,m,mm}'
15+
s.source_files = 'apple/**/*.{h,m,mm,metal}'
1616
s.ios.exclude_files = '**/*.macos.{h,m,mm}'
1717
s.tvos.exclude_files = '**/*.macos.{h,m,mm}'
1818
s.visionos.exclude_files = '**/*.macos.{h,m,mm}' if s.respond_to?(:visionos)
1919
s.osx.exclude_files = '**/*.ios.{h,m,mm}'
20-
s.requires_arc = true
20+
s.requires_arc = true
2121
s.platforms = { :osx => "10.14", :ios => "12.4", :tvos => "12.4", :visionos => "1.0" }
22+
23+
s.osx.resource_bundles = {'RNSVGFilters' => ['apple/**/*.macosx.metallib']}
24+
s.ios.resource_bundles = {'RNSVGFilters' => ['apple/**/*.iphoneos.metallib']}
25+
s.tvos.resource_bundles = {'RNSVGFilters' => ['apple/**/*.appletvos.metallib']}
26+
s.visionos.resource_bundles = {'RNSVGFilters' => ['apple/**/*.xros.metallib']}
2227

2328
if fabric_enabled
2429
install_modules_dependencies(s)
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package com.horcrux.svg;
2+
3+
import android.annotation.SuppressLint;
4+
import android.graphics.Bitmap;
5+
import android.graphics.Canvas;
6+
import android.graphics.Paint;
7+
import android.graphics.PorterDuff;
8+
import android.graphics.PorterDuffXfermode;
9+
import com.facebook.react.bridge.ReactContext;
10+
import java.util.HashMap;
11+
12+
@SuppressLint("ViewConstructor")
13+
class FeCompositeView extends FilterPrimitiveView {
14+
String mIn1;
15+
String mIn2;
16+
float mK1;
17+
float mK2;
18+
float mK3;
19+
float mK4;
20+
FilterProperties.FeCompositeOperator mOperator;
21+
22+
public FeCompositeView(ReactContext reactContext) {
23+
super(reactContext);
24+
}
25+
26+
public void setIn1(String in1) {
27+
this.mIn1 = in1;
28+
invalidate();
29+
}
30+
31+
public void setIn2(String in2) {
32+
this.mIn2 = in2;
33+
invalidate();
34+
}
35+
36+
public void setK1(Float value) {
37+
this.mK1 = value;
38+
invalidate();
39+
}
40+
41+
public void setK2(Float value) {
42+
this.mK2 = value;
43+
invalidate();
44+
}
45+
46+
public void setK3(Float value) {
47+
this.mK3 = value;
48+
invalidate();
49+
}
50+
51+
public void setK4(Float value) {
52+
this.mK4 = value;
53+
invalidate();
54+
}
55+
56+
public void setOperator(String operator) {
57+
this.mOperator = FilterProperties.FeCompositeOperator.getEnum(operator);
58+
invalidate();
59+
}
60+
61+
@Override
62+
public Bitmap applyFilter(HashMap<String, Bitmap> resultsMap, Bitmap prevResult) {
63+
Bitmap in1 = getSource(resultsMap, prevResult, this.mIn1);
64+
Bitmap in2 = getSource(resultsMap, prevResult, this.mIn2);
65+
Bitmap result = Bitmap.createBitmap(in1.getWidth(), in1.getHeight(), Bitmap.Config.ARGB_8888);
66+
Canvas canvas = new Canvas(result);
67+
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
68+
canvas.drawBitmap(in1, 0, 0, paint);
69+
70+
switch (this.mOperator) {
71+
case OVER -> {
72+
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
73+
}
74+
case IN -> {
75+
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
76+
}
77+
case OUT -> {
78+
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
79+
}
80+
case ATOP -> {
81+
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
82+
}
83+
case XOR -> {
84+
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
85+
}
86+
case ARITHMETIC -> {
87+
// result = k1*i1*i2 + k2*i1 + k3*i2 + k4
88+
int nPixels = result.getWidth() * result.getHeight();
89+
int[] pixels1 = new int[nPixels];
90+
int[] pixels2 = new int[nPixels];
91+
result.getPixels(
92+
pixels1, 0, result.getWidth(), 0, 0, result.getWidth(), result.getHeight());
93+
94+
for (int i = 0; i < nPixels; i++) {
95+
int color1 = pixels1[i];
96+
int color2 = pixels2[i];
97+
98+
int r1 = (color1 >> 16) & 0xFF;
99+
int g1 = (color1 >> 8) & 0xFF;
100+
int b1 = color1 & 0xFF;
101+
int a1 = (color1 >>> 24);
102+
int r2 = (color2 >> 16) & 0xFF;
103+
int g2 = (color2 >> 8) & 0xFF;
104+
int b2 = color2 & 0xFF;
105+
int a2 = (color2 >>> 24);
106+
107+
int rResult = (int) (mK1 * r1 * r2 + mK2 * r1 + mK3 * r2 + mK4);
108+
int gResult = (int) (mK1 * g1 * g2 + mK2 * g1 + mK3 * g2 + mK4);
109+
int bResult = (int) (mK1 * b1 * b2 + mK2 * b1 + mK3 * b2 + mK4);
110+
int aResult = (int) (mK1 * a1 * a2 + mK2 * a1 + mK3 * a2 + mK4);
111+
112+
rResult = Math.min(255, Math.max(0, rResult));
113+
gResult = Math.min(255, Math.max(0, gResult));
114+
bResult = Math.min(255, Math.max(0, bResult));
115+
aResult = Math.min(255, Math.max(0, aResult));
116+
117+
int pixel = (aResult << 24) | (rResult << 16) | (gResult << 8) | bResult;
118+
pixels1[i] = pixel;
119+
}
120+
121+
result.setPixels(
122+
pixels1, 0, result.getWidth(), 0, 0, result.getWidth(), result.getHeight());
123+
}
124+
}
125+
126+
if (this.mOperator != FilterProperties.FeCompositeOperator.ARITHMETIC) {
127+
canvas.drawBitmap(in2, 0, 0, paint);
128+
}
129+
130+
return result;
131+
}
132+
}

android/src/main/java/com/horcrux/svg/FilterProperties.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,41 @@ public String toString() {
144144
return type;
145145
}
146146
}
147+
148+
enum FeCompositeOperator {
149+
OVER("over"),
150+
IN("in"),
151+
OUT("out"),
152+
ATOP("atop"),
153+
XOR("xor"),
154+
ARITHMETIC("arithmetic"),
155+
;
156+
157+
private final String type;
158+
159+
FeCompositeOperator(String type) {
160+
this.type = type;
161+
}
162+
163+
static FeCompositeOperator getEnum(String strVal) {
164+
if (!typeToEnum.containsKey(strVal)) {
165+
throw new IllegalArgumentException("Unknown String Value: " + strVal);
166+
}
167+
return typeToEnum.get(strVal);
168+
}
169+
170+
private static final Map<String, FeCompositeOperator> typeToEnum = new HashMap<>();
171+
172+
static {
173+
for (final FeCompositeOperator en : FeCompositeOperator.values()) {
174+
typeToEnum.put(en.type, en);
175+
}
176+
}
177+
178+
@Nonnull
179+
@Override
180+
public String toString() {
181+
return type;
182+
}
183+
}
147184
}

android/src/main/java/com/horcrux/svg/RenderableViewManager.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@
107107
import com.facebook.react.viewmanagers.RNSVGFeBlendManagerInterface;
108108
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerDelegate;
109109
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface;
110+
import com.facebook.react.viewmanagers.RNSVGFeCompositeManagerDelegate;
111+
import com.facebook.react.viewmanagers.RNSVGFeCompositeManagerInterface;
110112
import com.facebook.react.viewmanagers.RNSVGFeFloodManagerDelegate;
111113
import com.facebook.react.viewmanagers.RNSVGFeFloodManagerInterface;
112114
import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerDelegate;
@@ -595,6 +597,7 @@ protected enum SVGClass {
595597
RNSVGFilter,
596598
RNSVGFeBlend,
597599
RNSVGFeColorMatrix,
600+
RNSVGFeComposite,
598601
RNSVGFeFlood,
599602
RNSVGFeGaussianBlur,
600603
RNSVGFeMerge,
@@ -649,6 +652,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex
649652
return new FeBlendView(reactContext);
650653
case RNSVGFeColorMatrix:
651654
return new FeColorMatrixView(reactContext);
655+
case RNSVGFeComposite:
656+
return new FeCompositeView(reactContext);
652657
case RNSVGFeFlood:
653658
return new FeFloodView(reactContext);
654659
case RNSVGFeGaussianBlur:
@@ -1640,6 +1645,51 @@ public void setValues(FeColorMatrixView node, @Nullable ReadableArray values) {
16401645
}
16411646
}
16421647

1648+
static class FeCompositeManager extends FilterPrimitiveManager<FeCompositeView>
1649+
implements RNSVGFeCompositeManagerInterface<FeCompositeView> {
1650+
FeCompositeManager() {
1651+
super(SVGClass.RNSVGFeComposite);
1652+
mDelegate = new RNSVGFeCompositeManagerDelegate(this);
1653+
}
1654+
1655+
public static final String REACT_CLASS = "RNSVGFeComposite";
1656+
1657+
@ReactProp(name = "in1")
1658+
public void setIn1(FeCompositeView node, String in1) {
1659+
node.setIn1(in1);
1660+
}
1661+
1662+
@ReactProp(name = "in2")
1663+
public void setIn2(FeCompositeView node, String in2) {
1664+
node.setIn2(in2);
1665+
}
1666+
1667+
@ReactProp(name = "operator1")
1668+
public void setOperator1(FeCompositeView node, String operator) {
1669+
node.setOperator(operator);
1670+
}
1671+
1672+
@ReactProp(name = "k1")
1673+
public void setK1(FeCompositeView node, float value) {
1674+
node.setK1(value);
1675+
}
1676+
1677+
@ReactProp(name = "k2")
1678+
public void setK2(FeCompositeView node, float value) {
1679+
node.setK2(value);
1680+
}
1681+
1682+
@ReactProp(name = "k3")
1683+
public void setK3(FeCompositeView node, float value) {
1684+
node.setK3(value);
1685+
}
1686+
1687+
@ReactProp(name = "k4")
1688+
public void setK4(FeCompositeView node, float value) {
1689+
node.setK4(value);
1690+
}
1691+
}
1692+
16431693
static class FeFloodManager extends FilterPrimitiveManager<FeFloodView>
16441694
implements RNSVGFeFloodManagerInterface<FeFloodView> {
16451695
FeFloodManager() {

android/src/main/java/com/horcrux/svg/SvgPackage.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ public NativeModule get() {
232232
return new FeColorMatrixManager();
233233
}
234234
}));
235+
specs.put(
236+
FeCompositeManager.REACT_CLASS,
237+
ModuleSpec.viewManagerSpec(
238+
new Provider<NativeModule>() {
239+
@Override
240+
public NativeModule get() {
241+
return new FeCompositeManager();
242+
}
243+
}));
235244
specs.put(
236245
FeFloodManager.REACT_CLASS,
237246
ModuleSpec.viewManagerSpec(
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
3+
*
4+
* Do not edit this file as changes may cause incorrect behavior and will be lost
5+
* once the code is regenerated.
6+
*
7+
* @generated by codegen project: GeneratePropsJavaDelegate.js
8+
*/
9+
10+
package com.facebook.react.viewmanagers;
11+
12+
import android.view.View;
13+
import androidx.annotation.Nullable;
14+
import com.facebook.react.bridge.DynamicFromObject;
15+
import com.facebook.react.uimanager.BaseViewManagerDelegate;
16+
import com.facebook.react.uimanager.BaseViewManagerInterface;
17+
18+
public class RNSVGFeCompositeManagerDelegate<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFeCompositeManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
19+
public RNSVGFeCompositeManagerDelegate(U viewManager) {
20+
super(viewManager);
21+
}
22+
@Override
23+
public void setProperty(T view, String propName, @Nullable Object value) {
24+
switch (propName) {
25+
case "x":
26+
mViewManager.setX(view, new DynamicFromObject(value));
27+
break;
28+
case "y":
29+
mViewManager.setY(view, new DynamicFromObject(value));
30+
break;
31+
case "width":
32+
mViewManager.setWidth(view, new DynamicFromObject(value));
33+
break;
34+
case "height":
35+
mViewManager.setHeight(view, new DynamicFromObject(value));
36+
break;
37+
case "result":
38+
mViewManager.setResult(view, value == null ? null : (String) value);
39+
break;
40+
case "in1":
41+
mViewManager.setIn1(view, value == null ? null : (String) value);
42+
break;
43+
case "in2":
44+
mViewManager.setIn2(view, value == null ? null : (String) value);
45+
break;
46+
case "operator1":
47+
mViewManager.setOperator1(view, (String) value);
48+
break;
49+
case "k1":
50+
mViewManager.setK1(view, value == null ? 0f : ((Double) value).floatValue());
51+
break;
52+
case "k2":
53+
mViewManager.setK2(view, value == null ? 0f : ((Double) value).floatValue());
54+
break;
55+
case "k3":
56+
mViewManager.setK3(view, value == null ? 0f : ((Double) value).floatValue());
57+
break;
58+
case "k4":
59+
mViewManager.setK4(view, value == null ? 0f : ((Double) value).floatValue());
60+
break;
61+
default:
62+
super.setProperty(view, propName, value);
63+
}
64+
}
65+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
3+
*
4+
* Do not edit this file as changes may cause incorrect behavior and will be lost
5+
* once the code is regenerated.
6+
*
7+
* @generated by codegen project: GeneratePropsJavaInterface.js
8+
*/
9+
10+
package com.facebook.react.viewmanagers;
11+
12+
import android.view.View;
13+
import androidx.annotation.Nullable;
14+
import com.facebook.react.bridge.Dynamic;
15+
16+
public interface RNSVGFeCompositeManagerInterface<T extends View> {
17+
void setX(T view, Dynamic value);
18+
void setY(T view, Dynamic value);
19+
void setWidth(T view, Dynamic value);
20+
void setHeight(T view, Dynamic value);
21+
void setResult(T view, @Nullable String value);
22+
void setIn1(T view, @Nullable String value);
23+
void setIn2(T view, @Nullable String value);
24+
void setOperator1(T view, @Nullable String value);
25+
void setK1(T view, float value);
26+
void setK2(T view, float value);
27+
void setK3(T view, float value);
28+
void setK4(T view, float value);
29+
}
3.77 KB
Binary file not shown.
3.9 KB
Binary file not shown.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#import "RNSVGCustomFilter.h"
2+
3+
@interface RNSVGArithmeticFilter : RNSVGCustomFilter {
4+
CIImage *inputImage2;
5+
NSNumber *inputK1;
6+
NSNumber *inputK2;
7+
NSNumber *inputK3;
8+
NSNumber *inputK4;
9+
}
10+
11+
@end

0 commit comments

Comments
 (0)