Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add FeOffset filter #2361

Merged
merged 12 commits into from
Jul 25, 2024
1 change: 1 addition & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,7 @@ Currently supported\* filters are:

- FeColorMatrix
- FeGaussianBlur
- FeOffset

\*_More filters are coming soon_

Expand Down
50 changes: 50 additions & 0 deletions android/src/main/java/com/horcrux/svg/FeOffsetView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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.Dynamic;
import com.facebook.react.bridge.ReactContext;
import java.util.HashMap;

@SuppressLint("ViewConstructor")
class FeOffsetView extends FilterPrimitiveView {
String mIn1;
SVGLength mDx;
SVGLength mDy;

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

public void setIn1(String in1) {
this.mIn1 = in1;
invalidate();
}

public void setDx(Dynamic dx) {
mDx = SVGLength.from(dx);
invalidate();
}

public void setDy(Dynamic dy) {
mDy = SVGLength.from(dy);
invalidate();
}

@Override
public Bitmap applyFilter(HashMap<String, Bitmap> resultsMap, Bitmap prevResult) {
Bitmap source = getSource(resultsMap, prevResult, this.mIn1);
Bitmap result =
Bitmap.createBitmap(prevResult.getWidth(), prevResult.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);

float dx = this.mDx != null ? (float) this.relativeOnWidth(this.mDx) : 0;
float dy = this.mDy != null ? (float) this.relativeOnHeight(this.mDy) : 0;

canvas.drawBitmap(source, dx, dy, new Paint());

return result;
}
}
55 changes: 55 additions & 0 deletions android/src/main/java/com/horcrux/svg/RenderableViewManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeOffsetManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeOffsetManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFilterManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFilterManagerInterface;
import com.facebook.react.viewmanagers.RNSVGForeignObjectManagerDelegate;
Expand Down Expand Up @@ -587,6 +589,7 @@ protected enum SVGClass {
RNSVGFilter,
RNSVGFeColorMatrix,
RNSVGFeGaussianBlur,
RNSVGFeOffset,
RNSVGMarker,
RNSVGForeignObject,
}
Expand Down Expand Up @@ -637,6 +640,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex
return new FeColorMatrixView(reactContext);
case RNSVGFeGaussianBlur:
return new FeGaussianBlurView(reactContext);
case RNSVGFeOffset:
return new FeOffsetView(reactContext);
case RNSVGMarker:
return new MarkerView(reactContext);
case RNSVGForeignObject:
Expand Down Expand Up @@ -1447,6 +1452,56 @@ public void setEdgeMode(FeGaussianBlurView node, String edgeMode) {
}
}

static class FeOffsetManager extends VirtualViewManager<FeOffsetView>
implements RNSVGFeOffsetManagerInterface<FeOffsetView> {
FeOffsetManager() {
super(SVGClass.RNSVGFeOffset);
mDelegate = new RNSVGFeOffsetManagerDelegate(this);
}

public static final String REACT_CLASS = "RNSVGFeOffset";

@ReactProp(name = "x")
public void setX(FeOffsetView node, Dynamic x) {
node.setX(x);
}

@ReactProp(name = "y")
public void setY(FeOffsetView node, Dynamic y) {
node.setY(y);
}

@ReactProp(name = "width")
public void setWidth(FeOffsetView node, Dynamic width) {
node.setWidth(width);
}

@ReactProp(name = "height")
public void setHeight(FeOffsetView node, Dynamic height) {
node.setHeight(height);
}

@ReactProp(name = "result")
public void setResult(FeOffsetView node, String result) {
node.setResult(result);
}

@ReactProp(name = "in1")
public void setIn1(FeOffsetView node, String in1) {
node.setIn1(in1);
}

@ReactProp(name = "dx")
public void setDx(FeOffsetView node, Dynamic dx) {
node.setDx(dx);
}

@ReactProp(name = "dy")
public void setDy(FeOffsetView node, Dynamic dy) {
node.setDy(dy);
}
}

static class ForeignObjectManager extends GroupViewManagerAbstract<ForeignObjectView>
implements RNSVGForeignObjectManagerInterface<ForeignObjectView> {
ForeignObjectManager() {
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 @@ -232,6 +232,15 @@ public NativeModule get() {
return new FeGaussianBlurManager();
}
}));
specs.put(
FeOffsetManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FeOffsetManager();
}
}));
specs.put(
ForeignObjectManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
Expand Down
Original file line number Diff line number Diff line change
@@ -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 RNSVGFeOffsetManagerDelegate<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFeOffsetManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNSVGFeOffsetManagerDelegate(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 "dx":
mViewManager.setDx(view, new DynamicFromObject(value));
break;
case "dy":
mViewManager.setDy(view, new DynamicFromObject(value));
break;
default:
super.setProperty(view, propName, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -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 RNSVGFeOffsetManagerInterface<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 setDx(T view, Dynamic value);
void setDy(T view, Dynamic value);
}
9 changes: 9 additions & 0 deletions apple/Filters/RNSVGFeOffset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import "RNSVGFilterPrimitive.h"

@interface RNSVGFeOffset : RNSVGFilterPrimitive

@property (nonatomic, strong) NSString *in1;
@property (nonatomic, strong) RNSVGLength *dx;
@property (nonatomic, strong) RNSVGLength *dy;

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

@implementation RNSVGFeOffset

#ifdef RCT_NEW_ARCH_ENABLED
using namespace facebook::react;

- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RNSVGFeOffsetProps>();
_props = defaultProps;
}
return self;
}

#pragma mark - RCTComponentViewProtocol

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

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

self.in1 = RCTNSStringFromStringNilIfEmpty(newProps.in1);
id dx = RNSVGConvertFollyDynamicToId(newProps.dx);
if (dx != nil) {
self.dx = [RCTConvert RNSVGLength:dx];
}
id dy = RNSVGConvertFollyDynamicToId(newProps.dy);
if (dy != nil) {
self.dy = [RCTConvert RNSVGLength:dy];
}

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

- (void)prepareForRecycle
{
[super prepareForRecycle];
_in1 = nil;
_dx = nil;
_dy = nil;
}
#endif // RCT_NEW_ARCH_ENABLED

- (void)setIn1:(NSString *)in1
{
if ([in1 isEqualToString:_in1]) {
return;
}

_in1 = in1;
[self invalidate];
}

- (void)setDx:(RNSVGLength *)dx
{
if ([dx isEqualTo:_dx]) {
return;
}

_dx = dx;
[self invalidate];
}

- (void)setDy:(RNSVGLength *)dy
{
if ([dy isEqualTo:_dy]) {
return;
}

_dy = dy;
[self invalidate];
}

- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results
previousFilterResult:(CIImage *)previous
ctm:(CGAffineTransform)ctm
{
CIImage *inResults = self.in1 ? [results objectForKey:self.in1] : nil;
CIImage *inputImage = inResults ? inResults : previous;

if (!inputImage) {
return nil;
}

CIFilter *filter = [CIFilter filterWithName:@"CIAffineTransform"];
[filter setDefaults];
[filter setValue:inputImage forKey:@"inputImage"];

CGFloat dx = [self relativeOnWidth:self.dx];
CGFloat dy = [self relativeOnWidth:self.dy];

// reset ctm translation
CGAffineTransform contextTransform = CGAffineTransformConcat(ctm, CGAffineTransformMakeTranslation(-ctm.tx, -ctm.ty));

CGPoint translate = CGPointMake(dx, dy);
translate = CGPointApplyAffineTransform(translate, contextTransform);
CGAffineTransform transform = CGAffineTransformMakeTranslation(translate.x, translate.y);

[filter setValue:[NSValue valueWithCGAffineTransform:transform] forKey:@"inputTransform"];

return [filter valueForKey:@"outputImage"];
}

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

@end
3 changes: 2 additions & 1 deletion apple/Filters/RNSVGFilter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ - (CIImage *)applyFilter:(CIImage *)img
if ([node isKindOfClass:[RNSVGFilterPrimitive class]]) {
currentFilter = (RNSVGFilterPrimitive *)node;
CGImageRef cgResult = [[RNSVGRenderUtils sharedCIContext] createCGImage:[currentFilter applyFilter:resultsMap
previousFilterResult:result]
previousFilterResult:result
ctm:ctm]
fromRect:[result extent]];
result = [CIImage imageWithCGImage:cgResult];
CGImageRelease(cgResult);
Expand Down
3 changes: 3 additions & 0 deletions apple/Filters/RNSVGFilterPrimitive.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
@property (nonatomic, strong) NSString *result;

- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results previousFilterResult:(CIImage *)previous;
- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results
previousFilterResult:(CIImage *)previous
ctm:(CGAffineTransform)ctm;
- (CIImage *)cropResult:(CIImage *)result;

@end
Loading
Loading