Skip to content

feat: filters support FeColorMatrix and FilterImage #2316

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

Merged
merged 45 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f24dc44
feat: add filter props to codegen renderable components
jakex7 Jun 26, 2024
17040b7
fix: extract opacity allow percentage values
jakex7 Jun 26, 2024
945c4bd
feat: introduce Filter and FeColorMatrix components on js side
jakex7 Jun 26, 2024
d3d1106
feat: create base FilterPrimitive and implement FeColorMatrix on android
jakex7 Jun 26, 2024
1383d8c
feat: apply Filter on render (android)
jakex7 Jun 26, 2024
a0d10a4
feat: relativeOn
jakex7 Jun 26, 2024
9441bf7
chore: generate paper files with codegen
jakex7 Jun 26, 2024
af1408b
feat: extract filter props
jakex7 Jun 26, 2024
8779534
feat: create base FilterPrimitive and implement FeColorMatrix on Appl…
jakex7 Jun 26, 2024
f7c2f92
feat: introduce Filter elementa and register filters on SvgView
jakex7 Jun 26, 2024
dccedd3
feat: apply filter on render (iOS)
jakex7 Jun 26, 2024
25b2cfe
feat: ntroduce and expose FilterImage component as a subpackage
jakex7 Jun 26, 2024
54dada1
test: add FeColorMatrix to example app
jakex7 Jun 26, 2024
0070897
test: add FilterImage to example app
jakex7 Jun 26, 2024
7adc9a9
feat: crop filters on iOS
jakex7 Jun 27, 2024
2e31af6
fix: crop filters on iOS
jakex7 Jun 27, 2024
41c4b3c
feat: crop filters on android
jakex7 Jun 27, 2024
74b7b00
feat: expose bitmap to use it as background source
jakex7 Jun 27, 2024
a5d13ad
test: clean imports in example app
jakex7 Jun 27, 2024
53e2387
docs: remove todo from readme and update USAGE.md
jakex7 Jun 28, 2024
8341da7
fix: make FilterImage container overflow hidden
jakex7 Jun 28, 2024
8b4213a
docs: update filter USAGE and add screenshots
jakex7 Jun 28, 2024
e0941b8
fix: crop rect on iOS
jakex7 Jun 28, 2024
49e6c6e
docs: duplicate Example to FabricExample
jakex7 Jul 2, 2024
7852a57
Merge branch 'main' into @jakex7/filtersFeColorMatrix
jakex7 Jul 2, 2024
1e2ba72
docs: remove examples title
jakex7 Jul 3, 2024
17b59b2
fix: make resultsMap private
jakex7 Jul 3, 2024
ff98501
fix: change exceptions to more meaningful names
jakex7 Jul 3, 2024
742fc40
fix: update bitmap names and fix element bounds
jakex7 Jul 3, 2024
ef4aa7f
fix: use getters
jakex7 Jul 3, 2024
1f64c60
fix: use relativeOn function in relativeOnWidth, relativeOnHeight, re…
jakex7 Jul 3, 2024
0d53477
fix: remove saturation range warning
jakex7 Jul 3, 2024
36726ac
Revert "fix: extract opacity allow percentage values"
jakex7 Jul 3, 2024
439e8e0
Revert "docs: remove examples title"
jakex7 Jul 3, 2024
425a0eb
change: extract Filters type ot separate type
jakex7 Jul 3, 2024
13a3674
fix: throw error on invalid filter subview
jakex7 Jul 3, 2024
75e9e80
fix: extractFilter types
jakex7 Jul 3, 2024
25d1773
fix: extractResizeMode on FilterImage
jakex7 Jul 3, 2024
b952bdf
Merge branch 'main' into @jakex7/filtersFeColorMatrix
jakex7 Jul 3, 2024
ca324ed
fix: install warn-once and use as warning form primitiveUnits prop
jakex7 Jul 5, 2024
ba2059c
Merge branch 'main' into @jakex7/filtersFeColorMatrix
jakex7 Jul 5, 2024
7969928
remove: fabric example src
jakex7 Jul 5, 2024
ce5a942
fix: extract resize mode on android
jakex7 Jul 8, 2024
6855ee9
Merge branch 'main' into @jakex7/filtersFeColorMatrix
jakex7 Jul 10, 2024
9837434
fix: use macos platform view
jakex7 Jul 10, 2024
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
9 changes: 8 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ module.exports = {
'prettier',
'plugin:import/typescript',
],
plugins: ['react', 'react-native', 'import', '@typescript-eslint', 'react-hooks'],
plugins: [
'react',
'react-native',
'import',
'@typescript-eslint',
'react-hooks',
],
env: {
'react-native/react-native': true,
},
settings: {
'import/core-modules': [
'react-native-svg',
'react-native-svg/css',
'react-native-svg/filter-image',
],
'import/resolver': {
'babel-module': {
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,6 @@ If you suspect that you've found a spec conformance bug, then you can test using

To check how to use the library, see [USAGE.md](https://github.com/react-native-svg/react-native-svg/blob/main/USAGE.md)

## TODO:

1. Filters ([connected PR](https://github.com/react-native-svg/react-native-svg/pull/896))

## Known issues:

1. Unable to apply focus point of RadialGradient on Android.
Expand Down
81 changes: 81 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1252,3 +1252,84 @@ const styles = StyleSheet.create({
},
});
```

## Filters

Filter effects are a way of processing an element’s rendering before it is displayed in the document. Typically, rendering an element via CSS or SVG can conceptually be described as if the element, including its children, are drawn into a buffer (such as a raster image) and then that buffer is composited into the elements parent. Filters apply an effect before the compositing stage. Examples of such effects are blurring, changing color intensity and warping the image.

Currently supported\* filters are:

- FeColorMatrix

\*_More filters are coming soon_

Exmaple use of filters:

```jsx
import React from 'react';
import { FeColorMatrix, Filter, Rect, Svg } from 'react-native-svg';

export default () => {
return (
<Svg height="300" width="300">
<Filter id="myFilter">
<FeColorMatrix type="saturate" values="0.2" />
</Filter>
<Rect
x="0"
y="0"
width="300"
height="300"
fill="red"
filter="url(#myFilter)"
/>
</Svg>
);
};
```

![FeColorMatrix](./screenshots/feColorMatrix.png)

More info: <https://www.w3.org/TR/SVG11/filters.html>

## FilterImage

`FilterImage` is a new component that is not strictly related to SVG. Its behavior should be the same as a regular `Image` component from React Native with one exception - the additional prop `filters`, which accepts an array of filters to apply to the image.

### Example

```tsx
import React from 'react';
import { StyleSheet } from 'react-native';
import { FilterImage } from 'react-native-svg/filter-image';

const myImage = require('./myImage.jpg');

export default () => {
return (
<FilterImage
style={styles.image}
source={myImage}
filters={[
{ name: 'colorMatrix', type: 'saturate', values: 0.2 },
{
name: 'colorMatrix',
type: 'matrix',
values: [
0.2, 0.2, 0.2, 0, 0, 0.2, 0.2, 0.2, 0, 0, 0.2, 0.2, 0.2, 0, 0, 0, 0,
0, 1, 0,
],
},
]}
/>
);
};
const styles = StyleSheet.create({
image: {
width: 200,
height: 200,
},
});
```

![FilterImage](./screenshots/filterImage.png)
99 changes: 99 additions & 0 deletions android/src/main/java/com/horcrux/svg/FeColorMatrixView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.horcrux.svg;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.ColorMatrix;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import java.util.HashMap;

@SuppressLint("ViewConstructor")
class FeColorMatrixView extends FilterPrimitiveView {
String mIn1;
FilterProperties.FeColorMatrixType mType;
ReadableArray mValues;

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

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

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

public void setValues(ReadableArray values) {
this.mValues = values;
invalidate();
}

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

ColorMatrix colorMatrix = new ColorMatrix();
switch (this.mType) {
case MATRIX:
if (this.mValues.size() < 20) return source;

float[] rawMatrix = new float[mValues.size()];

for (int i = 0; i < this.mValues.size(); i++) {
rawMatrix[i] = (float) this.mValues.getDouble(i);
}

colorMatrix.set(rawMatrix);
break;
case SATURATE:
if (this.mValues.size() != 1) return source;

colorMatrix.setSaturation((float) this.mValues.getDouble(0));
break;
case HUE_ROTATE:
if (this.mValues.size() != 1) return source;

float hue = (float) this.mValues.getDouble(0);
float cosHue = (float) Math.cos(hue * Math.PI / 180);
float sinHue = (float) Math.sin(hue * Math.PI / 180);

colorMatrix.set(
new float[] {
0.213f + cosHue * 0.787f - sinHue * 0.213f, // 0
0.715f - cosHue * 0.715f - sinHue * 0.715f, // 1
0.072f - cosHue * 0.072f + sinHue * 0.928f, // 2
0, // 3
0, // 4
0.213f - cosHue * 0.213f + sinHue * 0.143f, // 5
0.715f + cosHue * 0.285f + sinHue * 0.140f, // 6
0.072f - cosHue * 0.072f - sinHue * 0.283f, // 7
0, // 8
0, // 9
0.213f - cosHue * 0.213f - sinHue * 0.787f, // 10
0.715f - cosHue * 0.715f + sinHue * 0.715f, // 11
0.072f + cosHue * 0.928f + sinHue * 0.072f, // 12
0, // 13
0, // 14
0, // 15
0, // 16
0, // 17
1, // 18
0, // 19
});
break;
case LUMINANCE_TO_ALPHA:
colorMatrix.set(
new float[] {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.2125f, 0.7154f, 0.0721f, 0, 0, 0, 0, 0,
0, 1
});
break;
}

return FilterUtils.getBitmapWithColorMatrix(colorMatrix, source);
}
}
62 changes: 62 additions & 0 deletions android/src/main/java/com/horcrux/svg/FilterPrimitiveView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.horcrux.svg;

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

@SuppressLint("ViewConstructor")
class FilterPrimitiveView extends DefinitionView {
SVGLength mX;
SVGLength mY;
SVGLength mW;
SVGLength mH;
private String mResult;

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

public void setX(Dynamic x) {
mX = SVGLength.from(x);
invalidate();
}

public void setY(Dynamic y) {
mY = SVGLength.from(y);
invalidate();
}

public void setWidth(Dynamic width) {
mW = SVGLength.from(width);
invalidate();
}

public void setHeight(Dynamic height) {
mH = SVGLength.from(height);
invalidate();
}

public void setResult(String result) {
mResult = result;
invalidate();
}

public String getResult() {
return mResult;
}

protected static Bitmap getSource(
HashMap<String, Bitmap> resultsMap, Bitmap prevResult, String in1) {
Bitmap sourceFromResults = in1 != null ? resultsMap.get(in1) : null;
return sourceFromResults != null ? sourceFromResults : prevResult;
}

public Bitmap applyFilter(HashMap<String, Bitmap> resultsMap, Bitmap prevResult) {
return null;
}

@Override
void saveDefinition() {}
}
110 changes: 110 additions & 0 deletions android/src/main/java/com/horcrux/svg/FilterProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.horcrux.svg;

import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;

class FilterProperties {
enum Units {
OBJECT_BOUNDING_BOX("objectBoundingBox"),
USER_SPACE_ON_USE("userSpaceOnUse"),
;

private final String units;

Units(String units) {
this.units = units;
}

static Units getEnum(String strVal) {
if (!unitsToEnum.containsKey(strVal)) {
throw new IllegalArgumentException("Unknown 'Unit' Value: " + strVal);
}
return unitsToEnum.get(strVal);
}

private static final Map<String, Units> unitsToEnum = new HashMap<>();

static {
for (final Units en : Units.values()) {
unitsToEnum.put(en.units, en);
}
}

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

enum EdgeMode {
UNKNOWN("unknown"),
DUPLICATE("duplicate"),
WRAP("wrap"),
NONE("none"),
;

private final String edgeMode;

EdgeMode(String edgeMode) {
this.edgeMode = edgeMode;
}

static EdgeMode getEnum(String strVal) {
if (!edgeModeToEnum.containsKey(strVal)) {
throw new IllegalArgumentException("Unknown 'edgeMode' Value: " + strVal);
}
return edgeModeToEnum.get(strVal);
}

private static final Map<String, EdgeMode> edgeModeToEnum = new HashMap<>();

static {
for (final EdgeMode en : EdgeMode.values()) {
edgeModeToEnum.put(en.edgeMode, en);
}
}

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

enum FeColorMatrixType {
MATRIX("matrix"),
SATURATE("saturate"),
HUE_ROTATE("hueRotate"),
LUMINANCE_TO_ALPHA("luminanceToAlpha"),
;

private final String type;

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

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

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

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

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