mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
feat: FeBlend (#2489)
# Summary Continuation of #2362 implementing `FeBlend` filter https://www.w3.org/TR/SVG11/filters.html#feBlendElement ## Test Plan Example app → Filters → `FeBlend` ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | macOS | ✅ _*_ | | Android | ✅ | | Web | ✅ |
This commit is contained in:
2
USAGE.md
2
USAGE.md
@@ -1300,6 +1300,7 @@ Filter effects are a way of processing an element’s rendering before it is dis
|
||||
|
||||
The following filters have been implemented:
|
||||
|
||||
- FeBlend
|
||||
- FeColorMatrix
|
||||
- FeFlood
|
||||
- FeGaussianBlur
|
||||
@@ -1308,7 +1309,6 @@ The following filters have been implemented:
|
||||
|
||||
Not supported yet:
|
||||
|
||||
- FeBlend
|
||||
- FeComponentTransfer
|
||||
- FeComposite
|
||||
- FeConvolveMatrix
|
||||
|
||||
61
android/src/main/java/com/horcrux/svg/CustomFilter.java
Normal file
61
android/src/main/java/com/horcrux/svg/CustomFilter.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.horcrux.svg;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
interface CustomFilterFunction {
|
||||
float[] execute(float[] src, float[] dst);
|
||||
}
|
||||
|
||||
public class CustomFilter {
|
||||
public static Bitmap apply(Bitmap srcBmp, Bitmap dstBmp, CustomFilterFunction func) {
|
||||
int width = srcBmp.getWidth();
|
||||
int height = srcBmp.getHeight();
|
||||
int[] srcPixels = new int[width * height];
|
||||
int[] dstPixels = new int[width * height];
|
||||
int[] resPixels = new int[width * height];
|
||||
int srcArgb = 0;
|
||||
float[] src = new float[] {0, 0, 0, 0};
|
||||
int dstArgb = 0;
|
||||
float[] dst = new float[] {0, 0, 0, 0};
|
||||
|
||||
try {
|
||||
srcBmp.getPixels(srcPixels, 0, width, 0, 0, width, height);
|
||||
dstBmp.getPixels(dstPixels, 0, width, 0, 0, width, height);
|
||||
} catch (IllegalArgumentException | ArrayIndexOutOfBoundsException ignored) {
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
srcArgb = srcPixels[y * width + x];
|
||||
src[0] = ((srcArgb >> 24) & 0xff) / 255f;
|
||||
src[1] = ((srcArgb >> 16) & 0xff) / 255f;
|
||||
src[2] = ((srcArgb >> 8) & 0xff) / 255f;
|
||||
src[3] = (srcArgb & 0xff) / 255f;
|
||||
|
||||
dstArgb = dstPixels[y * width + x];
|
||||
dst[0] = ((dstArgb >> 24) & 0xff) / 255f;
|
||||
dst[1] = ((dstArgb >> 16) & 0xff) / 255f;
|
||||
dst[2] = ((dstArgb >> 8) & 0xff) / 255f;
|
||||
dst[3] = (dstArgb & 0xff) / 255f;
|
||||
|
||||
resPixels[y * width + x] = normalizeFromFloats(func.execute(src, dst));
|
||||
}
|
||||
}
|
||||
|
||||
return Bitmap.createBitmap(resPixels, width, height, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
|
||||
public static int normalizeFromFloat(float c) {
|
||||
return Math.min(255, Math.max(0, Math.round(c * 255)));
|
||||
}
|
||||
|
||||
public static int normalizeFromFloats(float[] res) {
|
||||
if (res.length < 4 || normalizeFromFloat(res[0]) <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (normalizeFromFloat(res[0]) << 24)
|
||||
| (normalizeFromFloat(res[1] / res[0]) << 16)
|
||||
| (normalizeFromFloat(res[2] / res[0]) << 8)
|
||||
| normalizeFromFloat(res[3] / res[0]);
|
||||
}
|
||||
}
|
||||
92
android/src/main/java/com/horcrux/svg/FeBlendView.java
Normal file
92
android/src/main/java/com/horcrux/svg/FeBlendView.java
Normal file
@@ -0,0 +1,92 @@
|
||||
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 FeBlendView extends FilterPrimitiveView {
|
||||
String mIn1;
|
||||
String mIn2;
|
||||
FilterProperties.FeBlendMode mMode;
|
||||
|
||||
public FeBlendView(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
super.mFilterSubregion.mX = new SVGLength(0);
|
||||
super.mFilterSubregion.mY = new SVGLength(0);
|
||||
super.mFilterSubregion.mW = new SVGLength("100%");
|
||||
super.mFilterSubregion.mH = new SVGLength("100%");
|
||||
}
|
||||
|
||||
public void setIn1(String in1) {
|
||||
this.mIn1 = in1;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setIn2(String in2) {
|
||||
this.mIn2 = in2;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mMode = FilterProperties.FeBlendMode.getEnum(mode);
|
||||
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);
|
||||
|
||||
if (this.mMode == FilterProperties.FeBlendMode.MULTIPLY) {
|
||||
CustomFilterFunction multiply =
|
||||
(src, dst) -> {
|
||||
float[] res = new float[4];
|
||||
res[0] = 1f - (1f - src[0]) * (1f - dst[0]);
|
||||
res[1] =
|
||||
src[1] * src[0] * (1f - dst[0])
|
||||
+ dst[1] * dst[0] * (1f - src[0])
|
||||
+ src[1] * src[0] * dst[1] * dst[0];
|
||||
res[2] =
|
||||
src[2] * src[0] * (1f - dst[0])
|
||||
+ dst[2] * dst[0] * (1f - src[0])
|
||||
+ src[2] * src[0] * dst[2] * dst[0];
|
||||
res[3] =
|
||||
src[3] * src[0] * (1f - dst[0])
|
||||
+ dst[3] * dst[0] * (1f - src[0])
|
||||
+ src[3] * src[0] * dst[3] * dst[0];
|
||||
return res;
|
||||
};
|
||||
return CustomFilter.apply(in1, in2, multiply);
|
||||
}
|
||||
|
||||
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.mMode) {
|
||||
case NORMAL -> {
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
|
||||
}
|
||||
case SCREEN -> {
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SCREEN));
|
||||
}
|
||||
case LIGHTEN -> {
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));
|
||||
}
|
||||
case DARKEN -> {
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
|
||||
}
|
||||
case MULTIPLY -> {}
|
||||
}
|
||||
canvas.drawBitmap(in2, 0, 0, paint);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,43 @@ class FilterProperties {
|
||||
}
|
||||
}
|
||||
|
||||
enum FeBlendMode {
|
||||
UNKNOWN("unknown"),
|
||||
NORMAL("normal"),
|
||||
MULTIPLY("multiply"),
|
||||
SCREEN("screen"),
|
||||
DARKEN("darken"),
|
||||
LIGHTEN("lighten"),
|
||||
;
|
||||
|
||||
private final String mode;
|
||||
|
||||
FeBlendMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
static FeBlendMode getEnum(String strVal) {
|
||||
if (!typeToEnum.containsKey(strVal)) {
|
||||
throw new IllegalArgumentException("Unknown String Value: " + strVal);
|
||||
}
|
||||
return typeToEnum.get(strVal);
|
||||
}
|
||||
|
||||
private static final Map<String, FeBlendMode> typeToEnum = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (final FeBlendMode en : FeBlendMode.values()) {
|
||||
typeToEnum.put(en.mode, en);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String toString() {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
enum FeColorMatrixType {
|
||||
MATRIX("matrix"),
|
||||
SATURATE("saturate"),
|
||||
|
||||
@@ -103,6 +103,8 @@ import com.facebook.react.viewmanagers.RNSVGDefsManagerDelegate;
|
||||
import com.facebook.react.viewmanagers.RNSVGDefsManagerInterface;
|
||||
import com.facebook.react.viewmanagers.RNSVGEllipseManagerDelegate;
|
||||
import com.facebook.react.viewmanagers.RNSVGEllipseManagerInterface;
|
||||
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.RNSVGFeFloodManagerDelegate;
|
||||
@@ -591,6 +593,7 @@ class VirtualViewManager<V extends VirtualView> extends ViewGroupManager<Virtual
|
||||
RNSVGPattern,
|
||||
RNSVGMask,
|
||||
RNSVGFilter,
|
||||
RNSVGFeBlend,
|
||||
RNSVGFeColorMatrix,
|
||||
RNSVGFeFlood,
|
||||
RNSVGFeGaussianBlur,
|
||||
@@ -642,6 +645,8 @@ class VirtualViewManager<V extends VirtualView> extends ViewGroupManager<Virtual
|
||||
return new MaskView(reactContext);
|
||||
case RNSVGFilter:
|
||||
return new FilterView(reactContext);
|
||||
case RNSVGFeBlend:
|
||||
return new FeBlendView(reactContext);
|
||||
case RNSVGFeColorMatrix:
|
||||
return new FeColorMatrixView(reactContext);
|
||||
case RNSVGFeFlood:
|
||||
@@ -1585,6 +1590,31 @@ class RenderableViewManager<T extends RenderableView> extends VirtualViewManager
|
||||
}
|
||||
}
|
||||
|
||||
static class FeBlendManager extends FilterPrimitiveManager<FeBlendView>
|
||||
implements RNSVGFeBlendManagerInterface<FeBlendView> {
|
||||
FeBlendManager() {
|
||||
super(SVGClass.RNSVGFeBlend);
|
||||
mDelegate = new RNSVGFeBlendManagerDelegate(this);
|
||||
}
|
||||
|
||||
public static final String REACT_CLASS = "RNSVGFeBlend";
|
||||
|
||||
@ReactProp(name = "in1")
|
||||
public void setIn1(FeBlendView node, String in1) {
|
||||
node.setIn1(in1);
|
||||
}
|
||||
|
||||
@ReactProp(name = "in2")
|
||||
public void setIn2(FeBlendView node, String in2) {
|
||||
node.setIn2(in2);
|
||||
}
|
||||
|
||||
@ReactProp(name = "mode")
|
||||
public void setMode(FeBlendView node, String mode) {
|
||||
node.setMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
static class FeColorMatrixManager extends FilterPrimitiveManager<FeColorMatrixView>
|
||||
implements RNSVGFeColorMatrixManagerInterface<FeColorMatrixView> {
|
||||
FeColorMatrixManager() {
|
||||
|
||||
@@ -214,6 +214,15 @@ public class SvgPackage extends TurboReactPackage implements ViewManagerOnDemand
|
||||
return new FilterManager();
|
||||
}
|
||||
}));
|
||||
specs.put(
|
||||
FeBlendManager.REACT_CLASS,
|
||||
ModuleSpec.viewManagerSpec(
|
||||
new Provider<NativeModule>() {
|
||||
@Override
|
||||
public NativeModule get() {
|
||||
return new FeBlendManager();
|
||||
}
|
||||
}));
|
||||
specs.put(
|
||||
FeColorMatrixManager.REACT_CLASS,
|
||||
ModuleSpec.viewManagerSpec(
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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 RNSVGFeBlendManagerDelegate<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFeBlendManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
|
||||
public RNSVGFeBlendManagerDelegate(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 "mode":
|
||||
mViewManager.setMode(view, (String) value);
|
||||
break;
|
||||
default:
|
||||
super.setProperty(view, propName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 RNSVGFeBlendManagerInterface<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 setMode(T view, @Nullable String value);
|
||||
}
|
||||
8
apple/Filters/RNSVGBlendMode.h
Normal file
8
apple/Filters/RNSVGBlendMode.h
Normal file
@@ -0,0 +1,8 @@
|
||||
typedef CF_ENUM(int32_t, RNSVGBlendMode) {
|
||||
SVG_FEBLEND_MODE_UNKNOWN,
|
||||
SVG_FEBLEND_MODE_NORMAL,
|
||||
SVG_FEBLEND_MODE_MULTIPLY,
|
||||
SVG_FEBLEND_MODE_SCREEN,
|
||||
SVG_FEBLEND_MODE_DARKEN,
|
||||
SVG_FEBLEND_MODE_LIGHTEN,
|
||||
};
|
||||
10
apple/Filters/RNSVGFeBlend.h
Normal file
10
apple/Filters/RNSVGFeBlend.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#import "RNSVGBlendMode.h"
|
||||
#import "RNSVGFilterPrimitive.h"
|
||||
|
||||
@interface RNSVGFeBlend : RNSVGFilterPrimitive
|
||||
|
||||
@property (nonatomic, strong) NSString *in1;
|
||||
@property (nonatomic, strong) NSString *in2;
|
||||
@property (nonatomic, assign) RNSVGBlendMode mode;
|
||||
|
||||
@end
|
||||
132
apple/Filters/RNSVGFeBlend.mm
Normal file
132
apple/Filters/RNSVGFeBlend.mm
Normal file
@@ -0,0 +1,132 @@
|
||||
#import "RNSVGFeBlend.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
|
||||
|
||||
@implementation RNSVGFeBlend
|
||||
|
||||
#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 RNSVGFeBlendProps>();
|
||||
_props = defaultProps;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - RCTComponentViewProtocol
|
||||
|
||||
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
||||
{
|
||||
return concreteComponentDescriptorProvider<RNSVGFeBlendComponentDescriptor>();
|
||||
}
|
||||
|
||||
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
|
||||
{
|
||||
const auto &newProps = static_cast<const RNSVGFeBlendProps &>(*props);
|
||||
|
||||
self.in1 = RCTNSStringFromStringNilIfEmpty(newProps.in1);
|
||||
self.in2 = RCTNSStringFromStringNilIfEmpty(newProps.in2);
|
||||
self.mode = [RNSVGConvert RNSVGBlendModeFromCppEquivalent:newProps.mode];
|
||||
|
||||
setCommonFilterProps(newProps, self);
|
||||
_props = std::static_pointer_cast<RNSVGFeBlendProps const>(props);
|
||||
}
|
||||
|
||||
- (void)prepareForRecycle
|
||||
{
|
||||
[super prepareForRecycle];
|
||||
_in1 = nil;
|
||||
_in2 = nil;
|
||||
_mode = RNSVGBlendMode::SVG_FEBLEND_MODE_NORMAL;
|
||||
}
|
||||
#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)setMode:(RNSVGBlendMode)mode
|
||||
{
|
||||
if (mode == _mode) {
|
||||
return;
|
||||
}
|
||||
_mode = mode;
|
||||
[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.mode) {
|
||||
case SVG_FEBLEND_MODE_NORMAL:
|
||||
filter = [CIFilter filterWithName:@"CISourceOverCompositing"];
|
||||
break;
|
||||
case SVG_FEBLEND_MODE_MULTIPLY:
|
||||
filter = [CIFilter filterWithName:@"CIMultiplyBlendMode"];
|
||||
break;
|
||||
case SVG_FEBLEND_MODE_SCREEN:
|
||||
filter = [CIFilter filterWithName:@"CIScreenBlendMode"];
|
||||
break;
|
||||
case SVG_FEBLEND_MODE_DARKEN:
|
||||
filter = [CIFilter filterWithName:@"CIDarkenBlendMode"];
|
||||
break;
|
||||
case SVG_FEBLEND_MODE_LIGHTEN:
|
||||
filter = [CIFilter filterWithName:@"CILightenBlendMode"];
|
||||
break;
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
|
||||
[filter setDefaults];
|
||||
[filter setValue:inputImage1 forKey:@"inputImage"];
|
||||
[filter setValue:inputImage2 forKey:@"inputBackgroundImage"];
|
||||
|
||||
return [filter valueForKey:@"outputImage"];
|
||||
}
|
||||
|
||||
#ifdef RCT_NEW_ARCH_ENABLED
|
||||
Class<RCTComponentViewProtocol> RNSVGFeBlendCls(void)
|
||||
{
|
||||
return RNSVGFeBlend.class;
|
||||
}
|
||||
#endif // RCT_NEW_ARCH_ENABLED
|
||||
|
||||
@end
|
||||
@@ -10,6 +10,7 @@
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import "RCTConvert+RNSVG.h"
|
||||
#import "RNSVGBlendMode.h"
|
||||
#import "RNSVGCGFCRule.h"
|
||||
#import "RNSVGColorMatrixType.h"
|
||||
#import "RNSVGEdgeMode.h"
|
||||
|
||||
@@ -72,6 +72,19 @@ RCT_ENUM_CONVERTER(
|
||||
SVG_FECOLORMATRIX_TYPE_UNKNOWN,
|
||||
intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(
|
||||
RNSVGBlendMode,
|
||||
(@{
|
||||
@"unknown" : @(SVG_FEBLEND_MODE_UNKNOWN),
|
||||
@"normal" : @(SVG_FEBLEND_MODE_NORMAL),
|
||||
@"multiply" : @(SVG_FEBLEND_MODE_MULTIPLY),
|
||||
@"screen" : @(SVG_FEBLEND_MODE_SCREEN),
|
||||
@"darken" : @(SVG_FEBLEND_MODE_DARKEN),
|
||||
@"lighten" : @(SVG_FEBLEND_MODE_LIGHTEN),
|
||||
}),
|
||||
SVG_FEBLEND_MODE_UNKNOWN,
|
||||
intValue)
|
||||
|
||||
+ (RNSVGBrush *)RNSVGBrush:(id)json
|
||||
{
|
||||
if ([json isKindOfClass:[NSNumber class]]) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#ifdef RCT_NEW_ARCH_ENABLED
|
||||
#import <react/renderer/components/rnsvg/Props.h>
|
||||
#import "RNSVGBlendMode.h"
|
||||
#import "RNSVGColorMatrixType.h"
|
||||
#import "RNSVGEdgeMode.h"
|
||||
#import "RNSVGUnits.h"
|
||||
@@ -10,6 +11,7 @@ namespace react = facebook::react;
|
||||
|
||||
+ (RNSVGUnits)RNSVGUnitsFromFilterUnitsCppEquivalent:(react::RNSVGFilterFilterUnits)svgUnits;
|
||||
+ (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimitiveUnits)svgUnits;
|
||||
+ (RNSVGBlendMode)RNSVGBlendModeFromCppEquivalent:(react::RNSVGFeBlendMode)mode;
|
||||
+ (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type;
|
||||
+ (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeMode)edgeMode;
|
||||
|
||||
|
||||
@@ -23,6 +23,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ (RNSVGBlendMode)RNSVGBlendModeFromCppEquivalent:(react::RNSVGFeBlendMode)mode
|
||||
{
|
||||
switch (mode) {
|
||||
case react::RNSVGFeBlendMode::Unknown:
|
||||
return SVG_FEBLEND_MODE_UNKNOWN;
|
||||
case react::RNSVGFeBlendMode::Normal:
|
||||
return SVG_FEBLEND_MODE_NORMAL;
|
||||
case react::RNSVGFeBlendMode::Multiply:
|
||||
return SVG_FEBLEND_MODE_MULTIPLY;
|
||||
case react::RNSVGFeBlendMode::Screen:
|
||||
return SVG_FEBLEND_MODE_SCREEN;
|
||||
case react::RNSVGFeBlendMode::Darken:
|
||||
return SVG_FEBLEND_MODE_DARKEN;
|
||||
case react::RNSVGFeBlendMode::Lighten:
|
||||
return SVG_FEBLEND_MODE_LIGHTEN;
|
||||
}
|
||||
}
|
||||
|
||||
+ (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type
|
||||
{
|
||||
switch (type) {
|
||||
|
||||
5
apple/ViewManagers/RNSVGFeBlendManager.h
Normal file
5
apple/ViewManagers/RNSVGFeBlendManager.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#import "RNSVGFilterPrimitiveManager.h"
|
||||
|
||||
@interface RNSVGFeBlendManager : RNSVGFilterPrimitiveManager
|
||||
|
||||
@end
|
||||
18
apple/ViewManagers/RNSVGFeBlendManager.mm
Normal file
18
apple/ViewManagers/RNSVGFeBlendManager.mm
Normal file
@@ -0,0 +1,18 @@
|
||||
#import "RNSVGFeBlendManager.h"
|
||||
#import "RNSVGBlendMode.h"
|
||||
#import "RNSVGFeBlend.h"
|
||||
|
||||
@implementation RNSVGFeBlendManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (RNSVGFeBlend *)node
|
||||
{
|
||||
return [RNSVGFeBlend new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(in1, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(in2, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(mode, RNSVGBlendMode)
|
||||
|
||||
@end
|
||||
155
apps/examples/src/examples/Filters/FeBlend.tsx
Normal file
155
apps/examples/src/examples/Filters/FeBlend.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React, {Component} from 'react';
|
||||
import {
|
||||
Circle,
|
||||
FeBlend,
|
||||
FeFlood,
|
||||
Filter,
|
||||
G,
|
||||
Image,
|
||||
LinearGradient,
|
||||
Rect,
|
||||
Stop,
|
||||
Svg,
|
||||
Text,
|
||||
} from 'react-native-svg';
|
||||
|
||||
class W3Blend extends Component {
|
||||
static title = 'W3 FeBlend example';
|
||||
render() {
|
||||
return (
|
||||
<Svg width="350" height="350" viewBox="0 0 500 500">
|
||||
<LinearGradient
|
||||
id="MyGradient"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="100"
|
||||
y1="0"
|
||||
x2="300"
|
||||
y2="0">
|
||||
<Stop offset="0" stopColor="#000000" />
|
||||
<Stop offset="0.33" stopColor="#ffffff" />
|
||||
<Stop offset="0.67" stopColor="#ff0000" />
|
||||
<Stop offset="1" stopColor="#808080" />
|
||||
</LinearGradient>
|
||||
<Filter id="Normal">
|
||||
<FeBlend mode="normal" in2="BackgroundImage" in="SourceGraphic" />
|
||||
</Filter>
|
||||
<Filter id="Multiply">
|
||||
<FeBlend mode="multiply" in2="BackgroundImage" in="SourceGraphic" />
|
||||
</Filter>
|
||||
<Filter id="Screen">
|
||||
<FeBlend mode="screen" in2="BackgroundImage" in="SourceGraphic" />
|
||||
</Filter>
|
||||
<Filter id="Darken">
|
||||
<FeBlend mode="darken" in2="BackgroundImage" in="SourceGraphic" />
|
||||
</Filter>
|
||||
<Filter id="Lighten">
|
||||
<FeBlend mode="lighten" in2="BackgroundImage" in="SourceGraphic" />
|
||||
</Filter>
|
||||
|
||||
<Rect fill="none" stroke="blue" x="1" y="1" width="498" height="498" />
|
||||
<G enable-background="new">
|
||||
<Rect
|
||||
x="100"
|
||||
y="20"
|
||||
width="300"
|
||||
height="460"
|
||||
fill="url(#MyGradient)"
|
||||
/>
|
||||
<G fontFamily="Verdana" fontSize="75" fill="#888888" fillOpacity=".6">
|
||||
<Text x="50" y="90" filter="url(#Normal)">
|
||||
Normal
|
||||
</Text>
|
||||
<Text x="50" y="180" filter="url(#Multiply)">
|
||||
Multiply
|
||||
</Text>
|
||||
<Text x="50" y="270" filter="url(#Screen)">
|
||||
Screen
|
||||
</Text>
|
||||
<Text x="50" y="360" filter="url(#Darken)">
|
||||
Darken
|
||||
</Text>
|
||||
<Text x="50" y="450" filter="url(#Lighten)">
|
||||
Lighten
|
||||
</Text>
|
||||
</G>
|
||||
</G>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleExample extends Component {
|
||||
static title = 'MDN example';
|
||||
render() {
|
||||
return (
|
||||
<Svg width="200" height="200">
|
||||
<Filter id="spotlight">
|
||||
<FeFlood
|
||||
result="floodFill"
|
||||
x="0%"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
floodColor="green"
|
||||
floodOpacity="1"
|
||||
/>
|
||||
<FeBlend in="SourceGraphic" in2="floodFill" mode="multiply" />
|
||||
</Filter>
|
||||
|
||||
<Circle cx="50%" cy="50%" r="40%" fill="red" filter="url(#spotlight)" />
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MDNExample extends Component {
|
||||
static title = 'MDN example';
|
||||
render() {
|
||||
return (
|
||||
<Svg width="200" height="200">
|
||||
<Filter id="spotlight">
|
||||
<FeFlood
|
||||
result="floodFill"
|
||||
x="0"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
floodColor="green"
|
||||
floodOpacity="1"
|
||||
/>
|
||||
<FeBlend in="SourceGraphic" in2="floodFill" mode="multiply" />
|
||||
</Filter>
|
||||
<Image
|
||||
href="https://live.mdnplay.dev/en-US/docs/Web/SVG/Element/feBlend/mdn_logo_only_color.png"
|
||||
x="10%"
|
||||
y="10%"
|
||||
width="80%"
|
||||
height="80%"
|
||||
filter="url(#spotlight)"
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const icon = (
|
||||
<Svg width="30" height="30">
|
||||
<Filter id="blendIcon">
|
||||
<FeFlood
|
||||
result="floodRes"
|
||||
x="0%"
|
||||
y="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
floodColor="green"
|
||||
floodOpacity="1"
|
||||
/>
|
||||
<FeBlend in="SourceGraphic" in2="floodRes" mode="multiply" />
|
||||
</Filter>
|
||||
|
||||
<Circle cx="50%" cy="50%" r="40%" fill="red" filter="url(#blendIcon)" />
|
||||
</Svg>
|
||||
);
|
||||
|
||||
const samples = [W3Blend, SimpleExample, MDNExample];
|
||||
export {icon, samples};
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as FeBlend from './FeBlend';
|
||||
import * as FeColorMatrix from './FeColorMatrix';
|
||||
import * as FeFlood from './FeFlood';
|
||||
import * as FeGaussianBlur from './FeGaussianBlur';
|
||||
@@ -5,6 +6,7 @@ import * as FeMerge from './FeMerge';
|
||||
import * as FeOffset from './FeOffset';
|
||||
import * as ReanimatedFeColorMatrix from './ReanimatedFeColorMatrix';
|
||||
export {
|
||||
FeBlend,
|
||||
FeColorMatrix,
|
||||
FeFlood,
|
||||
FeGaussianBlur,
|
||||
|
||||
@@ -17,6 +17,7 @@ module.exports = {
|
||||
'RNSVGCircleComponentDescriptor',
|
||||
'RNSVGClipPathComponentDescriptor',
|
||||
'RNSVGDefsComponentDescriptor',
|
||||
'RNSVGFeBlendComponentDescriptor',
|
||||
'RNSVGFeColorMatrixComponentDescriptor',
|
||||
'RNSVGFeFloodComponentDescriptor',
|
||||
'RNSVGFeGaussianBlurComponentDescriptor',
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { warnUnimplementedFilter } from '../../lib/util';
|
||||
import { NativeMethods } from 'react-native';
|
||||
import {
|
||||
extractFeBlend,
|
||||
extractFilter,
|
||||
extractIn,
|
||||
} from '../../lib/extract/extractFilter';
|
||||
import RNSVGFeBlend from '../../fabric/FeBlendNativeComponent';
|
||||
import FilterPrimitive from './FilterPrimitive';
|
||||
|
||||
type BlendMode = 'normal' | 'multiply' | 'screen' | 'darken' | 'lighten';
|
||||
@@ -17,7 +23,13 @@ export default class FeBlend extends FilterPrimitive<FeBlendProps> {
|
||||
};
|
||||
|
||||
render() {
|
||||
warnUnimplementedFilter();
|
||||
return null;
|
||||
return (
|
||||
<RNSVGFeBlend
|
||||
ref={(ref) => this.refMethod(ref as (FeBlend & NativeMethods) | null)}
|
||||
{...extractFilter(this.props)}
|
||||
{...extractIn(this.props)}
|
||||
{...extractFeBlend(this.props)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
29
src/fabric/FeBlendNativeComponent.ts
Normal file
29
src/fabric/FeBlendNativeComponent.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
||||
import { NumberProp } from '../lib/extract/types';
|
||||
import type { UnsafeMixed } from './codegenUtils';
|
||||
import type { ViewProps } from './utils';
|
||||
import { WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
|
||||
|
||||
interface FilterPrimitiveCommonProps {
|
||||
x?: UnsafeMixed<NumberProp>;
|
||||
y?: UnsafeMixed<NumberProp>;
|
||||
width?: UnsafeMixed<NumberProp>;
|
||||
height?: UnsafeMixed<NumberProp>;
|
||||
result?: string;
|
||||
}
|
||||
|
||||
type BlendMode =
|
||||
| 'unknown'
|
||||
| 'normal'
|
||||
| 'multiply'
|
||||
| 'screen'
|
||||
| 'darken'
|
||||
| 'lighten';
|
||||
|
||||
export interface NativeProps extends ViewProps, FilterPrimitiveCommonProps {
|
||||
in1?: string;
|
||||
in2?: string;
|
||||
mode?: WithDefault<BlendMode, 'normal'>;
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<NativeProps>('RNSVGFeBlend');
|
||||
@@ -21,6 +21,7 @@ import RNSVGTextPath from './TextPathNativeComponent';
|
||||
import RNSVGTSpan from './TSpanNativeComponent';
|
||||
import RNSVGUse from './UseNativeComponent';
|
||||
import RNSVGFilter from './FilterNativeComponent';
|
||||
import RNSVGFeBlend from './FeBlendNativeComponent';
|
||||
import RNSVGFeColorMatrix from './FeColorMatrixNativeComponent';
|
||||
import RNSVGFeFlood from './FeFloodNativeComponent';
|
||||
import RNSVGFeGaussianBlur from './FeGaussianBlurNativeComponent';
|
||||
@@ -51,6 +52,7 @@ export {
|
||||
RNSVGTSpan,
|
||||
RNSVGUse,
|
||||
RNSVGFilter,
|
||||
RNSVGFeBlend,
|
||||
RNSVGFeColorMatrix,
|
||||
RNSVGFeFlood,
|
||||
RNSVGFeGaussianBlur,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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 { 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 FeFloodNativeProps } from '../../fabric/FeFloodNativeComponent';
|
||||
import { NativeProps as FeGaussianBlurNativeProps } from '../../fabric/FeGaussianBlurNativeComponent';
|
||||
@@ -44,6 +46,21 @@ export const extractIn = (props: { in?: string }) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const extractFeBlend = (
|
||||
props: FeBlendComponentProps
|
||||
): FeBlendNativeProps => {
|
||||
const extracted: FeBlendNativeProps = {};
|
||||
|
||||
if (props.in2) {
|
||||
extracted.in2 = props.in2;
|
||||
}
|
||||
if (props.mode) {
|
||||
extracted.mode = props.mode;
|
||||
}
|
||||
|
||||
return extracted;
|
||||
};
|
||||
|
||||
export const extractFeColorMatrix = (
|
||||
props: FeColorMatrixComponentProps
|
||||
): FeColorMatrixNativeProps => {
|
||||
|
||||
Reference in New Issue
Block a user