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
10 changes: 5 additions & 5 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1301,24 +1301,24 @@ Filter effects are a way of processing an element’s rendering before it is dis
The following filters have been implemented:

- FeBlend
- FeComponentTransfer
- FeComposite
- FeColorMatrix
- FeDropShadow
- FeFlood
- FeFuncA
- FeFuncB
- FeFuncG
- FeFuncR
- FeGaussianBlur
- FeMerge
- FeOffset

Not supported yet:

- FeComponentTransfer
- FeConvolveMatrix
- FeDiffuseLighting
- FeDisplacementMap
- FeFuncA
- FeFuncB
- FeFuncG
- FeFuncR
- FeImage
- FeMorphology
- FePointLight
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.horcrux.svg;

import android.annotation.SuppressLint;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import java.util.ArrayList;

@SuppressLint("ViewConstructor")
class FeComponentTransferFunctionView extends FilterPrimitiveView {
FilterProperties.FeComponentTransferFuncChannel mChannel;
FilterProperties.FeComponentTransferFuncType mType;
ArrayList<SVGLength> mTableValues;
SVGLength mSlope;
SVGLength mIntercept;
SVGLength mAmplitude;
SVGLength mExponent;
SVGLength mOffset;

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

public void setChannel(String channel) {
this.mChannel = FilterProperties.FeComponentTransferFuncChannel.getEnum(channel);
invalidate();
}

public void setType(String type) {
this.mType = FilterProperties.FeComponentTransferFuncType.getEnum(type);
invalidate();
}

public void setTableValues(Dynamic tableValues) {
this.mTableValues = SVGLength.arrayFrom(tableValues);
invalidate();
}

public void setSlope(Dynamic slope) {
this.mSlope = SVGLength.from(slope);
invalidate();
}

public void setIntercept(Dynamic intercept) {
this.mIntercept = SVGLength.from(intercept);
invalidate();
}

public void setAmplitude(Dynamic amplitude) {
this.mAmplitude = SVGLength.from(amplitude);
invalidate();
}

public void setExponent(Dynamic exponent) {
this.mExponent = SVGLength.from(exponent);
invalidate();
}

public void setOffset(Dynamic offset) {
this.mOffset = SVGLength.from(offset);
invalidate();
}

public int apply(int colorValue) {
double C = colorValue / 255f; // normalize to [0,1]
double Cprime = C;

switch (mType) {
case TABLE:
if (mTableValues != null && !mTableValues.isEmpty()) {
int n = mTableValues.size() - 1;
if (C >= 1f) {
Cprime = mTableValues.get(n).value;
} else {
double pos = C * n;
int k = (int) Math.floor(pos);
double frac = pos - k;
Cprime =
mTableValues.get(k).value
+ frac * (mTableValues.get(k + 1).value - mTableValues.get(k).value);
}
}
break;

case DISCRETE:
if (mTableValues != null && !mTableValues.isEmpty()) {
int n = mTableValues.size();
if (C >= 1f) {
Cprime = mTableValues.get(n - 1).value;
} else {
int k = (int) Math.floor(C * n);
Cprime = mTableValues.get(k).value;
}
}
break;

case LINEAR:
Cprime = mSlope.value * C + mIntercept.value;
break;

case GAMMA:
Cprime = mAmplitude.value * Math.pow(C, mExponent.value) + mOffset.value;
break;

case IDENTITY:
default:
break;
}

// clamp to [0,1] and scale back to 0..255
Cprime = Math.max(0f, Math.min(1f, Cprime));
return (int) (Cprime * 255f + 0.5f);
}
}
82 changes: 82 additions & 0 deletions android/src/main/java/com/horcrux/svg/FeComponentTransferView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.horcrux.svg;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.view.View;

import com.facebook.react.bridge.ReactContext;

import java.util.HashMap;

@SuppressLint("ViewConstructor")
class FeComponentTransferView extends FilterPrimitiveView {
String mIn1;
FeComponentTransferFunctionView mFeFuncR;
FeComponentTransferFunctionView mFeFuncG;
FeComponentTransferFunctionView mFeFuncB;
FeComponentTransferFunctionView mFeFuncA;

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

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

@Override
public Bitmap applyFilter(HashMap<String, Bitmap> resultsMap, Bitmap prevResult) {
Bitmap source = getSource(resultsMap, prevResult, this.mIn1);
if (source == null) return prevResult;
assignFeComponentTransferFunctionViews();

Bitmap result = source.copy(Bitmap.Config.ARGB_8888, true);
int w = result.getWidth();
int h = result.getHeight();
int[] pixels = new int[w * h];
result.getPixels(pixels, 0, w, 0, 0, w, h);

applyComponentTransferOnPixels(pixels);

result.setPixels(pixels, 0, w, 0, 0, w, h);
return result;
}

private void assignFeComponentTransferFunctionViews() {
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (!(node instanceof FeComponentTransferFunctionView)) {
continue;
}

FeComponentTransferFunctionView functionView = (FeComponentTransferFunctionView) node;

switch (functionView.mChannel) {
case R -> mFeFuncR = functionView;
case G -> mFeFuncG = functionView;
case B -> mFeFuncB = functionView;
case A -> mFeFuncA = functionView;
}
}
}

private void applyComponentTransferOnPixels(int[] pixels) {
for (int i = 0; i < pixels.length; i++) {
int c = pixels[i];

int a = Color.alpha(c);
int r = Color.red(c);
int g = Color.green(c);
int b = Color.blue(c);

if (mFeFuncR != null) r = mFeFuncR.apply(r);
if (mFeFuncG != null) g = mFeFuncG.apply(g);
if (mFeFuncB != null) b = mFeFuncB.apply(b);
if (mFeFuncA != null) a = mFeFuncA.apply(a);

pixels[i] = Color.argb(a, r, g, b);
}
}
}
70 changes: 70 additions & 0 deletions android/src/main/java/com/horcrux/svg/FilterProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,76 @@ public String toString() {
}
}

enum FeComponentTransferFuncType {
IDENTITY("identity"),
TABLE("table"),
DISCRETE("discrete"),
LINEAR("linear"),
GAMMA("gamma");

private final String type;

FeComponentTransferFuncType(String type) {
this.type = type;
}

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

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

static {
for (final FeComponentTransferFuncType en : FeComponentTransferFuncType.values()) {
typeToEnum.put(en.type, en);
}
}

@Nonnull
@Override
public String toString() {
return type;
}
}

enum FeComponentTransferFuncChannel {
R("R"),
G("G"),
B("B"),
A("A"),
UNKNOWN("UNKNOWN");

private final String channel;

FeComponentTransferFuncChannel(String channel) {
this.channel = channel;
}

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

private static final Map<String, FeComponentTransferFuncChannel> channelToEnum = new HashMap<>();

static {
for (final FeComponentTransferFuncChannel en : FeComponentTransferFuncChannel.values()) {
channelToEnum.put(en.channel, en);
}
}

@Nonnull
@Override
public String toString() {
return channel;
}
}

enum FeColorMatrixType {
MATRIX("matrix"),
SATURATE("saturate"),
Expand Down
Loading
Loading