Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,7 @@ The following filters have been implemented:
- FeFlood
- FeGaussianBlur
- FeMerge
- FeMorphology
- FeOffset

Not supported yet:
Expand All @@ -1320,7 +1321,6 @@ Not supported yet:
- FeFuncG
- FeFuncR
- FeImage
- FeMorphology
- FePointLight
- FeSpecularLighting
- FeSpotLight
Expand Down
159 changes: 159 additions & 0 deletions android/src/main/java/com/horcrux/svg/FeMorphologyView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package com.horcrux.svg;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Color;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import java.util.ArrayList;
import java.util.HashMap;

@SuppressLint("ViewConstructor")
class FeMorphologyView extends FilterPrimitiveView {
String mIn1;
FilterProperties.FeMorphologyOperator mOperator;
ArrayList<SVGLength> mRadius;

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

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

public void setOperator1(String operator1) {
this.mOperator = FilterProperties.FeMorphologyOperator.getEnum(operator1);
invalidate();
}

public void setRadius(Dynamic radius) {
this.mRadius = SVGLength.arrayFrom(radius);
invalidate();
}

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

int[] srcPixels = new int[width * height];
source.getPixels(srcPixels, 0, width, 0, 0, width, height);
int[] dstPixels = this.applyMorphology(srcPixels, width, height);

result.setPixels(dstPixels, 0, width, 0, 0, width, height);
return result;
}

private int[] applyMorphology(int[] srcPixels, int width, int height) {
int[] dstPixels = new int[width * height];
int[] tmpPixels = new int[width * height];
boolean isErode = this.mOperator == FilterProperties.FeMorphologyOperator.ERODE;
int radiusX = this.getRadiusX();
int radiusY = this.getRadiusY();

if (radiusX == 0 && radiusY == 0) {
return srcPixels;
}

horizontalPass(srcPixels, tmpPixels, width, height, radiusX, isErode);
verticalPass(tmpPixels, dstPixels, width, height, radiusY, isErode);

return dstPixels;
}

private int getRadiusX() {
if (this.mRadius != null && !this.mRadius.isEmpty()) {
return (int) Math.max(0, this.mRadius.get(0).value);
}

return 0;
}

private int getRadiusY() {
if (this.mRadius != null) {
if (this.mRadius.size() == 1) {
return this.getRadiusX();
}

if (this.mRadius.size() == 2) {
return (int) Math.max(0, this.mRadius.get(1).value);
}
}

return 0;
}

private void horizontalPass(
int[] src, int[] dst, int width, int height, int radiusX, boolean isErode) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
PixelRange range = new PixelRange();

int startX = Math.max(0, x - radiusX);
int endX = Math.min(width - 1, x + radiusX);

for (int nx = startX; nx <= endX; nx++) {
int c = src[y * width + nx];
range.include(c);
}

dst[y * width + x] = isErode ? range.minColor() : range.maxColor();
}
}
}

private void verticalPass(
int[] src, int[] dst, int width, int height, int radiusY, boolean isErode) {
for (int y = 0; y < height; y++) {
int startY = Math.max(0, y - radiusY);
int endY = Math.min(height - 1, y + radiusY);

for (int x = 0; x < width; x++) {
PixelRange range = new PixelRange();

for (int ny = startY; ny <= endY; ny++) {
int c = src[ny * width + x];
range.include(c);
}

dst[y * width + x] = isErode ? range.minColor() : range.maxColor();
}
}
}

/** Helper to track min and max RGBA values while scanning neighbors. */
private static class PixelRange {
int minR = 255, minG = 255, minB = 255, minA = 255;
int maxR = 0, maxG = 0, maxB = 0, maxA = 0;

void include(int color) {
int A = Color.alpha(color);
int R = Color.red(color);
int G = Color.green(color);
int B = Color.blue(color);

if (R < minR) minR = R;
if (G < minG) minG = G;
if (B < minB) minB = B;
if (A < minA) minA = A;

if (R > maxR) maxR = R;
if (G > maxG) maxG = G;
if (B > maxB) maxB = B;
if (A > maxA) maxA = A;
}

int minColor() {
return Color.argb(minA, minR, minG, minB);
}

int maxColor() {
return Color.argb(maxA, maxR, maxG, maxB);
}
}
}
32 changes: 32 additions & 0 deletions android/src/main/java/com/horcrux/svg/FilterProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,36 @@ public String toString() {
return type;
}
}

enum FeMorphologyOperator {
ERODE("erode"),
DILATE("dilate");

private final String operator;

FeMorphologyOperator(String operator) {
this.operator = operator;
}

static FeMorphologyOperator getEnum(String strVal) {
if (!typeToEnum.containsKey(strVal)) {
throw new IllegalArgumentException("Unknown String Value: " + strVal);
}
return typeToEnum.get(strVal);
}

private static final Map<String, FeMorphologyOperator> typeToEnum = new HashMap<>();

static {
for (final FeMorphologyOperator en : FeMorphologyOperator.values()) {
typeToEnum.put(en.operator, en);
}
}

@Nonnull
@Override
public String toString() {
return operator;
}
}
}
30 changes: 30 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.RNSVGFeGaussianBlurManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeMergeManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeMergeManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeMorphologyManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeMorphologyManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeOffsetManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeOffsetManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFilterManagerDelegate;
Expand Down Expand Up @@ -487,6 +489,7 @@ protected enum SVGClass {
RNSVGFeFlood,
RNSVGFeGaussianBlur,
RNSVGFeMerge,
RNSVGFeMorphology,
RNSVGFeOffset,
RNSVGMarker,
RNSVGForeignObject,
Expand Down Expand Up @@ -546,6 +549,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex
return new FeGaussianBlurView(reactContext);
case RNSVGFeMerge:
return new FeMergeView(reactContext);
case RNSVGFeMorphology:
return new FeMorphologyView(reactContext);
case RNSVGFeOffset:
return new FeOffsetView(reactContext);
case RNSVGMarker:
Expand Down Expand Up @@ -1651,6 +1656,31 @@ public void setNodes(FeMergeView node, ReadableArray nodes) {
}
}

static class FeMorphologyManager extends FilterPrimitiveManager<FeMorphologyView>
implements RNSVGFeMorphologyManagerInterface<FeMorphologyView> {
FeMorphologyManager() {
super(SVGClass.RNSVGFeMorphology);
mDelegate = new RNSVGFeMorphologyManagerDelegate(this);
}

public static final String REACT_CLASS = "RNSVGFeMorphology";

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

@ReactProp(name = "operator1")
public void setOperator1(FeMorphologyView node, String operator1) {
node.setOperator1(operator1);
}

@ReactProp(name = "radius")
public void setRadius(FeMorphologyView node, Dynamic radius) {
node.setRadius(radius);
}
}

static class FeOffsetManager extends FilterPrimitiveManager<FeOffsetView>
implements RNSVGFeOffsetManagerInterface<FeOffsetView> {
FeOffsetManager() {
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 @@ -268,6 +268,15 @@ public NativeModule get() {
return new FeMergeManager();
}
}));
specs.put(
FeMorphologyManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FeMorphologyManager();
}
}));
specs.put(
FeOffsetManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 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.BaseViewManager;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.LayoutShadowNode;

public class RNSVGFeMorphologyManagerDelegate<T extends View, U extends BaseViewManager<T, ? extends LayoutShadowNode> & RNSVGFeMorphologyManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNSVGFeMorphologyManagerDelegate(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 "operator1":
mViewManager.setOperator1(view, (String) value);
break;
case "radius":
mViewManager.setRadius(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 RNSVGFeMorphologyManagerInterface<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 setOperator1(T view, @Nullable String value);
void setRadius(T view, Dynamic value);
}
10 changes: 10 additions & 0 deletions apple/Filters/RNSVGFEMorphology.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import "RNSVGMorphologyOperator.h"
#import "RNSVGFilterPrimitive.h"

@interface RNSVGFeMorphology : RNSVGFilterPrimitive

@property (nonatomic, strong) NSString *in1;
@property (nonatomic, assign) RNSVGMorphologyOperator operator1;
@property (nonatomic, strong) NSArray<RNSVGLength *> *radius;

@end
Loading
Loading