feat: filters support FeColorMatrix and FilterImage (#2316)

# Summary

Introducing the long-awaited **Filters** in `react-native-svg` 🎉

### Motivation

This PR is the beginning of bringing support of SVG Filters into
`react-native-svg`.

* **related issues**: This PR series will address the following issues:
#150, #176, #635, #883, #994, #996, #1216
* **feature overview**: This PR is a boilerplate for Filters
   * introducing `Filter` component and `FeColorMatrix` as a start. 
* It also introduces a new subdirectory called
`react-native-svg/filter-image` with a `FilterImage` component.

# Usage

## Filter and Fe...

Filters are compatible with the web familiar standard, so most things
should be compatible out-of-the-box and changes will be limited to using
a capital letter as it's component.

### Example

```tsx
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>
  );
};
```

![image](https://github.com/software-mansion/react-native-svg/assets/39670088/c36fb238-95f4-455d-b0aa-2a7d4038b828)

## Filter Image

`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,
  },
});
```


![image](https://github.com/software-mansion/react-native-svg/assets/39670088/666ed89f-68d8-491b-b97f-1eef112b7095)

## Test Plan

**Example App**: Updated the example app with various filter effects,
showcasing real-world usage.

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |         |
| Android |         |

## Checklist

- [x] I have tested this on a device and a simulator
- [x] I added documentation in `README.md` and `USAGE.md`
- [x] I updated the typed files (typescript)
This commit is contained in:
Jakub Grzywacz
2024-07-11 11:17:35 +02:00
committed by GitHub
parent 1de7709176
commit 08e92074b4
135 changed files with 3046 additions and 322 deletions

View File

@@ -7,7 +7,13 @@ 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,
},
@@ -15,6 +21,7 @@ module.exports = {
'import/core-modules': [
'react-native-svg',
'react-native-svg/css',
'react-native-svg/filter-image',
],
'import/resolver': {
'babel-module': {

View File

@@ -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.

View File

@@ -1252,3 +1252,84 @@ const styles = StyleSheet.create({
},
});
```
## Filters
Filter effects are a way of processing an elements 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)

View File

@@ -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);
}
}

View File

@@ -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() {}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,37 @@
package com.horcrux.svg;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
public class FilterUtils {
public static Bitmap getBitmapWithColorMatrix(ColorMatrix colorMatrix, Bitmap sourceBitmap) {
Bitmap results =
Bitmap.createBitmap(
sourceBitmap.getWidth(), sourceBitmap.getHeight(), sourceBitmap.getConfig());
Canvas canvas = new Canvas(results);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
canvas.drawBitmap(sourceBitmap, 0, 0, paint);
return results;
}
public static Bitmap applySourceAlphaFilter(Bitmap source) {
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.set(
new float[] {
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1
});
return getBitmapWithColorMatrix(colorMatrix, source);
}
}

View File

@@ -0,0 +1,113 @@
package com.horcrux.svg;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import java.util.HashMap;
@SuppressLint("ViewConstructor")
class FilterView extends DefinitionView {
private final HashMap<String, Bitmap> mResultsMap = new HashMap<>();
SVGLength mX;
SVGLength mY;
SVGLength mW;
SVGLength mH;
private FilterProperties.Units mFilterUnits;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private FilterProperties.Units mPrimitiveUnits;
public FilterView(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 setFilterUnits(String filterUnits) {
mFilterUnits = FilterProperties.Units.getEnum(filterUnits);
invalidate();
}
public void setPrimitiveUnits(String primitiveUnits) {
mPrimitiveUnits = FilterProperties.Units.getEnum(primitiveUnits);
invalidate();
}
@Override
void saveDefinition() {
if (mName != null) {
SvgView svg = getSvgView();
if (svg != null) {
svg.defineFilter(this, mName);
}
}
}
public Bitmap applyFilter(
Bitmap source, Bitmap background, Rect renderableBounds, Rect canvasBounds) {
mResultsMap.clear();
mResultsMap.put("SourceGraphic", source);
mResultsMap.put("SourceAlpha", FilterUtils.applySourceAlphaFilter(source));
mResultsMap.put("BackgroundImage", background);
mResultsMap.put("BackgroundAlpha", FilterUtils.applySourceAlphaFilter(background));
Bitmap res = source;
for (int i = 0; i < getChildCount(); i++) {
View node = getChildAt(i);
if (node instanceof FilterPrimitiveView currentFilter) {
res = currentFilter.applyFilter(mResultsMap, res);
String resultName = currentFilter.getResult();
if (resultName != null) {
mResultsMap.put(resultName, res);
}
} else {
Log.e("RNSVG", "Invalid `Filter` child: Filter children can only be `Fe...` components");
}
}
// crop Bitmap to filter coordinates
int x, y, width, height;
if (this.mFilterUnits == FilterProperties.Units.USER_SPACE_ON_USE) {
x = (int) this.relativeOn(this.mX, canvasBounds.width());
y = (int) this.relativeOn(this.mY, canvasBounds.height());
width = (int) this.relativeOn(this.mW, canvasBounds.width());
height = (int) this.relativeOn(this.mH, canvasBounds.height());
} else { // FilterProperties.Units.OBJECT_BOUNDING_BOX
x = (int) this.relativeOnFraction(this.mX, renderableBounds.width());
y = (int) this.relativeOnFraction(this.mY, renderableBounds.height());
width = (int) this.relativeOnFraction(this.mW, renderableBounds.width());
height = (int) this.relativeOnFraction(this.mH, renderableBounds.height());
}
Rect cropRect = new Rect(x, y, x + width, y + height);
Bitmap resultBitmap = Bitmap.createBitmap(res.getWidth(), res.getHeight(), res.getConfig());
Canvas canvas = new Canvas(resultBitmap);
canvas.drawBitmap(res, cropRect, cropRect, null);
return resultBitmap;
}
}

View File

@@ -62,7 +62,8 @@ class PathParser {
if (!has_prev_cmd && first_char != 'M' && first_char != 'm') {
// The first segment must be a MoveTo.
throw new IllegalArgumentException(String.format("Unexpected character '%c' (i=%d, s=%s)", first_char, i, s));
throw new IllegalArgumentException(
String.format("Unexpected character '%c' (i=%d, s=%s)", first_char, i, s));
}
// TODO: simplify
@@ -75,7 +76,8 @@ class PathParser {
} else if (is_number_start(first_char) && has_prev_cmd) {
if (prev_cmd == 'Z' || prev_cmd == 'z') {
// ClosePath cannot be followed by a number.
throw new IllegalArgumentException(String.format("Unexpected number after 'z' (s=%s)", s));
throw new IllegalArgumentException(
String.format("Unexpected number after 'z' (s=%s)", s));
}
if (prev_cmd == 'M' || prev_cmd == 'm') {
@@ -93,7 +95,8 @@ class PathParser {
cmd = prev_cmd;
}
} else {
throw new IllegalArgumentException(String.format("Unexpected character '%c' (i=%d, s=%s)", first_char, i, s));
throw new IllegalArgumentException(
String.format("Unexpected character '%c' (i=%d, s=%s)", first_char, i, s));
}
boolean absolute = is_absolute(cmd);
@@ -226,8 +229,8 @@ class PathParser {
}
default:
{
throw new IllegalArgumentException(String.format("Unexpected comand '%c' (s=%s)", cmd, s));
throw new IllegalArgumentException(
String.format("Unexpected comand '%c' (s=%s)", cmd, s));
}
}
@@ -624,7 +627,8 @@ class PathParser {
c = s.charAt(i);
}
} else if (c != '.') {
throw new IllegalArgumentException(String.format("Invalid number formating character '%c' (i=%d, s=%s)", c, i, s));
throw new IllegalArgumentException(
String.format("Invalid number formating character '%c' (i=%d, s=%s)", c, i, s));
}
// Consume fraction.
@@ -649,7 +653,8 @@ class PathParser {
} else if (c >= '0' && c <= '9') {
skip_digits();
} else {
throw new IllegalArgumentException(String.format("Invalid number formating character '%c' (i=%d, s=%s)", c, i, s));
throw new IllegalArgumentException(
String.format("Invalid number formating character '%c' (i=%d, s=%s)", c, i, s));
}
}
}
@@ -659,7 +664,8 @@ class PathParser {
// inf, nan, etc. are an error.
if (Float.isInfinite(n) || Float.isNaN(n)) {
throw new IllegalArgumentException(String.format("Invalid number '%s' (start=%d, i=%d, s=%s)", num, start, i, s));
throw new IllegalArgumentException(
String.format("Invalid number '%s' (start=%d, i=%d, s=%s)", num, start, i, s));
}
return n;

View File

@@ -8,6 +8,7 @@
package com.horcrux.svg;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
@@ -94,6 +95,8 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
private @Nullable ArrayList<String> mPropList;
private @Nullable ArrayList<String> mAttributeList;
@Nullable String mFilter;
private static final Pattern regex = Pattern.compile("[0-9.-]+");
@Nullable
@@ -326,29 +329,64 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
invalidate();
}
public void setFilter(String filter) {
mFilter = filter;
invalidate();
}
void render(Canvas canvas, Paint paint, float opacity) {
MaskView mask = null;
FilterView filter = null;
if (mMask != null) {
SvgView root = getSvgView();
mask = (MaskView) root.getDefinedMask(mMask);
}
if (mFilter != null) {
SvgView root = getSvgView();
filter = (FilterView) root.getDefinedFilter(mFilter);
}
if (mask != null) {
// https://www.w3.org/TR/SVG11/masking.html
// Adding a mask involves several steps
// 1. applying luminanceToAlpha to the mask element
// 2. merging the alpha channel of the element with the alpha channel from the previous step
// 3. applying the result from step 2 to the target element
if (mask != null || filter != null) {
if (filter != null) {
Paint bitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
canvas.saveLayer(null, bitmapPaint);
canvas.saveLayer(null, paint);
draw(canvas, paint, opacity);
Rect canvasBounds = this.getSvgView().getCanvasBounds();
Paint dstInPaint = new Paint();
dstInPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
// draw element to self bitmap
Bitmap elementBitmap =
Bitmap.createBitmap(
canvasBounds.width(), canvasBounds.height(), Bitmap.Config.ARGB_8888);
Canvas elementCanvas = new Canvas(elementBitmap);
// prepare step 3 - combined layer
canvas.saveLayer(null, dstInPaint);
draw(elementCanvas, paint, opacity);
// apply filters
Bitmap backgroundBitmap = this.getSvgView().getCurrentBitmap();
elementBitmap =
filter.applyFilter(
elementBitmap, backgroundBitmap, elementCanvas.getClipBounds(), canvasBounds);
// draw bitmap to canvas
canvas.drawBitmap(elementBitmap, 0, 0, bitmapPaint);
} else {
canvas.saveLayer(null, paint);
draw(canvas, paint, opacity);
}
if (mask != null) {
// https://www.w3.org/TR/SVG11/masking.html
// Adding a mask involves several steps
// 1. applying luminanceToAlpha to the mask element
// 2. merging the alpha channel of the element with the alpha channel from the previous step
// 3. applying the result from step 2 to the target element
Paint dstInPaint = new Paint();
dstInPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
// prepare step 3 - combined layer
canvas.saveLayer(null, dstInPaint);
if (mask.getMaskType() == MaskView.MaskType.LUMINANCE) {
// step 1 - luminance layer
@@ -366,32 +404,32 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
canvas.saveLayer(null, paint);
}
// calculate mask bounds
float maskX = (float) relativeOnWidth(mask.mX);
float maskY = (float) relativeOnHeight(mask.mY);
float maskWidth = (float) relativeOnWidth(mask.mW);
float maskHeight = (float) relativeOnHeight(mask.mH);
// clip to mask bounds
canvas.clipRect(maskX, maskY, maskX + maskWidth, maskY + maskHeight);
// calculate mask bounds
float maskX = (float) relativeOnWidth(mask.mX);
float maskY = (float) relativeOnHeight(mask.mY);
float maskWidth = (float) relativeOnWidth(mask.mW);
float maskHeight = (float) relativeOnHeight(mask.mH);
// clip to mask bounds
canvas.clipRect(maskX, maskY, maskX + maskWidth, maskY + maskHeight);
mask.draw(canvas, paint, 1f);
mask.draw(canvas, paint, 1f);
// close luminance layer
canvas.restore();
// close luminance layer
canvas.restore();
// step 2 - alpha layer
canvas.saveLayer(null, dstInPaint);
// clip to mask bounds
canvas.clipRect(maskX, maskY, maskX + maskWidth, maskY + maskHeight);
// step 2 - alpha layer
canvas.saveLayer(null, dstInPaint);
// clip to mask bounds
canvas.clipRect(maskX, maskY, maskX + maskWidth, maskY + maskHeight);
mask.draw(canvas, paint, 1f);
mask.draw(canvas, paint, 1f);
// close alpha layer
canvas.restore();
// close combined layer
canvas.restore();
// close alpha layer
canvas.restore();
// close combined layer
canvas.restore();
}
// close element layer
canvas.restore();
} else {

View File

@@ -103,6 +103,10 @@ import com.facebook.react.viewmanagers.RNSVGDefsManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGDefsManagerInterface;
import com.facebook.react.viewmanagers.RNSVGEllipseManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGEllipseManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface;
import com.facebook.react.viewmanagers.RNSVGFilterManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGFilterManagerInterface;
import com.facebook.react.viewmanagers.RNSVGForeignObjectManagerDelegate;
import com.facebook.react.viewmanagers.RNSVGForeignObjectManagerInterface;
import com.facebook.react.viewmanagers.RNSVGGroupManagerDelegate;
@@ -578,6 +582,8 @@ class VirtualViewManager<V extends VirtualView> extends ViewGroupManager<Virtual
RNSVGRadialGradient,
RNSVGPattern,
RNSVGMask,
RNSVGFilter,
RNSVGFeColorMatrix,
RNSVGMarker,
RNSVGForeignObject,
}
@@ -622,6 +628,10 @@ class VirtualViewManager<V extends VirtualView> extends ViewGroupManager<Virtual
return new PatternView(reactContext);
case RNSVGMask:
return new MaskView(reactContext);
case RNSVGFilter:
return new FilterView(reactContext);
case RNSVGFeColorMatrix:
return new FeColorMatrixView(reactContext);
case RNSVGMarker:
return new MarkerView(reactContext);
case RNSVGForeignObject:
@@ -664,6 +674,11 @@ class RenderableViewManager<T extends RenderableView> extends VirtualViewManager
super(svgclass);
}
@ReactProp(name = "filter")
public void setFilter(T node, String filter) {
node.setFilter(filter);
}
static class GroupViewManagerAbstract<U extends GroupView> extends RenderableViewManager<U> {
GroupViewManagerAbstract(SVGClass svgClass) {
super(svgClass);
@@ -1282,6 +1297,96 @@ class RenderableViewManager<T extends RenderableView> extends VirtualViewManager
}
}
static class FilterManager extends VirtualViewManager<FilterView>
implements RNSVGFilterManagerInterface<FilterView> {
FilterManager() {
super(SVGClass.RNSVGFilter);
mDelegate = new RNSVGFilterManagerDelegate(this);
}
public static final String REACT_CLASS = "RNSVGFilter";
@ReactProp(name = "x")
public void setX(FilterView node, Dynamic x) {
node.setX(x);
}
@ReactProp(name = "y")
public void setY(FilterView node, Dynamic y) {
node.setY(y);
}
@ReactProp(name = "width")
public void setWidth(FilterView node, Dynamic width) {
node.setWidth(width);
}
@ReactProp(name = "height")
public void setHeight(FilterView node, Dynamic height) {
node.setHeight(height);
}
@ReactProp(name = "filterUnits")
public void setFilterUnits(FilterView node, String filterUnits) {
node.setFilterUnits(filterUnits);
}
@ReactProp(name = "primitiveUnits")
public void setPrimitiveUnits(FilterView node, String primitiveUnits) {
node.setPrimitiveUnits(primitiveUnits);
}
}
static class FeColorMatrixManager extends VirtualViewManager<FeColorMatrixView>
implements RNSVGFeColorMatrixManagerInterface<FeColorMatrixView> {
FeColorMatrixManager() {
super(SVGClass.RNSVGFeColorMatrix);
mDelegate = new RNSVGFeColorMatrixManagerDelegate(this);
}
public static final String REACT_CLASS = "RNSVGFeColorMatrix";
@ReactProp(name = "x")
public void setX(FeColorMatrixView node, Dynamic x) {
node.setX(x);
}
@ReactProp(name = "y")
public void setY(FeColorMatrixView node, Dynamic y) {
node.setY(y);
}
@ReactProp(name = "width")
public void setWidth(FeColorMatrixView node, Dynamic width) {
node.setWidth(width);
}
@ReactProp(name = "height")
public void setHeight(FeColorMatrixView node, Dynamic height) {
node.setHeight(height);
}
@ReactProp(name = "result")
public void setResult(FeColorMatrixView node, String result) {
node.setResult(result);
}
@ReactProp(name = "in1")
public void setIn1(FeColorMatrixView node, String in1) {
node.setIn1(in1);
}
@ReactProp(name = "type")
public void setType(FeColorMatrixView node, String type) {
node.setType(type);
}
@ReactProp(name = "values")
public void setValues(FeColorMatrixView node, @Nullable ReadableArray values) {
node.setValues(values);
}
}
static class ForeignObjectManager extends GroupViewManagerAbstract<ForeignObjectView>
implements RNSVGForeignObjectManagerInterface<ForeignObjectView> {
ForeignObjectManager() {

View File

@@ -205,6 +205,24 @@ public class SvgPackage extends TurboReactPackage implements ViewManagerOnDemand
return new MaskManager();
}
}));
specs.put(
FilterManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FilterManager();
}
}));
specs.put(
FeColorMatrixManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(
new Provider<NativeModule>() {
@Override
public NativeModule get() {
return new FeColorMatrixManager();
}
}));
specs.put(
ForeignObjectManager.REACT_CLASS,
ModuleSpec.viewManagerSpec(

View File

@@ -58,6 +58,7 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
}
private @Nullable Bitmap mBitmap;
private @Nullable Bitmap mCurrentBitmap;
private boolean mRemovalTransitionStarted;
public SvgView(ReactContext reactContext) {
@@ -161,6 +162,7 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
private final Map<String, VirtualView> mDefinedTemplates = new HashMap<>();
private final Map<String, VirtualView> mDefinedMarkers = new HashMap<>();
private final Map<String, VirtualView> mDefinedMasks = new HashMap<>();
private final Map<String, VirtualView> mDefinedFilters = new HashMap<>();
private final Map<String, Brush> mDefinedBrushes = new HashMap<>();
private Canvas mCanvas;
private final float mScale;
@@ -264,7 +266,7 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
return null;
}
Bitmap bitmap = Bitmap.createBitmap((int) width, (int) height, Bitmap.Config.ARGB_8888);
mCurrentBitmap = bitmap;
drawChildren(new Canvas(bitmap));
return bitmap;
}
@@ -423,6 +425,14 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
return mDefinedMasks.get(maskRef);
}
void defineFilter(VirtualView filter, String filterRef) {
mDefinedFilters.put(filterRef, filter);
}
VirtualView getDefinedFilter(String filterRef) {
return mDefinedFilters.get(filterRef);
}
void defineMarker(VirtualView marker, String markerRef) {
mDefinedMarkers.put(markerRef, marker);
}
@@ -430,4 +440,8 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
VirtualView getDefinedMarker(String markerRef) {
return mDefinedMarkers.get(markerRef);
}
public Bitmap getCurrentBitmap() {
return mCurrentBitmap;
}
}

View File

@@ -20,7 +20,6 @@ import android.view.View;
import android.view.ViewParent;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import java.util.ArrayList;
import javax.annotation.Nullable;

View File

@@ -418,34 +418,36 @@ public abstract class VirtualView extends ReactViewGroup {
return svgView;
}
double relativeOnWidth(SVGLength length) {
double relativeOnFraction(SVGLength length, float relative) {
SVGLength.UnitType unit = length.unit;
if (unit == SVGLength.UnitType.NUMBER) {
return length.value * relative;
} else if (unit == SVGLength.UnitType.PERCENTAGE) {
return length.value / 100 * relative;
}
return fromRelativeFast(length);
}
double relativeOn(SVGLength length, float relative) {
SVGLength.UnitType unit = length.unit;
if (unit == SVGLength.UnitType.NUMBER) {
return length.value * mScale;
} else if (unit == SVGLength.UnitType.PERCENTAGE) {
return length.value / 100 * getCanvasWidth();
return length.value / 100 * relative;
}
return fromRelativeFast(length);
}
double relativeOnWidth(SVGLength length) {
return relativeOn(length, getCanvasWidth());
}
double relativeOnHeight(SVGLength length) {
SVGLength.UnitType unit = length.unit;
if (unit == SVGLength.UnitType.NUMBER) {
return length.value * mScale;
} else if (unit == SVGLength.UnitType.PERCENTAGE) {
return length.value / 100 * getCanvasHeight();
}
return fromRelativeFast(length);
return relativeOn(length, getCanvasHeight());
}
double relativeOnOther(SVGLength length) {
SVGLength.UnitType unit = length.unit;
if (unit == SVGLength.UnitType.NUMBER) {
return length.value * mScale;
} else if (unit == SVGLength.UnitType.PERCENTAGE) {
return length.value / 100 * getCanvasDiagonal();
}
return fromRelativeFast(length);
return relativeOn(length, (float) getCanvasDiagonal());
}
/**

View File

@@ -99,6 +99,9 @@ public class RNSVGCircleManagerDelegate<T extends View, U extends BaseViewManage
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "cx":
mViewManager.setCx(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGCircleManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setCx(T view, Dynamic value);
void setCy(T view, Dynamic value);
void setR(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGClipPathManagerDelegate<T extends View, U extends BaseViewMana
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGClipPathManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGEllipseManagerDelegate<T extends View, U extends BaseViewManag
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "cx":
mViewManager.setCx(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGEllipseManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setCx(T view, Dynamic value);
void setCy(T view, Dynamic value);
void setRx(T view, Dynamic value);

View File

@@ -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.bridge.ReadableArray;
import com.facebook.react.uimanager.BaseViewManagerDelegate;
import com.facebook.react.uimanager.BaseViewManagerInterface;
public class RNSVGFeColorMatrixManagerDelegate<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFeColorMatrixManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNSVGFeColorMatrixManagerDelegate(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 "type":
mViewManager.setType(view, (String) value);
break;
case "values":
mViewManager.setValues(view, (ReadableArray) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* 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;
import com.facebook.react.bridge.ReadableArray;
public interface RNSVGFeColorMatrixManagerInterface<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 setType(T view, @Nullable String value);
void setValues(T view, @Nullable ReadableArray value);
}

View File

@@ -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 RNSVGFilterManagerDelegate<T extends View, U extends BaseViewManagerInterface<T> & RNSVGFilterManagerInterface<T>> extends BaseViewManagerDelegate<T, U> {
public RNSVGFilterManagerDelegate(U viewManager) {
super(viewManager);
}
@Override
public void setProperty(T view, String propName, @Nullable Object value) {
switch (propName) {
case "name":
mViewManager.setName(view, value == null ? null : (String) value);
break;
case "x":
mViewManager.setX(view, new DynamicFromObject(value));
break;
case "y":
mViewManager.setY(view, new DynamicFromObject(value));
break;
case "height":
mViewManager.setHeight(view, new DynamicFromObject(value));
break;
case "width":
mViewManager.setWidth(view, new DynamicFromObject(value));
break;
case "filterUnits":
mViewManager.setFilterUnits(view, (String) value);
break;
case "primitiveUnits":
mViewManager.setPrimitiveUnits(view, (String) value);
break;
default:
super.setProperty(view, propName, value);
}
}
}

View File

@@ -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 RNSVGFilterManagerInterface<T extends View> {
void setName(T view, @Nullable String value);
void setX(T view, Dynamic value);
void setY(T view, Dynamic value);
void setHeight(T view, Dynamic value);
void setWidth(T view, Dynamic value);
void setFilterUnits(T view, @Nullable String value);
void setPrimitiveUnits(T view, @Nullable String value);
}

View File

@@ -99,6 +99,9 @@ public class RNSVGForeignObjectManagerDelegate<T extends View, U extends BaseVie
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGForeignObjectManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGGroupManagerDelegate<T extends View, U extends BaseViewManager
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGGroupManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGImageManagerDelegate<T extends View, U extends BaseViewManager
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "x":
mViewManager.setX(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGImageManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setX(T view, Dynamic value);
void setY(T view, Dynamic value);
void setWidth(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGLineManagerDelegate<T extends View, U extends BaseViewManagerI
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "x1":
mViewManager.setX1(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGLineManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setX1(T view, Dynamic value);
void setY1(T view, Dynamic value);
void setX2(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGMarkerManagerDelegate<T extends View, U extends BaseViewManage
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGMarkerManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGMaskManagerDelegate<T extends View, U extends BaseViewManagerI
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGMaskManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGPathManagerDelegate<T extends View, U extends BaseViewManagerI
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "d":
mViewManager.setD(view, value == null ? null : (String) value);
break;

View File

@@ -41,5 +41,6 @@ public interface RNSVGPathManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setD(T view, @Nullable String value);
}

View File

@@ -99,6 +99,9 @@ public class RNSVGPatternManagerDelegate<T extends View, U extends BaseViewManag
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGPatternManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGRectManagerDelegate<T extends View, U extends BaseViewManagerI
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "x":
mViewManager.setX(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGRectManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setX(T view, Dynamic value);
void setY(T view, Dynamic value);
void setHeight(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGSymbolManagerDelegate<T extends View, U extends BaseViewManage
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGSymbolManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGTSpanManagerDelegate<T extends View, U extends BaseViewManager
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGTSpanManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGTextManagerDelegate<T extends View, U extends BaseViewManagerI
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGTextManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGTextPathManagerDelegate<T extends View, U extends BaseViewMana
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "fontSize":
mViewManager.setFontSize(view, new DynamicFromObject(value));
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGTextPathManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setFontSize(T view, Dynamic value);
void setFontWeight(T view, Dynamic value);
void setFont(T view, Dynamic value);

View File

@@ -99,6 +99,9 @@ public class RNSVGUseManagerDelegate<T extends View, U extends BaseViewManagerIn
case "propList":
mViewManager.setPropList(view, (ReadableArray) value);
break;
case "filter":
mViewManager.setFilter(view, value == null ? null : (String) value);
break;
case "href":
mViewManager.setHref(view, value == null ? null : (String) value);
break;

View File

@@ -41,6 +41,7 @@ public interface RNSVGUseManagerInterface<T extends View> {
void setStrokeMiterlimit(T view, float value);
void setVectorEffect(T view, int value);
void setPropList(T view, @Nullable ReadableArray value);
void setFilter(T view, @Nullable String value);
void setHref(T view, @Nullable String value);
void setX(T view, Dynamic value);
void setY(T view, Dynamic value);

View File

@@ -1,15 +1,13 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
* 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.
* <p>Do not edit this file as changes may cause incorrect behavior and will be lost once the code
* is regenerated.
*
* @generated by codegen project: GenerateModuleJavaSpec.js
*
* @nolint
*/
package com.horcrux.svg;
import com.facebook.proguard.annotations.DoNotStrip;
@@ -22,7 +20,8 @@ import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public abstract class NativeSvgViewModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
public abstract class NativeSvgViewModuleSpec extends ReactContextBaseJavaModule
implements TurboModule {
public static final String NAME = "RNSVGSvgViewModule";
public NativeSvgViewModuleSpec(ReactApplicationContext reactContext) {
@@ -36,5 +35,6 @@ public abstract class NativeSvgViewModuleSpec extends ReactContextBaseJavaModule
@ReactMethod
@DoNotStrip
public abstract void toDataURL(@Nullable Double tag, @Nullable ReadableMap options, @Nullable Callback callback);
public abstract void toDataURL(
@Nullable Double tag, @Nullable ReadableMap options, @Nullable Callback callback);
}

View File

@@ -63,6 +63,10 @@
- (RNSVGNode *)getDefinedMask:(NSString *)maskName;
- (void)defineFilter:(RNSVGNode *)filter filterName:(NSString *)filterName;
- (RNSVGNode *)getDefinedFilter:(NSString *)filterName;
- (NSString *)getDataURLWithBounds:(CGRect)bounds;
- (CGRect)getContextBounds;

View File

@@ -25,6 +25,7 @@
NSMutableDictionary<NSString *, RNSVGPainter *> *_painters;
NSMutableDictionary<NSString *, RNSVGNode *> *_markers;
NSMutableDictionary<NSString *, RNSVGNode *> *_masks;
NSMutableDictionary<NSString *, RNSVGNode *> *_filters;
CGAffineTransform _invviewBoxTransform;
bool rendered;
}
@@ -113,6 +114,7 @@ using namespace facebook::react;
_painters = nil;
_markers = nil;
_masks = nil;
_filters = nil;
_invviewBoxTransform = CGAffineTransformIdentity;
rendered = NO;
}
@@ -436,6 +438,19 @@ using namespace facebook::react;
return _masks ? [_masks objectForKey:maskName] : nil;
}
- (void)defineFilter:(RNSVGNode *)filter filterName:(NSString *)filterName
{
if (!_filters) {
_filters = [[NSMutableDictionary alloc] init];
}
[_filters setObject:filter forKey:filterName];
}
- (RNSVGNode *)getDefinedFilter:(NSString *)filterName
{
return _filters ? [_filters objectForKey:filterName] : nil;
}
- (CGRect)getContextBounds
{
return CGContextGetClipBoundingBox(UIGraphicsGetCurrentContext());

View File

@@ -0,0 +1,7 @@
typedef CF_ENUM(int32_t, RNSVGColorMatrixType) {
SVG_FECOLORMATRIX_TYPE_UNKNOWN,
SVG_FECOLORMATRIX_TYPE_MATRIX,
SVG_FECOLORMATRIX_TYPE_SATURATE,
SVG_FECOLORMATRIX_TYPE_HUEROTATE,
SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA
};

View File

@@ -0,0 +1,6 @@
typedef CF_ENUM(int32_t, RNSVGEdgeModeTypes) {
SVG_EDGEMODE_UNKNOWN,
SVG_EDGEMODE_DUPLICATE,
SVG_EDGEMODE_WRAP,
SVG_EDGEMODE_NONE
};

View File

@@ -0,0 +1,10 @@
#import "RNSVGColorMatrixType.h"
#import "RNSVGFilterPrimitive.h"
@interface RNSVGFeColorMatrix : RNSVGFilterPrimitive
@property (nonatomic, strong) NSString *in1;
@property (nonatomic, assign) RNSVGColorMatrixType type;
@property (nonatomic, strong) NSArray<NSNumber *> *values;
@end

View File

@@ -0,0 +1,174 @@
#import "RNSVGFeColorMatrix.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 RNSVGFeColorMatrix
#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 RNSVGFeColorMatrixProps>();
_props = defaultProps;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNSVGFeColorMatrixComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = static_cast<const RNSVGFeColorMatrixProps &>(*props);
self.in1 = RCTNSStringFromStringNilIfEmpty(newProps.in1);
if (newProps.values.size() > 0) {
NSMutableArray<NSNumber *> *valuesArray = [NSMutableArray new];
for (auto number : newProps.values) {
[valuesArray addObject:[NSNumber numberWithFloat:number]];
}
self.values = valuesArray;
}
self.type = [RNSVGConvert RNSVGColorMatrixTypeFromCppEquivalent:newProps.type];
setCommonFilterProps(newProps, self);
_props = std::static_pointer_cast<RNSVGFeColorMatrixProps const>(props);
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_in1 = nil;
_values = nil;
_type = RNSVGColorMatrixType::SVG_FECOLORMATRIX_TYPE_MATRIX;
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)setIn1:(NSString *)in1
{
if ([in1 isEqualToString:_in1]) {
return;
}
_in1 = in1;
[self invalidate];
}
- (void)setValues:(NSArray<NSNumber *> *)values
{
if (values == _values) {
return;
}
_values = values;
[self invalidate];
}
- (void)setType:(RNSVGColorMatrixType)type
{
if (type == _type) {
return;
}
_type = type;
[self invalidate];
}
#define deg2rad(degrees) ((M_PI * degrees) / 180)
- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results previousFilterResult:(CIImage *)previous
{
CIImage *inResults = self.in1 ? [results objectForKey:self.in1] : nil;
CIImage *inputImage = inResults ? inResults : previous;
CIFilter *filter = nil;
NSArray<NSNumber *> *array = self.values;
NSUInteger count = [array count];
switch (self.type) {
case SVG_FECOLORMATRIX_TYPE_UNKNOWN:
return nil;
case SVG_FECOLORMATRIX_TYPE_MATRIX: {
if (count != 20) {
return nil;
}
CGFloat v[20] = {0};
for (NSUInteger i = 0; i < count; i++) {
v[i] = (CGFloat)[array[i] doubleValue];
}
filter = [CIFilter filterWithName:@"CIColorMatrix"];
[filter setDefaults];
[filter setValue:[CIVector vectorWithX:v[0] Y:v[1] Z:v[2] W:v[3]] forKey:@"inputRVector"];
[filter setValue:[CIVector vectorWithX:v[5] Y:v[6] Z:v[7] W:v[8]] forKey:@"inputGVector"];
[filter setValue:[CIVector vectorWithX:v[10] Y:v[11] Z:v[12] W:v[13]] forKey:@"inputBVector"];
[filter setValue:[CIVector vectorWithX:v[15] Y:v[16] Z:v[17] W:v[18]] forKey:@"inputAVector"];
[filter setValue:[CIVector vectorWithX:v[4] Y:v[9] Z:v[14] W:v[19]] forKey:@"inputBiasVector"];
break;
}
case SVG_FECOLORMATRIX_TYPE_SATURATE: {
if (count != 1) {
return nil;
}
float saturation = [array[0] floatValue];
filter = [CIFilter filterWithName:@"CIColorControls"];
[filter setDefaults];
[filter setValue:[NSNumber numberWithFloat:saturation] forKey:@"inputSaturation"];
break;
}
case SVG_FECOLORMATRIX_TYPE_HUEROTATE: {
if (count != 1) {
return nil;
}
double deg = [array[0] doubleValue];
filter = [CIFilter filterWithName:@"CIHueAdjust"];
[filter setDefaults];
float radians = (float)deg2rad(deg);
[filter setValue:[NSNumber numberWithFloat:radians] forKey:@"inputAngle"];
break;
}
case SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA: {
if (count != 0) {
return nil;
}
filter = [CIFilter filterWithName:@"CIColorMatrix"];
[filter setDefaults];
CGFloat zero[4] = {0, 0, 0, 0};
CGFloat alpha[4] = {0.2125, 0.7154, 0.0721, 0};
[filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputRVector"];
[filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputGVector"];
[filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputBVector"];
[filter setValue:[CIVector vectorWithValues:alpha count:4] forKey:@"inputAVector"];
[filter setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputBiasVector"];
break;
}
default:
return nil;
}
[filter setValue:inputImage forKey:@"inputImage"];
return [filter valueForKey:@"outputImage"];
return nil;
}
#ifdef RCT_NEW_ARCH_ENABLED
Class<RCTComponentViewProtocol> RNSVGFeColorMatrixCls(void)
{
return RNSVGFeColorMatrix.class;
}
#endif // RCT_NEW_ARCH_ENABLED
@end

View File

@@ -0,0 +1,18 @@
#import "RNSVGNode.h"
@interface RNSVGFilter : RNSVGNode
@property (nonatomic, strong) RNSVGLength *x;
@property (nonatomic, strong) RNSVGLength *y;
@property (nonatomic, strong) RNSVGLength *width;
@property (nonatomic, strong) RNSVGLength *height;
@property (nonatomic, assign) RNSVGUnits filterUnits;
@property (nonatomic, assign) RNSVGUnits primitiveUnits;
- (CIImage *)applyFilter:(CIImage *)img
backgroundImg:(CIImage *)backgroundImg
renderableBounds:(CGRect)renderableBounds
canvasBounds:(CGRect)canvasBounds
ctm:(CGAffineTransform)ctm;
@end

View File

@@ -0,0 +1,232 @@
#import "RNSVGFilter.h"
#import "RNSVGFilterPrimitive.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 RNSVGFilter {
NSMutableDictionary<NSString *, CIImage *> *resultsMap;
}
#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 RNSVGFilterProps>();
_props = defaultProps;
}
return self;
}
#pragma mark - RCTComponentViewProtocol
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RNSVGFilterComponentDescriptor>();
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &newProps = static_cast<const RNSVGFilterProps &>(*props);
self.name = RCTNSStringFromStringNilIfEmpty(newProps.name);
id x = RNSVGConvertFollyDynamicToId(newProps.x);
if (x != nil) {
self.x = [RCTConvert RNSVGLength:x];
}
id y = RNSVGConvertFollyDynamicToId(newProps.y);
if (y != nil) {
self.y = [RCTConvert RNSVGLength:y];
}
id height = RNSVGConvertFollyDynamicToId(newProps.height);
if (height != nil) {
self.height = [RCTConvert RNSVGLength:height];
}
id width = RNSVGConvertFollyDynamicToId(newProps.width);
if (width != nil) {
self.width = [RCTConvert RNSVGLength:width];
}
self.filterUnits = [RNSVGConvert RNSVGUnitsFromFilterUnitsCppEquivalent:newProps.filterUnits];
self.primitiveUnits = [RNSVGConvert RNSVGUnitsFromPrimitiveUnitsCppEquivalent:newProps.primitiveUnits];
_props = std::static_pointer_cast<RNSVGFilterProps const>(props);
}
- (void)prepareForRecycle
{
[super prepareForRecycle];
_x = nil;
_y = nil;
_height = nil;
_width = nil;
_filterUnits = kRNSVGUnitsObjectBoundingBox;
_primitiveUnits = kRNSVGUnitsUserSpaceOnUse;
}
#endif // RCT_NEW_ARCH_ENABLED
- (id)init
{
if (self = [super init]) {
resultsMap = [NSMutableDictionary dictionary];
}
return self;
}
- (CIImage *)applyFilter:(CIImage *)img
backgroundImg:(CIImage *)backgroundImg
renderableBounds:(CGRect)renderableBounds
canvasBounds:(CGRect)canvasBounds
ctm:(CGAffineTransform)ctm
{
[resultsMap removeAllObjects];
[resultsMap setObject:img forKey:@"SourceGraphic"];
[resultsMap setObject:applySourceAlphaFilter(img) forKey:@"SourceAlpha"];
[resultsMap setObject:backgroundImg forKey:@"BackgroundImage"];
[resultsMap setObject:applySourceAlphaFilter(backgroundImg) forKey:@"BackgroundAlpha"];
CIImage *result = img;
RNSVGFilterPrimitive *currentFilter;
for (RNSVGNode *node in self.subviews) {
if ([node isKindOfClass:[RNSVGFilterPrimitive class]]) {
currentFilter = (RNSVGFilterPrimitive *)node;
result = [currentFilter applyFilter:resultsMap previousFilterResult:result];
if (currentFilter.result) {
[resultsMap setObject:result forKey:currentFilter.result];
}
} else {
RCTLogError(@"Invalid `Filter` subview: Filter children can only be `Fe...` components");
}
}
// Crop results to filter bounds
CIFilter *crop = [CIFilter filterWithName:@"CICrop"];
[crop setDefaults];
[crop setValue:result forKey:@"inputImage"];
CGFloat scaleX = ctm.a, scaleY = fabs(ctm.d);
CGFloat x, y, width, height;
if (self.filterUnits == kRNSVGUnitsUserSpaceOnUse) {
x = [self relativeOn:self.x relative:canvasBounds.size.width / scaleX];
y = [self relativeOn:self.y relative:canvasBounds.size.height / scaleY];
width = [self relativeOn:self.width relative:canvasBounds.size.width / scaleX];
height = [self relativeOn:self.height relative:canvasBounds.size.height / scaleY];
} else { // kRNSVGUnitsObjectBoundingBox
x = renderableBounds.origin.x + [self relativeOnFraction:self.x relative:renderableBounds.size.width];
y = renderableBounds.origin.y + [self relativeOnFraction:self.y relative:renderableBounds.size.height];
width = [self relativeOnFraction:self.width relative:renderableBounds.size.width];
height = [self relativeOnFraction:self.height relative:renderableBounds.size.height];
}
CGRect cropCGRect = CGRectMake(x, y, width, height);
cropCGRect = CGRectApplyAffineTransform(cropCGRect, ctm);
CIVector *cropRect = [CIVector vectorWithCGRect:cropCGRect];
[crop setValue:cropRect forKey:@"inputRectangle"];
return [crop valueForKey:@"outputImage"];
}
static CIFilter *sourceAlphaFilter()
{
CIFilter *sourceAlpha = [CIFilter filterWithName:@"CIColorMatrix"];
CGFloat zero[4] = {0, 0, 0, 0};
[sourceAlpha setDefaults];
[sourceAlpha setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputRVector"];
[sourceAlpha setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputGVector"];
[sourceAlpha setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputBVector"];
[sourceAlpha setValue:[CIVector vectorWithX:0.0 Y:0.0 Z:0.0 W:1.0] forKey:@"inputAVector"];
[sourceAlpha setValue:[CIVector vectorWithValues:zero count:4] forKey:@"inputBiasVector"];
return sourceAlpha;
}
static CIImage *applySourceAlphaFilter(CIImage *inputImage)
{
CIFilter *sourceAlpha = sourceAlphaFilter();
[sourceAlpha setValue:inputImage forKey:@"inputImage"];
return [sourceAlpha valueForKey:@"outputImage"];
}
- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
}
- (void)parseReference
{
[self.svgView defineFilter:self filterName:self.name];
}
- (void)setX:(RNSVGLength *)x
{
if ([x isEqualTo:_x]) {
return;
}
_x = x;
[self invalidate];
}
- (void)setY:(RNSVGLength *)y
{
if ([y isEqualTo:_y]) {
return;
}
_y = y;
[self invalidate];
}
- (void)setWidth:(RNSVGLength *)width
{
if ([width isEqualTo:_width]) {
return;
}
_width = width;
[self invalidate];
}
- (void)setHeight:(RNSVGLength *)height
{
if ([height isEqualTo:_height]) {
return;
}
_height = height;
[self invalidate];
}
- (void)setFilterUnits:(RNSVGUnits)filterUnits
{
if (filterUnits == _filterUnits) {
return;
}
_filterUnits = filterUnits;
[self invalidate];
}
- (void)setPrimitiveUnits:(RNSVGUnits)primitiveUnits
{
if (primitiveUnits == _primitiveUnits) {
return;
}
_primitiveUnits = primitiveUnits;
[self invalidate];
}
@end
#ifdef RCT_NEW_ARCH_ENABLED
Class<RCTComponentViewProtocol> RNSVGFilterCls(void)
{
return RNSVGFilter.class;
}
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,14 @@
#import "RNSVGNode.h"
@interface RNSVGFilterPrimitive : RNSVGNode
@property (nonatomic, strong) RNSVGLength *x;
@property (nonatomic, strong) RNSVGLength *y;
@property (nonatomic, strong) RNSVGLength *width;
@property (nonatomic, strong) RNSVGLength *height;
@property (nonatomic, strong) NSString *result;
- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results previousFilterResult:(CIImage *)previous;
- (CIImage *)cropResult:(CIImage *)result;
@end

View File

@@ -0,0 +1,110 @@
#import <RNSVGFilterPrimitive.h>
#import <RNSVGNode.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 RNSVGFilterPrimitive
#ifdef RCT_NEW_ARCH_ENABLED
- (void)prepareForRecycle
{
[super prepareForRecycle];
_x = nil;
_y = nil;
_height = nil;
_width = nil;
_result = nil;
}
#endif // RCT_NEW_ARCH_ENABLED
- (RNSVGPlatformView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return nil;
}
- (void)parseReference
{
}
- (void)setX:(RNSVGLength *)x
{
if ([x isEqualTo:_x]) {
return;
}
_x = x;
[self invalidate];
}
- (void)invalidate
{
self.dirty = false;
[super invalidate];
}
- (void)setY:(RNSVGLength *)y
{
if ([y isEqualTo:_y]) {
return;
}
_y = y;
[self invalidate];
}
- (void)setWidth:(RNSVGLength *)width
{
if ([width isEqualTo:_width]) {
return;
}
_width = width;
[self invalidate];
}
- (void)setHeight:(RNSVGLength *)height
{
if ([height isEqualTo:_height]) {
return;
}
_height = height;
[self invalidate];
}
- (void)setResult:(NSString *)result
{
if ([result isEqualToString:_result]) {
return;
}
_result = result;
[self invalidate];
}
- (CIImage *)applyFilter:(NSMutableDictionary<NSString *, CIImage *> *)results previousFilterResult:(CIImage *)previous
{
return previous;
}
- (CIImage *)cropResult:(CIImage *)result
{
CIFilter *filter = [CIFilter filterWithName:@"CICrop"];
[filter setDefaults];
[filter setValue:result forKey:@"inputImage"];
CGFloat x = [self relativeOnWidth:self.x];
CGFloat y = [self relativeOnHeight:self.y];
CGFloat width = [self relativeOnWidth:self.width];
CGFloat height = [self relativeOnHeight:self.height];
[filter setValue:[CIVector vectorWithX:x Y:y Z:width W:height] forKey:@"inputRectangle"];
return [filter valueForKey:@"outputImage"];
}
@end

View File

@@ -121,6 +121,8 @@ extern CGFloat const RNSVG_DEFAULT_FONT_SIZE;
- (CGFloat)relativeOn:(RNSVGLength *)length relative:(CGFloat)relative;
- (CGFloat)relativeOnFraction:(RNSVGLength *)length relative:(CGFloat)relative;
- (CGFloat)relativeOnWidth:(RNSVGLength *)length;
- (CGFloat)relativeOnHeight:(RNSVGLength *)length;

View File

@@ -446,6 +446,17 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
fontSize:[self getFontSizeFromContext]];
}
- (CGFloat)relativeOnFraction:(RNSVGLength *)length relative:(CGFloat)relative
{
RNSVGLengthUnitType unit = length.unit;
if (unit == SVG_LENGTHTYPE_NUMBER) {
return relative * length.value;
} else if (unit == SVG_LENGTHTYPE_PERCENTAGE) {
return length.value / 100 * relative;
}
return [self fromRelative:length];
}
- (CGFloat)relativeOn:(RNSVGLength *)length relative:(CGFloat)relative
{
RNSVGLengthUnitType unit = length.unit;
@@ -459,35 +470,17 @@ CGFloat const RNSVG_DEFAULT_FONT_SIZE = 12;
- (CGFloat)relativeOnWidth:(RNSVGLength *)length
{
RNSVGLengthUnitType unit = length.unit;
if (unit == SVG_LENGTHTYPE_NUMBER) {
return length.value;
} else if (unit == SVG_LENGTHTYPE_PERCENTAGE) {
return length.value / 100 * [self getCanvasWidth];
}
return [self fromRelative:length];
return [self relativeOn:length relative:[self getCanvasWidth]];
}
- (CGFloat)relativeOnHeight:(RNSVGLength *)length
{
RNSVGLengthUnitType unit = length.unit;
if (unit == SVG_LENGTHTYPE_NUMBER) {
return length.value;
} else if (unit == SVG_LENGTHTYPE_PERCENTAGE) {
return length.value / 100 * [self getCanvasHeight];
}
return [self fromRelative:length];
return [self relativeOn:length relative:[self getCanvasHeight]];
}
- (CGFloat)relativeOnOther:(RNSVGLength *)length
{
RNSVGLengthUnitType unit = length.unit;
if (unit == SVG_LENGTHTYPE_NUMBER) {
return length.value;
} else if (unit == SVG_LENGTHTYPE_PERCENTAGE) {
return length.value / 100 * [self getCanvasDiagonal];
}
return [self fromRelative:length];
return [self relativeOn:length relative:[self getCanvasDiagonal]];
}
- (CGFloat)fromRelative:(RNSVGLength *)length

View File

@@ -33,6 +33,7 @@
@property (nonatomic, assign) RNSVGVectorEffect vectorEffect;
@property (nonatomic, copy) NSArray<NSString *> *propList;
@property (nonatomic, assign) CGPathRef hitArea;
@property (nonatomic, strong) NSString *filter;
- (void)setHitArea:(CGPathRef)path;

View File

@@ -10,9 +10,11 @@
#import <React/RCTPointerEvents.h>
#import "RNSVGBezierElement.h"
#import "RNSVGClipPath.h"
#import "RNSVGFilter.h"
#import "RNSVGMarker.h"
#import "RNSVGMarkerPosition.h"
#import "RNSVGMask.h"
#import "RNSVGRenderUtils.h"
#import "RNSVGVectorEffect.h"
#import "RNSVGViewBox.h"
@@ -175,6 +177,15 @@ static RNSVGRenderable *_contextElement;
[self invalidate];
}
- (void)setFilter:(NSString *)filter
{
if ([_filter isEqualToString:filter]) {
return;
}
_filter = filter;
[self invalidate];
}
- (void)dealloc
{
CGPathRelease(_hitArea);
@@ -219,6 +230,7 @@ static RNSVGRenderable *_contextElement;
_strokeDashoffset = 0;
_vectorEffect = kRNSVGVectorEffectDefault;
_propList = nil;
_filter = nil;
}
#endif // RCT_NEW_ARCH_ENABLED
@@ -238,9 +250,7 @@ UInt32 saturate(CGFloat value)
[self beginTransparencyLayer:context];
if (self.mask) {
// https://www.w3.org/TR/SVG11/masking.html#MaskElement
RNSVGMask *_maskNode = (RNSVGMask *)[self.svgView getDefinedMask:self.mask];
if (self.mask || self.filter) {
CGFloat height = rect.size.height;
CGFloat width = rect.size.width;
CGFloat scale = 0.0;
@@ -266,123 +276,151 @@ UInt32 saturate(CGFloat value)
// Get current context transformations for offscreenContext
CGAffineTransform currentCTM = CGContextGetCTM(context);
// Allocate pixel buffer and bitmap context for mask
NSUInteger bytesPerPixel = 4;
NSUInteger bitsPerComponent = 8;
NSUInteger bytesPerRow = bytesPerPixel * scaledWidth;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UInt32 *pixels = (UInt32 *)calloc(npixels, sizeof(UInt32));
CGContextRef bcontext = CGBitmapContextCreate(
pixels,
scaledWidth,
scaledHeight,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
#if TARGET_OS_OSX // [macOS]
// on macOS currentCTM is not scaled properly with screen scale so we need to scale it manually
CGContextConcatCTM(bcontext, screenScaleCTM);
#endif // [macOS]
CGContextConcatCTM(bcontext, currentCTM);
CGImage *contentImage = [RNSVGRenderUtils renderToImage:self ctm:currentCTM rect:scaledRect clip:nil];
// Clip to mask bounds and render the mask
CGFloat x = [self relativeOn:[_maskNode x] relative:width];
CGFloat y = [self relativeOn:[_maskNode y] relative:height];
CGFloat w = [self relativeOn:[_maskNode maskwidth] relative:width];
CGFloat h = [self relativeOn:[_maskNode maskheight] relative:height];
CGRect maskBounds = CGRectApplyAffineTransform(CGRectMake(x, y, w, h), screenScaleCTM);
CGContextClipToRect(bcontext, maskBounds);
[_maskNode renderLayerTo:bcontext rect:scaledRect];
if (self.filter) {
// https://www.w3.org/TR/SVG11/filters.html#FilterElement
RNSVGFilter *filterNode = (RNSVGFilter *)[self.svgView getDefinedFilter:self.filter];
// Apply luminanceToAlpha filter primitive
// https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
UInt32 *currentPixel = pixels;
if (_maskNode.maskType == kRNSVGMaskTypeLuminance) {
for (NSUInteger i = 0; i < npixels; i++) {
UInt32 color = *currentPixel;
CIImage *content = [CIImage imageWithCGImage:contentImage];
UInt32 r = color & 0xFF;
UInt32 g = (color >> 8) & 0xFF;
UInt32 b = (color >> 16) & 0xFF;
CGImage *backgroundImage = CGBitmapContextCreateImage(context);
CIImage *background =
(backgroundImage != nil) ? [CIImage imageWithCGImage:backgroundImage] : [CIImage emptyImage];
CGFloat luma = (CGFloat)(0.299 * r + 0.587 * g + 0.144 * b);
*currentPixel = saturate(luma) << 24;
currentPixel++;
content = [filterNode applyFilter:content
backgroundImg:background
renderableBounds:self.pathBounds
canvasBounds:scaledRect
ctm:currentCTM];
CGImageRelease(contentImage);
contentImage = [[RNSVGRenderUtils sharedCIContext] createCGImage:content fromRect:scaledRect];
if (!self.mask) {
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
CGContextDrawImage(context, scaledRect, contentImage);
CGContextConcatCTM(context, currentCTM);
}
}
// Create mask image and release memory
CGImageRef maskImage = CGBitmapContextCreateImage(bcontext);
CGColorSpaceRelease(colorSpace);
CGContextRelease(bcontext);
free(pixels);
CGImageRelease(backgroundImage);
}
if (self.mask) {
// https://www.w3.org/TR/SVG11/masking.html#MaskElement
RNSVGMask *_maskNode = (RNSVGMask *)[self.svgView getDefinedMask:self.mask];
// Allocate pixel buffer and bitmap context for mask
NSUInteger bytesPerPixel = 4;
NSUInteger bitsPerComponent = 8;
NSUInteger bytesPerRow = bytesPerPixel * scaledWidth;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UInt32 *pixels = (UInt32 *)calloc(npixels, sizeof(UInt32));
CGContextRef bcontext = CGBitmapContextCreate(
pixels,
scaledWidth,
scaledHeight,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
#if TARGET_OS_OSX // [macOS]
// on macOS currentCTM is not scaled properly with screen scale so we need to scale it manually
CGContextConcatCTM(bcontext, screenScaleCTM);
#endif // [macOS]
CGContextConcatCTM(bcontext, currentCTM);
// Clip to mask bounds and render the mask
CGFloat x = [self relativeOn:[_maskNode x] relative:width];
CGFloat y = [self relativeOn:[_maskNode y] relative:height];
CGFloat w = [self relativeOn:[_maskNode maskwidth] relative:width];
CGFloat h = [self relativeOn:[_maskNode maskheight] relative:height];
CGRect maskBounds = CGRectApplyAffineTransform(CGRectMake(x, y, w, h), screenScaleCTM);
CGContextClipToRect(bcontext, maskBounds);
[_maskNode renderLayerTo:bcontext rect:scaledRect];
// Apply luminanceToAlpha filter primitive
// https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
UInt32 *currentPixel = pixels;
if (_maskNode.maskType == kRNSVGMaskTypeLuminance) {
for (NSUInteger i = 0; i < npixels; i++) {
UInt32 color = *currentPixel;
UInt32 r = color & 0xFF;
UInt32 g = (color >> 8) & 0xFF;
UInt32 b = (color >> 16) & 0xFF;
CGFloat luma = (CGFloat)(0.299 * r + 0.587 * g + 0.144 * b);
*currentPixel = saturate(luma) << 24;
currentPixel++;
}
}
// Create mask image and release memory
CGImageRef maskImage = CGBitmapContextCreateImage(bcontext);
CGColorSpaceRelease(colorSpace);
CGContextRelease(bcontext);
free(pixels);
#if !TARGET_OS_OSX // [macOS]
UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat];
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:rect.size format:format];
UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat];
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:rect.size format:format];
// Get the content image
UIImage *contentImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
CGContextConcatCTM(
rendererContext.CGContext, CGAffineTransformInvert(CGContextGetCTM(rendererContext.CGContext)));
CGContextConcatCTM(rendererContext.CGContext, currentCTM);
[self renderLayerTo:rendererContext.CGContext rect:scaledRect];
}];
// Blend current element and mask
UIImage *blendedImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
CGContextConcatCTM(
rendererContext.CGContext, CGAffineTransformInvert(CGContextGetCTM(rendererContext.CGContext)));
CGContextTranslateCTM(rendererContext.CGContext, 0.0, scaledHeight);
CGContextScaleCTM(rendererContext.CGContext, 1.0, -1.0);
// Blend current element and mask
UIImage *blendedImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
CGContextConcatCTM(
rendererContext.CGContext, CGAffineTransformInvert(CGContextGetCTM(rendererContext.CGContext)));
CGContextTranslateCTM(rendererContext.CGContext, 0.0, scaledHeight);
CGContextScaleCTM(rendererContext.CGContext, 1.0, -1.0);
CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeCopy);
CGContextDrawImage(rendererContext.CGContext, scaledRect, maskImage);
CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeSourceIn);
CGContextDrawImage(rendererContext.CGContext, scaledRect, contentImage);
}];
CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeCopy);
CGContextDrawImage(rendererContext.CGContext, scaledRect, maskImage);
CGContextSetBlendMode(rendererContext.CGContext, kCGBlendModeSourceIn);
CGContextDrawImage(rendererContext.CGContext, scaledRect, contentImage.CGImage);
}];
// Render blended result into current render context
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
[blendedImage drawInRect:scaledRect];
CGContextConcatCTM(context, currentCTM);
// Render blended result into current render context
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
[blendedImage drawInRect:scaledRect];
CGContextConcatCTM(context, currentCTM);
// Render blended result into current render context
CGImageRelease(maskImage);
// Render blended result into current render context
CGImageRelease(maskImage);
#else // [macOS
// Render content of current SVG Renderable to image
UIGraphicsBeginImageContextWithOptions(scaledRect.size, NO, 1.0);
CGContextRef newContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(newContext, CGAffineTransformInvert(CGContextGetCTM(newContext)));
CGContextConcatCTM(newContext, screenScaleCTM);
CGContextConcatCTM(newContext, currentCTM);
[self renderLayerTo:newContext rect:scaledRect];
CGImageRef contentImage = CGBitmapContextCreateImage(newContext);
UIGraphicsEndImageContext();
UIGraphicsBeginImageContextWithOptions(scaledRect.size, NO, 1.0);
CGContextRef newContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(newContext, CGAffineTransformInvert(CGContextGetCTM(newContext)));
CGContextConcatCTM(newContext, screenScaleCTM);
CGContextConcatCTM(newContext, currentCTM);
[self renderLayerTo:newContext rect:scaledRect];
CGImageRef contentImage = CGBitmapContextCreateImage(newContext);
UIGraphicsEndImageContext();
// Blend current element and mask
UIGraphicsBeginImageContextWithOptions(scaledRect.size, NO, 1.0);
newContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(newContext, CGAffineTransformInvert(CGContextGetCTM(newContext)));
// Blend current element and mask
UIGraphicsBeginImageContextWithOptions(scaledRect.size, NO, 0.0);
newContext = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(newContext, 0.0, height);
CGContextScaleCTM(newContext, 1.0, -1.0);
CGContextSetBlendMode(newContext, kCGBlendModeCopy);
CGContextDrawImage(newContext, scaledRect, maskImage);
CGImageRelease(maskImage);
CGContextSetBlendMode(newContext, kCGBlendModeCopy);
CGContextDrawImage(newContext, scaledRect, maskImage);
CGImageRelease(maskImage);
CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
CGContextDrawImage(newContext, scaledRect, contentImage);
CGImageRelease(contentImage);
CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
CGContextDrawImage(newContext, scaledRect, contentImage);
CGImageRelease(contentImage);
CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
UIGraphicsEndImageContext();
CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
UIGraphicsEndImageContext();
// Render blended result into current render context
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
CGContextDrawImage(context, rect, blendedImage);
CGContextConcatCTM(context, currentCTM);
CGImageRelease(blendedImage);
// Render blended result into current render context
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
CGContextDrawImage(context, rect, blendedImage);
CGContextConcatCTM(context, currentCTM);
CGImageRelease(blendedImage);
#endif // macOS]
}
CGImageRelease(contentImage);
} else {
[self renderLayerTo:context rect:rect];
}

View File

@@ -11,6 +11,8 @@
#import <React/RCTConvert.h>
#import "RCTConvert+RNSVG.h"
#import "RNSVGCGFCRule.h"
#import "RNSVGColorMatrixType.h"
#import "RNSVGEdgeModeTypes.h"
#import "RNSVGLength.h"
#import "RNSVGMaskType.h"
#import "RNSVGPathParser.h"

View File

@@ -51,6 +51,27 @@ RCT_ENUM_CONVERTER(
kRNSVGMaskTypeLuminance,
intValue)
RCT_ENUM_CONVERTER(
RNSVGEdgeModeTypes,
(@{
@"duplicate" : @(SVG_EDGEMODE_DUPLICATE),
@"wrap" : @(SVG_EDGEMODE_WRAP),
@"none" : @(SVG_EDGEMODE_NONE),
}),
SVG_FECOLORMATRIX_TYPE_UNKNOWN,
intValue)
RCT_ENUM_CONVERTER(
RNSVGColorMatrixType,
(@{
@"matrix" : @(SVG_FECOLORMATRIX_TYPE_MATRIX),
@"saturate" : @(SVG_FECOLORMATRIX_TYPE_SATURATE),
@"hueRotate" : @(SVG_FECOLORMATRIX_TYPE_HUEROTATE),
@"luminanceToAlpha" : @(SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA),
}),
SVG_FECOLORMATRIX_TYPE_UNKNOWN,
intValue)
+ (RNSVGBrush *)RNSVGBrush:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {

View File

@@ -0,0 +1,17 @@
#ifdef RCT_NEW_ARCH_ENABLED
#import <react/renderer/components/rnsvg/Props.h>
#import "RNSVGColorMatrixType.h"
#import "RNSVGEdgeModeTypes.h"
#import "RNSVGUnits.h"
namespace react = facebook::react;
@interface RNSVGConvert : NSObject
+ (RNSVGUnits)RNSVGUnitsFromFilterUnitsCppEquivalent:(react::RNSVGFilterFilterUnits)svgUnits;
+ (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimitiveUnits)svgUnits;
+ (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type;
@end
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,42 @@
#import "RNSVGConvert.h"
#ifdef RCT_NEW_ARCH_ENABLED
@implementation RNSVGConvert
+ (RNSVGUnits)RNSVGUnitsFromFilterUnitsCppEquivalent:(react::RNSVGFilterFilterUnits)svgUnits
{
switch (svgUnits) {
case react::RNSVGFilterFilterUnits::UserSpaceOnUse:
return kRNSVGUnitsUserSpaceOnUse;
case react::RNSVGFilterFilterUnits::ObjectBoundingBox:
return kRNSVGUnitsObjectBoundingBox;
}
}
+ (RNSVGUnits)RNSVGUnitsFromPrimitiveUnitsCppEquivalent:(react::RNSVGFilterPrimitiveUnits)svgUnits
{
switch (svgUnits) {
case react::RNSVGFilterPrimitiveUnits::UserSpaceOnUse:
return kRNSVGUnitsUserSpaceOnUse;
case react::RNSVGFilterPrimitiveUnits::ObjectBoundingBox:
return kRNSVGUnitsObjectBoundingBox;
}
}
+ (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type;
{
switch (type) {
case react::RNSVGFeColorMatrixType::Matrix:
return SVG_FECOLORMATRIX_TYPE_MATRIX;
case react::RNSVGFeColorMatrixType::Saturate:
return SVG_FECOLORMATRIX_TYPE_SATURATE;
case react::RNSVGFeColorMatrixType::HueRotate:
return SVG_FECOLORMATRIX_TYPE_HUEROTATE;
case react::RNSVGFeColorMatrixType::LuminanceToAlpha:
return SVG_FECOLORMATRIX_TYPE_LUMINANCETOALPHA;
}
}
@end
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -1,4 +1,5 @@
#import "RNSVGContextBrush.h"
#import "RNSVGFilterPrimitive.h"
#import "RNSVGGroup.h"
#import "RNSVGLength.h"
#import "RNSVGPainterBrush.h"
@@ -167,6 +168,7 @@ void setCommonRenderableProps(const T &renderableProps, RNSVGRenderable *rendera
}
renderableNode.propList = propArray;
}
renderableNode.filter = RCTNSStringFromStringNilIfEmpty(renderableProps.filter);
}
template <typename T>
@@ -193,6 +195,28 @@ void setCommonGroupProps(const T &groupProps, RNSVGGroup *groupNode)
}
}
template <typename T>
void setCommonFilterProps(const T &filterProps, RNSVGFilterPrimitive *filterPrimitiveNode)
{
id x = RNSVGConvertFollyDynamicToId(filterProps.x);
if (x != nil) {
filterPrimitiveNode.x = [RCTConvert RNSVGLength:x];
}
id y = RNSVGConvertFollyDynamicToId(filterProps.y);
if (y != nil) {
filterPrimitiveNode.y = [RCTConvert RNSVGLength:y];
}
id height = RNSVGConvertFollyDynamicToId(filterProps.height);
if (height != nil) {
filterPrimitiveNode.height = [RCTConvert RNSVGLength:height];
}
id width = RNSVGConvertFollyDynamicToId(filterProps.width);
if (width != nil) {
filterPrimitiveNode.width = [RCTConvert RNSVGLength:width];
}
filterPrimitiveNode.result = RCTNSStringFromStringNilIfEmpty(filterProps.result);
}
template <typename T>
void setCommonTextProps(const T &textProps, RNSVGText *textNode)
{

View File

@@ -0,0 +1,11 @@
#import "RNSVGRenderable.h"
@interface RNSVGRenderUtils : NSObject
+ (CIContext *)sharedCIContext;
+ (CGImage *)renderToImage:(RNSVGRenderable *)renderable
ctm:(CGAffineTransform)ctm
rect:(CGRect)rect
clip:(CGRect *)clip;
@end

View File

@@ -0,0 +1,35 @@
#import "RNSVGRenderUtils.h"
@implementation RNSVGRenderUtils
+ (CIContext *)sharedCIContext
{
static CIContext *sharedCIContext = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedCIContext = [[CIContext alloc] init];
});
return sharedCIContext;
}
+ (CGImage *)renderToImage:(RNSVGRenderable *)renderable
ctm:(CGAffineTransform)ctm
rect:(CGRect)rect
clip:(CGRect *)clip
{
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1.0);
CGContextRef cgContext = UIGraphicsGetCurrentContext();
CGContextConcatCTM(cgContext, CGAffineTransformInvert(CGContextGetCTM(cgContext)));
CGContextConcatCTM(cgContext, ctm);
if (clip) {
CGContextClipToRect(cgContext, *clip);
}
[renderable renderLayerTo:cgContext rect:rect];
CGImageRef contentImage = CGBitmapContextCreateImage(cgContext);
UIGraphicsEndImageContext();
return contentImage;
}
@end

View File

@@ -0,0 +1,5 @@
#import "RNSVGFilterPrimitiveManager.h"
@interface RNSVGFeColorMatrixManager : RNSVGFilterPrimitiveManager
@end

View File

@@ -0,0 +1,18 @@
#import "RNSVGFeColorMatrixManager.h"
#import "RNSVGColorMatrixType.h"
#import "RNSVGFeColorMatrix.h"
@implementation RNSVGFeColorMatrixManager
RCT_EXPORT_MODULE()
- (RNSVGFeColorMatrix *)node
{
return [RNSVGFeColorMatrix new];
}
RCT_EXPORT_VIEW_PROPERTY(in1, NSString)
RCT_EXPORT_VIEW_PROPERTY(type, RNSVGColorMatrixType)
RCT_EXPORT_VIEW_PROPERTY(values, NSArray<NSNumber *>)
@end

View File

@@ -0,0 +1,5 @@
#import "RNSVGNodeManager.h"
@interface RNSVGFilterManager : RNSVGNodeManager
@end

View File

@@ -0,0 +1,26 @@
#import "RNSVGFilterManager.h"
#import "RNSVGFilter.h"
@implementation RNSVGFilterManager
RCT_EXPORT_MODULE()
- (RNSVGFilter *)node
{
return [RNSVGFilter new];
}
RCT_EXPORT_VIEW_PROPERTY(x, RNSVGLength *)
RCT_EXPORT_VIEW_PROPERTY(y, RNSVGLength *)
RCT_CUSTOM_VIEW_PROPERTY(width, id, RNSVGFilter)
{
view.width = [RCTConvert RNSVGLength:json];
}
RCT_CUSTOM_VIEW_PROPERTY(height, id, RNSVGFilter)
{
view.height = [RCTConvert RNSVGLength:json];
}
RCT_EXPORT_VIEW_PROPERTY(filterUnits, RNSVGUnits)
RCT_EXPORT_VIEW_PROPERTY(primitiveUnits, RNSVGUnits)
@end

View File

@@ -0,0 +1,5 @@
#import "RNSVGNodeManager.h"
@interface RNSVGFilterPrimitiveManager : RNSVGNodeManager
@end

View File

@@ -0,0 +1,25 @@
#import "RNSVGFilterPrimitiveManager.h"
#import "RNSVGFilterPrimitive.h"
@implementation RNSVGFilterPrimitiveManager
RCT_EXPORT_MODULE()
- (RNSVGFilterPrimitive *)node
{
return [RNSVGFilterPrimitive new];
}
RCT_EXPORT_VIEW_PROPERTY(x, RNSVGLength *)
RCT_EXPORT_VIEW_PROPERTY(y, RNSVGLength *)
RCT_CUSTOM_VIEW_PROPERTY(width, id, RNSVGFilterPrimitive)
{
view.width = [RCTConvert RNSVGLength:json];
}
RCT_CUSTOM_VIEW_PROPERTY(height, id, RNSVGFilterPrimitive)
{
view.height = [RCTConvert RNSVGLength:json];
}
RCT_EXPORT_VIEW_PROPERTY(result, NSString)
@end

View File

@@ -37,5 +37,6 @@ RCT_EXPORT_VIEW_PROPERTY(strokeDashoffset, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(vectorEffect, int)
RCT_EXPORT_VIEW_PROPERTY(propList, NSArray<NSString *>)
RCT_EXPORT_VIEW_PROPERTY(filter, NSString)
@end

View File

@@ -13,86 +13,13 @@ import {
ScrollView,
TouchableHighlight,
TouchableOpacity,
SafeAreaView,
} from 'react-native';
import {Modal, Platform} from 'react-native';
import {Svg, Circle, Line} from 'react-native-svg';
import * as examples from './src/examples';
const hairline = StyleSheet.hairlineWidth;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
alignItems: 'center',
overflow: 'hidden',
},
contentContainer: {
alignSelf: 'stretch',
borderTopWidth: hairline,
borderTopColor: '#ccc',
borderBottomWidth: hairline,
borderBottomColor: '#ccc',
flexWrap: 'wrap',
flexDirection: 'row',
marginHorizontal: 10,
},
welcome: {
padding: 10,
color: '#f60',
fontSize: 18,
fontWeight: 'bold',
},
link: {
height: 40,
alignSelf: 'stretch',
width: Dimensions.get('window').width / 2 - 10,
},
title: {
marginLeft: 10,
},
cell: {
height: 40,
paddingHorizontal: 10,
alignSelf: 'stretch',
alignItems: 'center',
flexDirection: 'row',
borderTopWidth: hairline,
borderTopColor: '#ccc',
marginTop: -hairline,
backgroundColor: 'transparent',
},
close: {
position: 'absolute',
right: 20,
top: 40,
},
scroll: {
position: 'absolute',
top: 30,
right: 10,
bottom: 20,
left: 10,
backgroundColor: '#fff',
},
scrollContent: {
borderTopWidth: hairline,
borderTopColor: '#ccc',
},
example: {
paddingVertical: 25,
alignSelf: 'stretch',
alignItems: 'center',
borderBottomWidth: hairline,
borderBottomColor: '#ccc',
},
sampleTitle: {
marginHorizontal: 15,
fontSize: 16,
color: '#666',
},
});
import {commonStyles} from './src/commonStyles';
const names: (keyof typeof examples)[] = [
'Svg',
@@ -116,6 +43,8 @@ const names: (keyof typeof examples)[] = [
'Transforms',
'Markers',
'Mask',
'Filters',
'FilterImage',
];
const initialState = {
@@ -142,8 +71,8 @@ export default class SvgExample extends Component {
content: (
<View>
{samples.map((Sample, i) => (
<View style={styles.example} key={`sample-${i}`}>
<Text style={styles.sampleTitle}>{Sample.title}</Text>
<View style={commonStyles.example} key={`sample-${i}`}>
<Text style={commonStyles.sampleTitle}>{Sample.title}</Text>
<Sample />
</View>
))}
@@ -171,9 +100,9 @@ export default class SvgExample extends Component {
underlayColor="#ccc"
key={`example-${name}`}
onPress={() => this.show(name)}>
<View style={styles.cell}>
<View style={commonStyles.cell}>
{icon}
<Text style={styles.title}>{name}</Text>
<Text style={commonStyles.title}>{name}</Text>
</View>
</TouchableHighlight>
);
@@ -182,13 +111,15 @@ export default class SvgExample extends Component {
modalContent = () => (
<>
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
scrollEnabled={this.state.scroll}>
{this.state.content}
</ScrollView>
<View style={styles.close}>
<SafeAreaView style={{flex: 1}}>
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
scrollEnabled={this.state.scroll}>
{this.state.content}
</ScrollView>
</SafeAreaView>
<SafeAreaView style={styles.close}>
<TouchableOpacity activeOpacity={0.7} onPress={this.hide}>
<Svg height="20" width="20">
<Circle cx="10" cy="10" r="10" fill="red" />
@@ -196,14 +127,14 @@ export default class SvgExample extends Component {
<Line x1="4" y1="16" x2="16" y2="4" stroke="#fff" strokeWidth="2" />
</Svg>
</TouchableOpacity>
</View>
</SafeAreaView>
</>
);
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>SVG library for React Apps</Text>
<SafeAreaView style={styles.container}>
<Text style={commonStyles.welcome}>SVG library for React Apps</Text>
<View style={styles.contentContainer}>{this.getExamples()}</View>
{(Platform.OS === 'windows' || Platform.OS === 'macos') &&
this.state.modal ? (
@@ -217,7 +148,50 @@ export default class SvgExample extends Component {
{this.modalContent()}
</Modal>
)}
</View>
</SafeAreaView>
);
}
}
const hairline = StyleSheet.hairlineWidth;
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
alignItems: 'center',
overflow: 'hidden',
},
contentContainer: {
alignSelf: 'stretch',
borderTopWidth: hairline,
borderTopColor: '#ccc',
borderBottomWidth: hairline,
borderBottomColor: '#ccc',
flexWrap: 'wrap',
flexDirection: 'row',
marginHorizontal: 10,
},
link: {
height: 40,
alignSelf: 'stretch',
width: Dimensions.get('window').width / 2 - 10,
},
close: {
position: 'absolute',
right: 20,
top: 20,
},
scroll: {
position: 'absolute',
top: 30,
right: 10,
bottom: 20,
left: 10,
backgroundColor: '#fff',
},
scrollContent: {
borderTopWidth: hairline,
borderTopColor: '#ccc',
},
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,41 @@
import {StyleSheet} from 'react-native';
const hairline = StyleSheet.hairlineWidth;
export const commonStyles = StyleSheet.create({
welcome: {
padding: 10,
color: '#f60',
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
link: {
height: 40,
},
title: {
marginLeft: 10,
},
cell: {
height: 40,
paddingHorizontal: 10,
alignSelf: 'stretch',
alignItems: 'center',
flexDirection: 'row',
borderTopWidth: hairline,
borderTopColor: '#ccc',
marginTop: -hairline,
},
example: {
paddingVertical: 25,
alignSelf: 'stretch',
alignItems: 'center',
borderBottomWidth: hairline,
borderBottomColor: '#ccc',
},
sampleTitle: {
marginHorizontal: 15,
fontSize: 16,
color: '#666',
},
});

View File

@@ -19,6 +19,8 @@ import * as Reanimated from './examples/Reanimated';
import * as Transforms from './examples/Transforms';
import * as Markers from './examples/Markers';
import * as Mask from './examples/Mask';
import * as Filters from './examples/Filters';
import * as FilterImage from './examples/FilterImage';
export {
Svg,
@@ -42,4 +44,6 @@ export {
Transforms,
Markers,
Mask,
Filters,
FilterImage,
};

View File

@@ -0,0 +1,142 @@
import React, {useState} from 'react';
import {
Dimensions,
FlatList,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import {FilterImage, Filters} from 'react-native-svg/filter-image';
const img = require('../../assets/office.jpg');
const normal: Filters = [];
const losAngeles: Filters = [
{
name: 'colorMatrix',
type: 'matrix',
values: [1.8, 0, 0, 0, 0, 0, 1.3, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0],
},
];
const lagos: Filters = [
{
name: 'colorMatrix',
type: 'matrix',
values: [
1.4, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1.5, 0, 0, 0, 0, 0, 0.9, 0,
],
},
];
const tokyo: Filters = [
{name: 'colorMatrix', type: 'saturate', values: [1.5]},
{
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 saturated: Filters = [
{name: 'colorMatrix', type: 'saturate', values: [1.5]},
];
const boring: Filters = [
{
name: 'colorMatrix',
type: 'matrix',
values: [
0.6965, 0.3845, 0.0945, 0, 0, 0.1745, 0.8430000000000001, 0.084, 0, 0,
0.136, 0.267, 0.5655, 0, 0, 0, 0, 0, 1, 0,
],
},
];
const filters = {
normal,
losAngeles,
lagos,
tokyo,
saturated,
boring,
} as const;
type FilterKeys =
| 'normal'
| 'losAngeles'
| 'lagos'
| 'tokyo'
| 'saturated'
| 'boring';
const filterKeys = Object.keys(filters) as FilterKeys[];
const FilterImagePickerExample = () => {
const [currentFilter, setCurrentFilter] = useState<FilterKeys>('normal');
return (
<View style={styles.container}>
<FilterImage
style={styles.image}
source={img}
filters={filters[currentFilter]}
/>
<View>
<FlatList
data={filterKeys}
horizontal
style={styles.list}
contentContainerStyle={styles.listElement}
renderItem={({item}) => {
return (
<TouchableOpacity onPress={() => setCurrentFilter(item)}>
<FilterImage
style={styles.listElementImage}
source={img}
filters={filters[item]}
/>
<Text numberOfLines={1} style={styles.listElementTitle}>
{item}
</Text>
</TouchableOpacity>
);
}}
/>
</View>
</View>
);
};
FilterImagePickerExample.title = 'Filter picker';
const styles = StyleSheet.create({
container: {
flex: 1,
height: Dimensions.get('window').height - 150,
width: '100%',
},
image: {flex: 1, width: '100%', height: '100%'},
list: {
marginTop: 8,
marginHorizontal: 8,
},
listElement: {gap: 8},
listElementImage: {width: 70, height: 70},
listElementTitle: {
width: 70,
textAlign: 'center',
marginTop: 2,
marginBottom: 8,
},
});
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={img}
width={30}
height={30}
/>
);
const samples = [FilterImagePickerExample];
export {icon, samples};

View File

@@ -0,0 +1,29 @@
import React from 'react';
import {View} from 'react-native';
import {FilterImage} from 'react-native-svg/filter-image';
const testImage = require('../../assets/image.jpg');
const FilterImageLocalExample = () => {
return (
<View>
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={testImage}
/>
</View>
);
};
FilterImageLocalExample.title = 'Local image with filter';
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={testImage}
width={30}
height={30}
/>
);
const samples = [FilterImageLocalExample];
export {icon, samples};

View File

@@ -0,0 +1,52 @@
import React from 'react';
import {View} from 'react-native';
import {FilterImage} from 'react-native-svg/filter-image';
const testSource = {
uri: 'https://cdn.pixabay.com/photo/2023/03/17/11/39/mountain-7858482_1280.jpg',
};
const FilterImageRemoteExample = () => {
return (
<View>
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [3]}]}
source={testSource}
style={{width: 200, height: 200}}
/>
</View>
);
};
FilterImageRemoteExample.title = 'Remote image with filter';
const FilterImageFewFiltersExample = () => {
return (
<View>
<FilterImage
filters={[
{name: 'colorMatrix', type: 'saturate', values: [10]},
{
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',
},
]}
source={testSource}
style={{width: 200, height: 200}}
/>
</View>
);
};
FilterImageFewFiltersExample.title = 'Remote image with filters';
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={testSource}
width={30}
height={30}
/>
);
const samples = [FilterImageRemoteExample, FilterImageFewFiltersExample];
export {icon, samples};

View File

@@ -0,0 +1,4 @@
import * as LocalImage from './LocalImage';
import * as RemoteImage from './RemoteImage';
import * as FilterPicker from './FilterPicker';
export {LocalImage, RemoteImage, FilterPicker};

View File

@@ -0,0 +1,65 @@
import React from 'react';
import {StyleSheet, Text, TouchableHighlight, View} from 'react-native';
import {FilterImage} from 'react-native-svg/filter-image';
import {commonStyles} from '../../commonStyles';
import * as examples from './examples';
const FilterImageList = () => {
const [example, setExample] = React.useState<keyof typeof examples | null>(
null,
);
if (example) {
return (
<>
{examples[example].samples.map((Sample, i) => (
<View style={commonStyles.example} key={`sample-${i}`}>
<Text style={commonStyles.sampleTitle}>{Sample.title}</Text>
<Sample />
</View>
))}
</>
);
}
return (
<View style={styles.container}>
<Text style={commonStyles.welcome}>Filter Image</Text>
{Object.keys(examples).map((element, i) => {
const name = element as keyof typeof examples;
return (
<TouchableHighlight
style={styles.link}
underlayColor="#ccc"
key={`example-${name}`}
onPress={() => setExample(name)}>
<View style={commonStyles.cell}>
{examples[name].icon}
<Text style={commonStyles.title}>{name}</Text>
</View>
</TouchableHighlight>
);
})}
</View>
);
};
FilterImageList.title = '';
const styles = StyleSheet.create({
container: {width: '100%'},
link: {height: 40},
});
const icon = (
<FilterImage
filters={[{name: 'colorMatrix', type: 'saturate', values: [0.5]}]}
source={require('../../assets/image.jpg')}
width={30}
height={30}
/>
);
const samples = [FilterImageList];
export {icon, samples};

View File

@@ -0,0 +1,130 @@
import React, {Component} from 'react';
import {Svg, Circle, FeColorMatrix, Filter, G} from 'react-native-svg';
class ReferenceExample extends Component {
static title = 'Reference';
render() {
return (
<Svg height="150" width="150">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
</Svg>
);
}
}
class IdentityExample extends Component {
static title = 'Identity matrix';
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<FeColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
</Filter>
<G filter="url(#filter)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
</G>
</Svg>
);
}
}
class RgbToGreenExample extends Component {
static title = 'RGB to Green';
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<FeColorMatrix
values="0 0 0 0 0
1 1 1 1 0
0 0 0 0 0
0 0 0 1 0"
/>
</Filter>
<G filter="url(#filter)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
</G>
</Svg>
);
}
}
class SaturateExample extends Component {
static title = 'Saturate';
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<FeColorMatrix type="saturate" values="0.2" />
</Filter>
<G filter="url(#filter)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
</G>
</Svg>
);
}
}
class HueRotateExample extends Component {
static title = 'Hue Rotate';
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<FeColorMatrix type="hueRotate" values="180" />
</Filter>
<G filter="url(#filter)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
</G>
</Svg>
);
}
}
class LuminanceToAlphaExample extends Component {
static title = 'Luminance to alpha';
render() {
return (
<Svg height="150" width="150">
<Filter id="filter">
<FeColorMatrix type="luminanceToAlpha" />
</Filter>
<G filter="url(#filter)">
<Circle cx="75" cy="50" r="40" fill="blue" fillOpacity="0.5" />
<Circle cx="55" cy="90" r="40" fill="green" fillOpacity="0.5" />
<Circle cx="95" cy="90" r="40" fill="red" fillOpacity="0.5" />
</G>
</Svg>
);
}
}
const icon = (
<Svg height="30" width="30" viewBox="0 0 20 20">
<Filter id="filter">
<FeColorMatrix values="0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0" />
</Filter>
<G filter="url(#filter)">
<Circle cx="10" cy="7.5" r="5" fill="blue" fillOpacity="0.5" />
<Circle cx="7.5" cy="12.5" r="5" fill="green" fillOpacity="0.5" />
<Circle cx="12.5" cy="12.5" r="5" fill="red" fillOpacity="0.5" />
</G>
</Svg>
);
const samples = [
ReferenceExample,
IdentityExample,
RgbToGreenExample,
SaturateExample,
HueRotateExample,
LuminanceToAlphaExample,
];
export {icon, samples};

View File

@@ -0,0 +1,56 @@
import React, {useEffect} from 'react';
import Animated, {
AnimatedProps,
useAnimatedProps,
useSharedValue,
withRepeat,
withTiming,
} from 'react-native-reanimated';
import {
Circle,
FeColorMatrix,
FeColorMatrixProps,
Filter,
Image,
Svg,
} from 'react-native-svg';
const AnimatedFeColorMatrix = Animated.createAnimatedComponent(
FeColorMatrix as any,
) as React.FunctionComponent<AnimatedProps<FeColorMatrixProps>>;
const ReanimatedHueRotateExample = () => {
const hue = useSharedValue(0);
useEffect(() => {
hue.value = withRepeat(withTiming(360, {duration: 2000}), -1, true);
}, []);
const animatedProps = useAnimatedProps(() => {
return {values: [hue.value]};
});
return (
<Svg height="100" width="150">
<Filter id="filter">
<AnimatedFeColorMatrix type="hueRotate" animatedProps={animatedProps} />
</Filter>
<Image
href="https://cdn.pixabay.com/photo/2024/05/26/00/40/lizard-8787888_1280.jpg"
height="100"
width="150"
filter="url(#filter)"
/>
</Svg>
);
};
ReanimatedHueRotateExample.title = 'Reanimated Hue Rotate';
const icon = (
<Svg height="30" width="30" viewBox="0 0 20 20">
<Circle cx="10" cy="7.5" r="5" fill="blue" fillOpacity="0.5" />
<Circle cx="7.5" cy="12.5" r="5" fill="green" fillOpacity="0.5" />
<Circle cx="12.5" cy="12.5" r="5" fill="red" fillOpacity="0.5" />
</Svg>
);
const samples = [ReanimatedHueRotateExample];
export {icon, samples};

View File

@@ -0,0 +1,3 @@
import * as FeColorMatrix from './FeColorMatrix';
import * as ReanimatedFeColorMatrix from './ReanimatedFeColorMatrix';
export {FeColorMatrix, ReanimatedFeColorMatrix};

View File

@@ -0,0 +1,64 @@
import React from 'react';
import {StyleSheet, Text, TouchableHighlight, View} from 'react-native';
import {Circle, Svg} from 'react-native-svg';
import * as examples from './examples';
import {commonStyles} from '../../commonStyles';
const FiltersList = () => {
const [example, setExample] = React.useState<keyof typeof examples | null>(
null,
);
if (example) {
return (
<>
{examples[example].samples.map((Sample, i) => (
<View style={commonStyles.example} key={`sample-${i}`}>
<Text style={commonStyles.sampleTitle}>{Sample.title}</Text>
<Sample />
</View>
))}
</>
);
}
return (
<View style={styles.container}>
<Text style={commonStyles.welcome}>SVG Filters</Text>
{Object.keys(examples).map((element, i) => {
const name = element as keyof typeof examples;
return (
<TouchableHighlight
style={styles.link}
underlayColor="#ccc"
key={`example-${name}`}
onPress={() => setExample(name)}>
<View style={commonStyles.cell}>
{examples[name].icon}
<Text style={commonStyles.title}>{name}</Text>
</View>
</TouchableHighlight>
);
})}
</View>
);
};
FiltersList.title = '';
const styles = StyleSheet.create({
container: {width: '100%'},
link: {height: 40},
});
const icon = (
<Svg height="30" width="30" viewBox="0 0 20 20">
<Circle cx="10" cy="7.5" r="5" fill="blue" fillOpacity="0.5" />
<Circle cx="7.5" cy="12.5" r="5" fill="green" fillOpacity="0.5" />
<Circle cx="12.5" cy="12.5" r="5" fill="red" fillOpacity="0.5" />
</Svg>
);
const samples = [FiltersList];
export {icon, samples};

View File

@@ -0,0 +1,6 @@
{
"main": "../lib/commonjs/filter-image/index",
"module": "../lib/module/filter-image/index",
"react-native": "../src/filter-image/index",
"types": "../lib/typescript/filter-image/index"
}

View File

@@ -19,6 +19,7 @@
"lib",
"src",
"css",
"filter-image",
"RNSVG.podspec",
"!android/build",
"windows",
@@ -64,7 +65,8 @@
},
"dependencies": {
"css-select": "^5.1.0",
"css-tree": "^1.1.3"
"css-tree": "^1.1.3",
"warn-once": "0.1.1"
},
"devDependencies": {
"@react-native-community/eslint-config": "^3.0.2",

View File

@@ -9,34 +9,38 @@ try {
}
module.exports = {
dependency: {
platforms: {
android: supportsCodegenConfig ? {
componentDescriptors: [
"RNSVGCircleComponentDescriptor",
"RNSVGClipPathComponentDescriptor",
"RNSVGDefsComponentDescriptor",
"RNSVGEllipseComponentDescriptor",
"RNSVGForeignObjectComponentDescriptor",
"RNSVGGroupComponentDescriptor",
"RNSVGImageComponentDescriptor",
"RNSVGLinearGradientComponentDescriptor",
"RNSVGLineComponentDescriptor",
"RNSVGMarkerComponentDescriptor",
"RNSVGMaskComponentDescriptor",
"RNSVGPathComponentDescriptor",
"RNSVGPatternComponentDescriptor",
"RNSVGRadialGradientComponentDescriptor",
"RNSVGRectComponentDescriptor",
"RNSVGSvgViewAndroidComponentDescriptor",
"RNSVGSymbolComponentDescriptor",
"RNSVGTextComponentDescriptor",
"RNSVGTextPathComponentDescriptor",
"RNSVGTSpanComponentDescriptor",
"RNSVGUseComponentDescriptor"
],
cmakeListsPath: "../android/src/main/jni/CMakeLists.txt"
} : {},
},
dependency: {
platforms: {
android: supportsCodegenConfig
? {
componentDescriptors: [
'RNSVGCircleComponentDescriptor',
'RNSVGClipPathComponentDescriptor',
'RNSVGDefsComponentDescriptor',
'RNSVGFeColorMatrixComponentDescriptor',
'RNSVGFilterComponentDescriptor',
'RNSVGEllipseComponentDescriptor',
'RNSVGForeignObjectComponentDescriptor',
'RNSVGGroupComponentDescriptor',
'RNSVGImageComponentDescriptor',
'RNSVGLinearGradientComponentDescriptor',
'RNSVGLineComponentDescriptor',
'RNSVGMarkerComponentDescriptor',
'RNSVGMaskComponentDescriptor',
'RNSVGPathComponentDescriptor',
'RNSVGPatternComponentDescriptor',
'RNSVGRadialGradientComponentDescriptor',
'RNSVGRectComponentDescriptor',
'RNSVGSvgViewAndroidComponentDescriptor',
'RNSVGSymbolComponentDescriptor',
'RNSVGTextComponentDescriptor',
'RNSVGTextPathComponentDescriptor',
'RNSVGTSpanComponentDescriptor',
'RNSVGUseComponentDescriptor',
],
cmakeListsPath: '../android/src/main/jni/CMakeLists.txt',
}
: {},
},
}
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
screenshots/filterImage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Some files were not shown because too many files have changed in this diff Show More