Skip to content

Commit

Permalink
feat: FeFlood (#2487)
Browse files Browse the repository at this point in the history
# Summary

Continuation of #2362 implementing `FeFlood` filter
https://www.w3.org/TR/SVG11/filters.html#feFloodElement

## Test Plan

Example app → Filters → `FeFlood`

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |    ✅      |
| macOS   |    ✅ _*_      |
| Android |    ✅      |
| Web     |    ✅      |

_* `canvasWidth/canvasHeight` is incorrect on macOS, so there might be
some problems_
  • Loading branch information
jakex7 authored Oct 15, 2024
1 parent 8fed774 commit ba54b15
Show file tree
Hide file tree
Showing 17 changed files with 581 additions and 6 deletions.
2 changes: 1 addition & 1 deletion USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,7 @@ Filter effects are a way of processing an element’s rendering before it is dis
The following filters have been implemented:

- FeColorMatrix
- FeFlood
- FeGaussianBlur
- FeMerge
- FeOffset
Expand All @@ -1314,7 +1315,6 @@ Not supported yet:
- FeDiffuseLighting
- FeDisplacementMap
- FeDropShadow
- FeFlood
- FeFuncA
- FeFuncB
- FeFuncG
Expand Down
130 changes: 130 additions & 0 deletions android/src/main/java/com/horcrux/svg/FeFloodView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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.ColorPropConverter;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;

@SuppressLint("ViewConstructor")
class FeFloodView extends FilterPrimitiveView {
private static final Pattern regex = Pattern.compile("[0-9.-]+");

public @Nullable ReadableArray floodColor;
public float floodOpacity = 1;

public FeFloodView(ReactContext reactContext) {
super(reactContext);
}

public void setFloodColor(@Nullable Dynamic color) {
if (color == null || color.isNull()) {
floodColor = null;
invalidate();
return;
}

ReadableType strokeType = color.getType();
if (strokeType.equals(ReadableType.Map)) {
ReadableMap colorMap = color.asMap();
setFloodColor(colorMap);
return;
}

// This code will probably never be reached with current changes
ReadableType type = color.getType();
if (type.equals(ReadableType.Number)) {
floodColor = JavaOnlyArray.of(0, color.asInt());
} else if (type.equals(ReadableType.Array)) {
floodColor = color.asArray();
} else {
JavaOnlyArray arr = new JavaOnlyArray();
arr.pushInt(0);
Matcher m = regex.matcher(color.asString());
int i = 0;
while (m.find()) {
double parsed = Double.parseDouble(m.group());
arr.pushDouble(i++ < 3 ? parsed / 255 : parsed);
}
floodColor = arr;
}
invalidate();
}

public void setFloodColor(@Nullable ReadableMap color) {
if (color == null) {
this.floodColor = null;
invalidate();
return;
}
int type = color.getInt("type");
if (type == 0) {
ReadableType payloadType = color.getType("payload");
if (payloadType.equals(ReadableType.Number)) {
this.floodColor = JavaOnlyArray.of(0, color.getInt("payload"));
} else if (payloadType.equals(ReadableType.Map)) {
this.floodColor = JavaOnlyArray.of(0, color.getMap("payload"));
}
} else if (type == 1) {
this.floodColor = JavaOnlyArray.of(1, color.getString("brushRef"));
} else {
this.floodColor = JavaOnlyArray.of(type);
}
invalidate();
}

public void setFloodOpacity(float opacity) {
this.floodOpacity = opacity;
invalidate();
}

@Override
public Bitmap applyFilter(HashMap<String, Bitmap> resultsMap, Bitmap prevResult) {
Bitmap floodBitmap =
Bitmap.createBitmap(prevResult.getWidth(), prevResult.getHeight(), Bitmap.Config.ARGB_8888);
Canvas floodCanvas = new Canvas(floodBitmap);
Paint paint = new Paint();
paint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
paint.setStyle(Paint.Style.FILL);
this.setupPaint(paint, this.floodOpacity, this.floodColor);
floodCanvas.drawPaint(paint);
return floodBitmap;
}

private void setupPaint(Paint paint, float opacity, @Nullable ReadableArray colors) {
int colorType = colors.getInt(0);
switch (colorType) {
case 0:
if (colors.size() == 2) {
int color;
if (colors.getType(1) == ReadableType.Map) {
color = ColorPropConverter.getColor(colors.getMap(1), getContext());
} else {
color = colors.getInt(1);
}
int alpha = color >>> 24;
int combined = Math.round((float) alpha * opacity);
paint.setColor(combined << 24 | (color & 0x00ffffff));
} else {
// solid color
paint.setARGB(
(int) (colors.size() > 4 ? colors.getDouble(4) * opacity * 255 : opacity * 255),
(int) (colors.getDouble(1) * 255),
(int) (colors.getDouble(2) * 255),
(int) (colors.getDouble(3) * 255));
}
break;
// TODO: handle currentColor
}
}
}
29 changes: 29 additions & 0 deletions android/src/main/java/com/horcrux/svg/RenderableViewManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
import com.facebook.react.viewmanagers.RNSVGEllipseManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeFloodManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeFloodManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeMergeManagerDelegate;
Expand Down Expand Up @@ -590,6 +592,7 @@ protected enum SVGClass {
RNSVGMask,
RNSVGFilter,
RNSVGFeColorMatrix,
RNSVGFeFlood,
RNSVGFeGaussianBlur,
RNSVGFeMerge,
RNSVGFeOffset,
Expand Down Expand Up @@ -641,6 +644,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex
return new FilterView(reactContext);
case RNSVGFeColorMatrix:
return new FeColorMatrixView(reactContext);
case RNSVGFeFlood:
return new FeFloodView(reactContext);
case RNSVGFeGaussianBlur:
return new FeGaussianBlurView(reactContext);
case RNSVGFeMerge:
Expand Down Expand Up @@ -1605,6 +1610,30 @@ public void setValues(FeColorMatrixView node, @Nullable ReadableArray values) {
}
}

static class FeFloodManager extends FilterPrimitiveManager<FeFloodView>
implements RNSVGFeFloodManagerInterface<FeFloodView> {
FeFloodManager() {
super(SVGClass.RNSVGFeFlood);
mDelegate = new RNSVGFeFloodManagerDelegate(this);
}

public static final String REACT_CLASS = "RNSVGFeFlood";

@ReactProp(name = "floodColor")
public void setFloodColor(FeFloodView node, @Nullable Dynamic strokeColors) {
node.setFloodColor(strokeColors);
}

public void setFloodColor(FeFloodView view, @Nullable ReadableMap value) {
view.setFloodColor(value);
}

@ReactProp(name = "floodOpacity", defaultFloat = 1f)
public void setFloodOpacity(FeFloodView node, float strokeOpacity) {
node.setFloodOpacity(strokeOpacity);
}
}

static class FeGaussianBlurManager extends FilterPrimitiveManager<FeGaussianBlurView>
implements RNSVGFeGaussianBlurManagerInterface<FeGaussianBlurView> {
FeGaussianBlurManager() {
Expand Down
9 changes: 9 additions & 0 deletions android/src/main/java/com/horcrux/svg/SvgPackage.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ public NativeModule get() {
return new FeColorMatrixManager();
}
}));
specs.put(
FeFloodManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FeFloodManager();
}
}));
specs.put(
FeGaussianBlurManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* 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 RNSVGFeFloodManagerDelegate<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFeFloodManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNSVGFeFloodManagerDelegate(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 "floodColor":
mViewManager.setFloodColor(view, new DynamicFromObject(value));
break;
case "floodOpacity":
mViewManager.setFloodOpacity(view, value == null ? 1f : ((Double) value).floatValue());
break;
default:
super.setProperty(view, propName, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

public interface RNSVGFeFloodManagerInterface<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 setFloodColor(T view, Dynamic value);
void setFloodOpacity(T view, float value);
}
9 changes: 9 additions & 0 deletions apple/Filters/RNSVGFeFlood.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import "RNSVGBrush.h"
#import "RNSVGFilterPrimitive.h"

@interface RNSVGFeFlood : RNSVGFilterPrimitive

@property (nonatomic, strong) RNSVGBrush *floodColor;
@property (nonatomic, assign) CGFloat floodOpacity;

@end
91 changes: 91 additions & 0 deletions apple/Filters/RNSVGFeFlood.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#import "RNSVGFeFlood.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 RNSVGFeFlood

#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 RNSVGFeFloodProps>();
_props = defaultProps;
}
return self;
}

#pragma mark - RCTComponentViewProtocol

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNSVGFeFloodComponentDescriptor>();
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = static_cast<const RNSVGFeFloodProps &>(*props);

id floodColor = RNSVGConvertFollyDynamicToId(newProps.floodColor);
if (floodColor != nil) {
self.floodColor = [RCTConvert RNSVGBrush:floodColor];
}
self.floodOpacity = newProps.floodOpacity;

setCommonFilterProps(newProps, self);
_props = std::static_pointer_cast<RNSVGFeFloodProps const>(props);
}

- (void)prepareForRecycle
{
[super prepareForRecycle];
_floodColor = nil;
_floodOpacity = 1;
}
#endif // RCT_NEW_ARCH_ENABLED

- (void)setFloodColor:(RNSVGBrush *)floodColor
{
if (floodColor == _floodColor) {
return;
}
_floodColor = floodColor;
[self invalidate];
}

- (void)setFloodOpacity:(CGFloat)floodOpacity
{
if (floodOpacity == _floodOpacity) {
return;
}
_floodOpacity = floodOpacity;
[self invalidate];
}

- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results previousFilterResult:(CIImage *)previous
{
return [CIImage imageWithColor:[CIColor colorWithCGColor:[self.floodColor getColorWithOpacity:self.floodOpacity]]];
}

#ifdef RCT_NEW_ARCH_ENABLED
Class<RCTComponentViewProtocol> RNSVGFeFloodCls(void)
{
return RNSVGFeFlood.class;
}
#endif // RCT_NEW_ARCH_ENABLED

@end
Loading

0 comments on commit ba54b15

Please sign in to comment.