mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-05 22:56:11 +00:00
feat: implement filter region (#2441)
# Summary Implement proper handling for filter region according to the specs: * [FilterEffectsRegion](https://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion) * [FilterPrimitiveSubRegion](https://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveSubRegion) enabling user to specify * `filterUnits` * `primitiveUnits` * `x` * `y` * `width` * `height` on `Filter` element and the last four on filter primitives. ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | MacOS | ✅ | | Android | ✅ | | Web | ✅ |
This commit is contained in:
@@ -3,7 +3,7 @@ package com.horcrux.svg;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import java.util.HashMap;
|
||||
@@ -42,8 +42,10 @@ class FeOffsetView extends FilterPrimitiveView {
|
||||
|
||||
float dx = this.mDx != null ? (float) this.relativeOnWidth(this.mDx) : 0;
|
||||
float dy = this.mDy != null ? (float) this.relativeOnHeight(this.mDy) : 0;
|
||||
RectF frame = new RectF(0, 0, dx, dy);
|
||||
this.getSvgView().getCtm().mapRect(frame);
|
||||
|
||||
canvas.drawBitmap(source, dx, dy, new Paint());
|
||||
canvas.drawBitmap(source, frame.width(), frame.height(), null);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -8,33 +8,31 @@ import java.util.HashMap;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class FilterPrimitiveView extends DefinitionView {
|
||||
SVGLength mX;
|
||||
SVGLength mY;
|
||||
SVGLength mW;
|
||||
SVGLength mH;
|
||||
private String mResult;
|
||||
public final FilterRegion mFilterRegion;
|
||||
|
||||
public FilterPrimitiveView(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
mFilterRegion = new FilterRegion();
|
||||
}
|
||||
|
||||
public void setX(Dynamic x) {
|
||||
mX = SVGLength.from(x);
|
||||
mFilterRegion.setX(x);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setY(Dynamic y) {
|
||||
mY = SVGLength.from(y);
|
||||
mFilterRegion.setY(y);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setWidth(Dynamic width) {
|
||||
mW = SVGLength.from(width);
|
||||
mFilterRegion.setWidth(width);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setHeight(Dynamic height) {
|
||||
mH = SVGLength.from(height);
|
||||
mFilterRegion.setHeight(height);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
|
||||
49
android/src/main/java/com/horcrux/svg/FilterRegion.java
Normal file
49
android/src/main/java/com/horcrux/svg/FilterRegion.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.horcrux.svg;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
|
||||
public class FilterRegion {
|
||||
SVGLength mX;
|
||||
SVGLength mY;
|
||||
SVGLength mW;
|
||||
SVGLength mH;
|
||||
|
||||
public void setX(Dynamic x) {
|
||||
mX = SVGLength.from(x);
|
||||
}
|
||||
|
||||
public void setY(Dynamic y) {
|
||||
mY = SVGLength.from(y);
|
||||
}
|
||||
|
||||
public void setWidth(Dynamic width) {
|
||||
mW = SVGLength.from(width);
|
||||
}
|
||||
|
||||
public void setHeight(Dynamic height) {
|
||||
mH = SVGLength.from(height);
|
||||
}
|
||||
|
||||
public Rect getCropRect(VirtualView view, FilterProperties.Units units, RectF renderableBounds) {
|
||||
double x, y, width, height;
|
||||
if (units == FilterProperties.Units.USER_SPACE_ON_USE) {
|
||||
x = view.relativeOn(this.mX, view.getSvgView().getCanvasWidth());
|
||||
y = view.relativeOn(this.mY, view.getSvgView().getCanvasHeight());
|
||||
width = view.relativeOn(this.mW, view.getSvgView().getCanvasWidth());
|
||||
height = view.relativeOn(this.mH, view.getSvgView().getCanvasHeight());
|
||||
return new Rect((int) x, (int) y, (int) (x + width), (int) (y + height));
|
||||
} else { // FilterProperties.Units.OBJECT_BOUNDING_BOX
|
||||
x = view.relativeOnFraction(this.mX, renderableBounds.width());
|
||||
y = view.relativeOnFraction(this.mY, renderableBounds.height());
|
||||
width = view.relativeOnFraction(this.mW, renderableBounds.width());
|
||||
height = view.relativeOnFraction(this.mH, renderableBounds.height());
|
||||
return new Rect(
|
||||
(int) (renderableBounds.left + x),
|
||||
(int) (renderableBounds.top + y),
|
||||
(int) (renderableBounds.left + x + width),
|
||||
(int) (renderableBounds.top + y + height));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package com.horcrux.svg;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import com.facebook.react.bridge.Dynamic;
|
||||
@@ -14,37 +16,32 @@ import java.util.HashMap;
|
||||
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;
|
||||
private final FilterRegion mFilterRegion;
|
||||
|
||||
public FilterView(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
mFilterRegion = new FilterRegion();
|
||||
}
|
||||
|
||||
public void setX(Dynamic x) {
|
||||
mX = SVGLength.from(x);
|
||||
mFilterRegion.setX(x);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setY(Dynamic y) {
|
||||
mY = SVGLength.from(y);
|
||||
mFilterRegion.setY(y);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setWidth(Dynamic width) {
|
||||
mW = SVGLength.from(width);
|
||||
mFilterRegion.setWidth(width);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setHeight(Dynamic height) {
|
||||
mH = SVGLength.from(height);
|
||||
mFilterRegion.setHeight(height);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@@ -68,8 +65,7 @@ class FilterView extends DefinitionView {
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap applyFilter(
|
||||
Bitmap source, Bitmap background, Rect renderableBounds, Rect canvasBounds) {
|
||||
public Bitmap applyFilter(Bitmap source, Bitmap background, RectF renderableBounds) {
|
||||
mResultsMap.clear();
|
||||
mResultsMap.put("SourceGraphic", source);
|
||||
mResultsMap.put("SourceAlpha", FilterUtils.applySourceAlphaFilter(source));
|
||||
@@ -77,12 +73,20 @@ class FilterView extends DefinitionView {
|
||||
mResultsMap.put("BackgroundAlpha", FilterUtils.applySourceAlphaFilter(background));
|
||||
|
||||
Bitmap res = source;
|
||||
Bitmap resultBitmap = Bitmap.createBitmap(res.getWidth(), res.getHeight(), res.getConfig());
|
||||
Canvas canvas = new Canvas(resultBitmap);
|
||||
Rect cropRect;
|
||||
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View node = getChildAt(i);
|
||||
if (node instanceof FilterPrimitiveView) {
|
||||
FilterPrimitiveView currentFilter = (FilterPrimitiveView) node;
|
||||
res = currentFilter.applyFilter(mResultsMap, res);
|
||||
resultBitmap.eraseColor(Color.TRANSPARENT);
|
||||
cropRect =
|
||||
currentFilter.mFilterRegion.getCropRect(
|
||||
currentFilter, this.mPrimitiveUnits, renderableBounds);
|
||||
canvas.drawBitmap(currentFilter.applyFilter(mResultsMap, res), cropRect, cropRect, null);
|
||||
res = resultBitmap.copy(Bitmap.Config.ARGB_8888, true);
|
||||
String resultName = currentFilter.getResult();
|
||||
if (resultName != null) {
|
||||
mResultsMap.put(resultName, res);
|
||||
@@ -93,21 +97,8 @@ class FilterView extends DefinitionView {
|
||||
}
|
||||
|
||||
// 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);
|
||||
resultBitmap.eraseColor(Color.TRANSPARENT);
|
||||
cropRect = this.mFilterRegion.getCropRect(this, this.mFilterUnits, renderableBounds);
|
||||
canvas.drawBitmap(res, cropRect, cropRect, null);
|
||||
return resultBitmap;
|
||||
}
|
||||
|
||||
@@ -352,24 +352,30 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
|
||||
Paint bitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
canvas.saveLayer(null, bitmapPaint);
|
||||
|
||||
Rect canvasBounds = this.getSvgView().getCanvasBounds();
|
||||
Bitmap backgroundBitmap = this.getSvgView().getCurrentBitmap();
|
||||
|
||||
// draw element to self bitmap
|
||||
Bitmap elementBitmap =
|
||||
Bitmap.createBitmap(
|
||||
canvasBounds.width(), canvasBounds.height(), Bitmap.Config.ARGB_8888);
|
||||
Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas elementCanvas = new Canvas(elementBitmap);
|
||||
elementCanvas.setMatrix(canvas.getMatrix());
|
||||
|
||||
draw(elementCanvas, paint, opacity);
|
||||
|
||||
// get renderableBounds
|
||||
this.initBounds();
|
||||
RectF clientRect = this.getClientRect();
|
||||
if (this instanceof ImageView && clientRect == null) {
|
||||
return;
|
||||
}
|
||||
// apply filters
|
||||
Bitmap backgroundBitmap = this.getSvgView().getCurrentBitmap();
|
||||
elementBitmap =
|
||||
filter.applyFilter(
|
||||
elementBitmap, backgroundBitmap, elementCanvas.getClipBounds(), canvasBounds);
|
||||
elementBitmap = filter.applyFilter(elementBitmap, backgroundBitmap, clientRect);
|
||||
|
||||
// draw bitmap to canvas
|
||||
// draw bitmap 1:1 to canvas
|
||||
int saveCount = canvas.save();
|
||||
canvas.setMatrix(null);
|
||||
canvas.drawBitmap(elementBitmap, 0, 0, bitmapPaint);
|
||||
canvas.restoreToCount(saveCount);
|
||||
} else {
|
||||
canvas.saveLayer(null, paint);
|
||||
draw(canvas, paint, opacity);
|
||||
|
||||
@@ -296,6 +296,18 @@ public class SvgView extends ReactViewGroup implements ReactCompoundView, ReactC
|
||||
return mCanvas.getClipBounds();
|
||||
}
|
||||
|
||||
float getCanvasWidth() {
|
||||
return mCanvas.getWidth();
|
||||
}
|
||||
|
||||
float getCanvasHeight() {
|
||||
return mCanvas.getHeight();
|
||||
}
|
||||
|
||||
Matrix getCtm() {
|
||||
return mCanvas.getMatrix();
|
||||
}
|
||||
|
||||
synchronized void drawChildren(final Canvas canvas) {
|
||||
mRendered = true;
|
||||
mCanvas = canvas;
|
||||
|
||||
Reference in New Issue
Block a user