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:
Jakub Grzywacz
2024-10-16 11:45:44 +02:00
committed by GitHub
parent cb30bd66d5
commit 096fdc22a5
24 changed files with 736 additions and 4 deletions

View File

@@ -1300,6 +1300,7 @@ Filter effects are a way of processing an elements 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

View 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]);
}
}

View 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;
}
}

View File

@@ -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"),

View File

@@ -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() {

View File

@@ -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(

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View 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,
};

View 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

View 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

View File

@@ -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"

View File

@@ -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]]) {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -0,0 +1,5 @@
#import "RNSVGFilterPrimitiveManager.h"
@interface RNSVGFeBlendManager : RNSVGFilterPrimitiveManager
@end

View 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

View 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};

View File

@@ -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,

View File

@@ -17,6 +17,7 @@ module.exports = {
'RNSVGCircleComponentDescriptor',
'RNSVGClipPathComponentDescriptor',
'RNSVGDefsComponentDescriptor',
'RNSVGFeBlendComponentDescriptor',
'RNSVGFeColorMatrixComponentDescriptor',
'RNSVGFeFloodComponentDescriptor',
'RNSVGFeGaussianBlurComponentDescriptor',

View File

@@ -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)}
/>
);
}
}

View 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');

View File

@@ -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,

View File

@@ -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 => {