diff --git a/USAGE.md b/USAGE.md index 6e9b21557..df7324250 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1301,8 +1301,9 @@ Filter effects are a way of processing an element’s rendering before it is dis The following filters have been implemented: - FeBlend -- FeComposite - FeColorMatrix +- FeComposite +- FeConvolveMatrix - FeDropShadow - FeFlood - FeGaussianBlur @@ -1312,7 +1313,6 @@ The following filters have been implemented: Not supported yet: - FeComponentTransfer -- FeConvolveMatrix - FeDiffuseLighting - FeDisplacementMap - FeFuncA diff --git a/android/src/main/java/com/horcrux/svg/FeConvolveMatrixView.java b/android/src/main/java/com/horcrux/svg/FeConvolveMatrixView.java new file mode 100644 index 000000000..2ef18e284 --- /dev/null +++ b/android/src/main/java/com/horcrux/svg/FeConvolveMatrixView.java @@ -0,0 +1,203 @@ +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.ArrayList; +import java.util.HashMap; + +@SuppressLint("ViewConstructor") +class FeConvolveMatrixView extends FilterPrimitiveView { + String mIn1; + ArrayList mOrder; + ArrayList mKernelMatrix; + SVGLength mDivisor; + SVGLength mBias; + SVGLength mTargetX; + SVGLength mTargetY; + FilterProperties.EdgeMode mEdgeMode; + boolean mPreserveAlpha; + + public FeConvolveMatrixView(ReactContext reactContext) { + super(reactContext); + } + + public void setIn1(String in1) { + this.mIn1 = in1; + invalidate(); + } + + public void setOrder(Dynamic order) { + this.mOrder = SVGLength.arrayFrom(order); + invalidate(); + } + + public void setKernelMatrix(Dynamic kernelMatrix) { + this.mKernelMatrix = SVGLength.arrayFrom(kernelMatrix); + invalidate(); + } + + public void setDivisor(Dynamic divisor) { + this.mDivisor = SVGLength.from(divisor); + invalidate(); + } + + public void setBias(Dynamic bias) { + this.mBias = SVGLength.from(bias); + invalidate(); + } + + public void setTargetX(Dynamic targetX) { + this.mTargetX = SVGLength.from(targetX); + invalidate(); + } + + public void setTargetY(Dynamic targetY) { + this.mTargetY = SVGLength.from(targetY); + invalidate(); + } + + public void setEdgeMode(String edgeMode) { + this.mEdgeMode = FilterProperties.EdgeMode.getEnum(edgeMode); + System.out.println(this.mEdgeMode); + invalidate(); + } + + public void setPreserveAlpha(boolean preserveAlpha) { + this.mPreserveAlpha = preserveAlpha; + invalidate(); + } + + @Override + public Bitmap applyFilter(HashMap resultsMap, Bitmap prevResult) { + Bitmap in1 = getSource(resultsMap, prevResult, this.mIn1); + + return performConvolution(in1); + } + + private Bitmap performConvolution(Bitmap src) { + int width = src.getWidth(); + int height = src.getHeight(); + int orderX = this.getOrderX(); + int orderY = this.getOrderY(); + float[] kernel = this.getKernelMatrixArray(orderX, orderY); + float divisor = this.getDivisor(kernel); + int targetX = this.getTargetX(orderX); + int targetY = this.getTargetY(orderY); + + Bitmap out = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + int[] inPixels = new int[width * height]; + src.getPixels(inPixels, 0, width, 0, 0, width, height); + int[] outPixels = new int[inPixels.length]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float r = 0, g = 0, b = 0, a = 0; + + for (int ky = 0; ky < orderY; ky++) { + for (int kx = 0; kx < orderX; kx++) { + int ix = x + (kx - targetX); + int iy = y + (ky - targetY); + + int sample; + switch (this.mEdgeMode) { + case DUPLICATE: + ix = Math.max(0, Math.min(ix, width - 1)); + iy = Math.max(0, Math.min(iy, height - 1)); + sample = inPixels[iy * width + ix]; + break; + case WRAP: + ix = (ix + width) % width; + iy = (iy + height) % height; + sample = inPixels[iy * width + ix]; + break; + case NONE: + default: + if (ix < 0 || iy < 0 || ix >= width || iy >= height) { + sample = 0; + } else { + sample = inPixels[iy * width + ix]; + } + break; + } + + float kval = kernel[(orderY - 1 - ky) * orderX + (orderX - 1 - kx)]; + + a += ((sample >> 24) & 0xFF) * kval; + r += ((sample >> 16) & 0xFF) * kval; + g += ((sample >> 8) & 0xFF) * kval; + b += (sample & 0xFF) * kval; + } + } + + r = r / divisor + (int) mBias.value; + g = g / divisor + (int) mBias.value; + b = b / divisor + (int) mBias.value; + + if (mPreserveAlpha) { + a = (inPixels[y * width + x] >> 24) & 0xFF; + } else { + a = a / divisor; + } + + int ir = Math.min(255, Math.max(0, Math.round(r))); + int ig = Math.min(255, Math.max(0, Math.round(g))); + int ib = Math.min(255, Math.max(0, Math.round(b))); + int ia = Math.min(255, Math.max(0, Math.round(a))); + + outPixels[y * width + x] = (ia << 24) | (ir << 16) | (ig << 8) | ib; + } + } + + out.setPixels(outPixels, 0, width, 0, 0, width, height); + return out; + } + + private int getOrderX() { + if (this.mOrder == null || this.mOrder.isEmpty()) { + return 3; + } + + return (int) this.mOrder.get(0).value; + } + + private int getOrderY() { + if (this.mOrder == null || this.mOrder.size() <= 1) { + return this.getOrderX(); + } + + return (int) this.mOrder.get(1).value; + } + + private float getDivisor(float[] kernel) { + float divisor = (mDivisor != null) ? (float) mDivisor.value : 0f; + + if (divisor == 0f) { + float sum = 0; + for (float v : kernel) sum += v; + divisor = (sum == 0f) ? 1f : sum; + } + + return divisor; + } + + private float[] getKernelMatrixArray(int orderX, int orderY) { + float[] kernel = new float[orderX * orderY]; + + for (int i = 0; i < kernel.length; i++) { + kernel[i] = (float) mKernelMatrix.get(i).value; + } + + return kernel; + } + + private int getTargetX(int orderX) { + return (mTargetX != null) ? (int) mTargetX.value : orderX / 2; + } + + private int getTargetY(int orderY) { + return (mTargetY != null) ? (int) mTargetY.value : orderY / 2; + } +} diff --git a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java index 06095e45f..e7b1a18c0 100644 --- a/android/src/main/java/com/horcrux/svg/RenderableViewManager.java +++ b/android/src/main/java/com/horcrux/svg/RenderableViewManager.java @@ -99,6 +99,8 @@ import com.facebook.react.viewmanagers.RNSVGFeColorMatrixManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeCompositeManagerDelegate; import com.facebook.react.viewmanagers.RNSVGFeCompositeManagerInterface; +import com.facebook.react.viewmanagers.RNSVGFeConvolveMatrixManagerDelegate; +import com.facebook.react.viewmanagers.RNSVGFeConvolveMatrixManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeFloodManagerDelegate; import com.facebook.react.viewmanagers.RNSVGFeFloodManagerInterface; import com.facebook.react.viewmanagers.RNSVGFeGaussianBlurManagerDelegate; @@ -484,6 +486,7 @@ protected enum SVGClass { RNSVGFeBlend, RNSVGFeColorMatrix, RNSVGFeComposite, + RNSVGFeConvolveMatrix, RNSVGFeFlood, RNSVGFeGaussianBlur, RNSVGFeMerge, @@ -540,6 +543,8 @@ protected VirtualView createViewInstance(@Nonnull ThemedReactContext reactContex return new FeColorMatrixView(reactContext); case RNSVGFeComposite: return new FeCompositeView(reactContext); + case RNSVGFeConvolveMatrix: + return new FeConvolveMatrixView(reactContext); case RNSVGFeFlood: return new FeFloodView(reactContext); case RNSVGFeGaussianBlur: @@ -1582,6 +1587,61 @@ public void setK4(FeCompositeView node, float value) { } } + static class FeConvolveMatrixManager extends FilterPrimitiveManager + implements RNSVGFeConvolveMatrixManagerInterface { + FeConvolveMatrixManager() { + super(SVGClass.RNSVGFeConvolveMatrix); + mDelegate = new RNSVGFeConvolveMatrixManagerDelegate(this); + } + + public static final String REACT_CLASS = "RNSVGFeConvolveMatrix"; + + @ReactProp(name = "in1") + public void setIn1(FeConvolveMatrixView node, String in1) { + node.setIn1(in1); + } + + @ReactProp(name = "order") + public void setOrder(FeConvolveMatrixView node, Dynamic order) { + node.setOrder(order); + } + + @ReactProp(name = "kernelMatrix") + public void setKernelMatrix(FeConvolveMatrixView node, Dynamic kernelMatrix) { + node.setKernelMatrix(kernelMatrix); + } + + @ReactProp(name = "divisor") + public void setDivisor(FeConvolveMatrixView node, Dynamic divisor) { + node.setDivisor(divisor); + } + + @ReactProp(name = "bias") + public void setBias(FeConvolveMatrixView node, Dynamic bias) { + node.setBias(bias); + } + + @ReactProp(name = "targetX") + public void setTargetX(FeConvolveMatrixView node, Dynamic targetX) { + node.setTargetX(targetX); + } + + @ReactProp(name = "targetY") + public void setTargetY(FeConvolveMatrixView node, Dynamic targetY) { + node.setTargetY(targetY); + } + + @ReactProp(name = "edgeMode") + public void setEdgeMode(FeConvolveMatrixView node, String edgeMode) { + node.setEdgeMode(edgeMode); + } + + @ReactProp(name = "preserveAlpha") + public void setPreserveAlpha(FeConvolveMatrixView node, boolean preserveAlpha) { + node.setPreserveAlpha(preserveAlpha); + } + } + static class FeFloodManager extends FilterPrimitiveManager implements RNSVGFeFloodManagerInterface { FeFloodManager() { diff --git a/android/src/main/java/com/horcrux/svg/SvgPackage.java b/android/src/main/java/com/horcrux/svg/SvgPackage.java index fe81f02c3..dd52c5541 100644 --- a/android/src/main/java/com/horcrux/svg/SvgPackage.java +++ b/android/src/main/java/com/horcrux/svg/SvgPackage.java @@ -241,6 +241,15 @@ public NativeModule get() { return new FeCompositeManager(); } })); + specs.put( + FeConvolveMatrixManager.REACT_CLASS, + ModuleSpec.viewManagerSpec( + new Provider() { + @Override + public NativeModule get() { + return new FeConvolveMatrixManager(); + } + })); specs.put( FeFloodManager.REACT_CLASS, ModuleSpec.viewManagerSpec( diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeConvolveMatrixManagerDelegate.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeConvolveMatrixManagerDelegate.java new file mode 100644 index 000000000..0cd94c2cb --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeConvolveMatrixManagerDelegate.java @@ -0,0 +1,72 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaDelegate.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.DynamicFromObject; +import com.facebook.react.uimanager.BaseViewManager; +import com.facebook.react.uimanager.BaseViewManagerDelegate; +import com.facebook.react.uimanager.LayoutShadowNode; + +public class RNSVGFeConvolveMatrixManagerDelegate & RNSVGFeConvolveMatrixManagerInterface> extends BaseViewManagerDelegate { + public RNSVGFeConvolveMatrixManagerDelegate(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 "order": + mViewManager.setOrder(view, new DynamicFromObject(value)); + break; + case "kernelMatrix": + mViewManager.setKernelMatrix(view, new DynamicFromObject(value)); + break; + case "divisor": + mViewManager.setDivisor(view, new DynamicFromObject(value)); + break; + case "bias": + mViewManager.setBias(view, new DynamicFromObject(value)); + break; + case "targetX": + mViewManager.setTargetX(view, new DynamicFromObject(value)); + break; + case "targetY": + mViewManager.setTargetY(view, new DynamicFromObject(value)); + break; + case "edgeMode": + mViewManager.setEdgeMode(view, (String) value); + break; + case "preserveAlpha": + mViewManager.setPreserveAlpha(view, value == null ? false : (boolean) value); + break; + default: + super.setProperty(view, propName, value); + } + } +} diff --git a/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeConvolveMatrixManagerInterface.java b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeConvolveMatrixManagerInterface.java new file mode 100644 index 000000000..7d8e1effa --- /dev/null +++ b/android/src/paper/java/com/facebook/react/viewmanagers/RNSVGFeConvolveMatrixManagerInterface.java @@ -0,0 +1,31 @@ +/** +* 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 RNSVGFeConvolveMatrixManagerInterface { + 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 setOrder(T view, Dynamic value); + void setKernelMatrix(T view, Dynamic value); + void setDivisor(T view, Dynamic value); + void setBias(T view, Dynamic value); + void setTargetX(T view, Dynamic value); + void setTargetY(T view, Dynamic value); + void setEdgeMode(T view, @Nullable String value); + void setPreserveAlpha(T view, boolean value); +} diff --git a/apple/Filters/RNSVGFeConvolveMatrix.h b/apple/Filters/RNSVGFeConvolveMatrix.h new file mode 100644 index 000000000..6d08a01cd --- /dev/null +++ b/apple/Filters/RNSVGFeConvolveMatrix.h @@ -0,0 +1,16 @@ +#import "RNSVGEdgeMode.h" +#import "RNSVGFilterPrimitive.h" + +@interface RNSVGFeConvolveMatrix : RNSVGFilterPrimitive + +@property (nonatomic, strong) NSString *in1; +@property (nonatomic, strong) NSArray *order; +@property (nonatomic, strong) NSArray *kernelMatrix; +@property (nonatomic, strong) RNSVGLength *divisor; +@property (nonatomic, strong) RNSVGLength *bias; +@property (nonatomic, strong) RNSVGLength *targetX; +@property (nonatomic, strong) RNSVGLength *targetY; +@property (nonatomic, assign) RNSVGEdgeMode edgeMode; +@property (nonatomic, assign) BOOL preserveAlpha; + +@end diff --git a/apple/Filters/RNSVGFeConvolveMatrix.mm b/apple/Filters/RNSVGFeConvolveMatrix.mm new file mode 100644 index 000000000..3b2cb7496 --- /dev/null +++ b/apple/Filters/RNSVGFeConvolveMatrix.mm @@ -0,0 +1,360 @@ +#import "RNSVGFeConvolveMatrix.h" + +#ifdef RCT_NEW_ARCH_ENABLED +#import +#import +#import +#import +#import "RNSVGConvert.h" +#import "RNSVGFabricConversions.h" +#endif // RCT_NEW_ARCH_ENABLED + +static const NSUInteger kBytesPerPixel = 4; +static const NSUInteger kBitsPerComponent = 8; + +@implementation RNSVGFeConvolveMatrix + +#ifdef RCT_NEW_ARCH_ENABLED +using namespace facebook::react; + +// Needed because of this: https://github.com/facebook/react-native/pull/37274 ++ (void)load +{ + [super load]; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + } + return self; +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + const auto &newProps = static_cast(*props); + + self.in1 = RCTNSStringFromStringNilIfEmpty(newProps.in1); + + id order = RNSVGConvertFollyDynamicToId(newProps.order); + if (order != nil) { + self.order = [RCTConvert RNSVGLengthArray:order]; + } + + id kernelMatrix = RNSVGConvertFollyDynamicToId(newProps.kernelMatrix); + if (kernelMatrix != nil) { + self.kernelMatrix = [RCTConvert RNSVGLengthArray:kernelMatrix]; + } + + id divisor = RNSVGConvertFollyDynamicToId(newProps.divisor); + if (divisor != nil) { + self.divisor = [RCTConvert RNSVGLength:divisor]; + } + + id bias = RNSVGConvertFollyDynamicToId(newProps.bias); + if (bias != nil) { + self.bias = [RCTConvert RNSVGLength:bias]; + } + + id targetX = RNSVGConvertFollyDynamicToId(newProps.targetX); + if (targetX != nil) { + self.targetX = [RCTConvert RNSVGLength:targetX]; + } + + id targetY = RNSVGConvertFollyDynamicToId(newProps.targetY); + if (targetY != nil) { + self.targetY = [RCTConvert RNSVGLength:targetY]; + } + + self.edgeMode = [RNSVGConvert RNSVGConvolveMatrixEdgeModeFromCppEquivalent:newProps.edgeMode]; + self.preserveAlpha = newProps.preserveAlpha; + + setCommonFilterProps(newProps, self); + _props = std::static_pointer_cast(props); +} + +- (void)prepareForRecycle +{ + [super prepareForRecycle]; + _in1 = nil; + _order = nil; + _kernelMatrix = nil; + _divisor = nil; + _bias = [RNSVGLength lengthWithNumber:0]; + _targetX = nil; + _targetY = nil; + _edgeMode = RNSVGEdgeMode::SVG_EDGEMODE_DUPLICATE; + _preserveAlpha = NO; +} +#endif // RCT_NEW_ARCH_ENABLED + +- (void)setIn1:(NSString *)in1 +{ + if ([in1 isEqualToString:_in1]) { + return; + } + + _in1 = in1; + [self invalidate]; +} + +- (void)setOrder:(NSArray *)order +{ + if (order == _order) { + return; + } + + _order = order; + [self invalidate]; +} + +- (void)setKernelMatrix:(NSArray *)kernelMatrix +{ + if (kernelMatrix == _kernelMatrix) { + return; + } + + _kernelMatrix = kernelMatrix; + [self invalidate]; +} + +- (void)setDivisor:(RNSVGLength *)divisor +{ + if ([divisor isEqualTo:_divisor]) { + return; + } + + _divisor = divisor; + [self invalidate]; +} + +- (void)setBias:(RNSVGLength *)bias +{ + if ([bias isEqualTo:_bias]) { + return; + } + + _bias = bias; + [self invalidate]; +} + +- (void)setTargetX:(RNSVGLength *)targetX +{ + if ([targetX isEqualTo:_targetX]) { + return; + } + + _targetX = targetX; + [self invalidate]; +} + +- (void)setTargetY:(RNSVGLength *)targetY +{ + if ([targetY isEqualTo:_targetY]) { + return; + } + + _targetY = targetY; + [self invalidate]; +} + +- (void)setEdgeMode:(RNSVGEdgeMode)edgeMode +{ + if (edgeMode == _edgeMode) { + return; + } + _edgeMode = edgeMode; + [self invalidate]; +} + +- (void)setPreserveAlpha:(BOOL)preserveAlpha +{ + if (preserveAlpha == _preserveAlpha) { + return; + } + + _preserveAlpha = preserveAlpha; + [self invalidate]; +} + +- (CIImage *)applyFilter:(NSMutableDictionary *)results previousFilterResult:(CIImage *)previous +{ + CIImage *inResults1 = self.in1 ? results[self.in1] : nil; + CIImage *inputImage1 = inResults1 ?: previous; + + return [self performConvolution:inputImage1]; +} + +- (CIImage *)performConvolution:(CIImage *)ciImage +{ + NSUInteger width, height; + unsigned char *rawData; + CGContextRef context = [self prepareContextForImage:ciImage width:&width height:&height imageData:&rawData]; + + unsigned char *outData = (unsigned char *)calloc(height * width * kBytesPerPixel, sizeof(unsigned char)); + + int orderX = [self getOrderX]; + int orderY = [self getOrderY]; + int kernelSize = orderX * orderY; + float *kernel = (float *)malloc(sizeof(float) * kernelSize); + [self getKernelMatrix:kernel orderX:orderX orderY:orderY]; + float divisor = [self getDivisor:kernel length:kernelSize]; + + int targetX = [self getTargetX:orderX]; + int targetY = [self getTargetY:orderY]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + float r = 0, g = 0, b = 0, a = 0; + + for (int ky = 0; ky < orderY; ky++) { + for (int kx = 0; kx < orderX; kx++) { + int ix = x + (kx - targetX); + int iy = y + (ky - targetY); + + unsigned char *samplePixel = NULL; + switch (self.edgeMode) { + case SVG_EDGEMODE_DUPLICATE: + ix = MAX(0, MIN(ix, (int)width - 1)); + iy = MAX(0, MIN(iy, (int)height - 1)); + break; + case SVG_EDGEMODE_WRAP: + ix = (ix + width) % width; + iy = (iy + height) % height; + break; + case SVG_EDGEMODE_NONE: + case SVG_EDGEMODE_UNKNOWN: + default: + if (ix < 0 || iy < 0 || ix >= width || iy >= height) { + continue; + } + break; + } + + samplePixel = rawData + (iy * width * kBytesPerPixel + ix * kBytesPerPixel); + float kval = kernel[(orderY - 1 - ky) * orderX + (orderX - 1 - kx)]; + + a += samplePixel[3] * kval; + r += samplePixel[0] * kval; + g += samplePixel[1] * kval; + b += samplePixel[2] * kval; + } + } + + r = r / divisor + self.bias.value; + g = g / divisor + self.bias.value; + b = b / divisor + self.bias.value; + if (self.preserveAlpha) { + a = rawData[y * width * kBytesPerPixel + x * kBytesPerPixel + 3]; + } else { + a = a / divisor; + } + + unsigned char *outPixel = outData + (y * width * kBytesPerPixel + x * kBytesPerPixel); + outPixel[0] = (unsigned char)MIN(MAX(r, 0), 255); + outPixel[1] = (unsigned char)MIN(MAX(g, 0), 255); + outPixel[2] = (unsigned char)MIN(MAX(b, 0), 255); + outPixel[3] = (unsigned char)MIN(MAX(a, 0), 255); + } + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef outCtx = CGBitmapContextCreate(outData, width, height, + kBitsPerComponent, width * kBytesPerPixel, + colorSpace, kCGImageAlphaPremultipliedLast); + CGImageRef outCGImage = CGBitmapContextCreateImage(outCtx); + CIImage *outCI = [CIImage imageWithCGImage:outCGImage]; + + CGColorSpaceRelease(colorSpace); + CGContextRelease(outCtx); + CGContextRelease(context); + free(rawData); + free(kernel); + free(outData); + + return outCI; +} + +- (CGContextRef)prepareContextForImage:(CIImage *)ciImage width:(NSUInteger *)width height:(NSUInteger *)height imageData:(unsigned char **)imageData +{ + CIContext *ctx = [CIContext contextWithOptions:nil]; + CGImageRef cgImage = [ctx createCGImage:ciImage fromRect:ciImage.extent]; + *width = CGImageGetWidth(cgImage); + *height = CGImageGetHeight(cgImage); + + *imageData = (unsigned char *)calloc(*height * *width * kBytesPerPixel, sizeof(unsigned char)); + CGContextRef context = CGBitmapContextCreate(*imageData, *width, *height, kBitsPerComponent, kBytesPerPixel * *width, + CGColorSpaceCreateDeviceRGB(), + kCGImageAlphaPremultipliedLast); + CGContextDrawImage(context, CGRectMake(0, 0, *width, *height), cgImage); + CGImageRelease(cgImage); + + return context; +} + +- (int)getOrderX +{ + if (self.order == nil || self.order.count == 0) { + return 3; + } + return self.order[0].value; +} + +- (int)getOrderY +{ + if (self.order == nil || self.order.count <= 1) { + return [self getOrderX]; + } + return self.order[1].value; +} + +- (float)getDivisor:(float *)kernel length:(int)length +{ + float divisor = (self.divisor != nil) ? self.divisor.value : 0.0f; + + if (divisor == 0.0f) { + float sum = 0.0f; + for (int i = 0; i < length; i++) { + sum += kernel[i]; + } + divisor = (sum == 0.0f) ? 1.0f : sum; + } + + return divisor; +} + +- (void)getKernelMatrix:(float *)outKernel orderX:(int)orderX orderY:(int)orderY +{ + int count = orderX * orderY; + for (int i = 0; i < count; i++) { + outKernel[i] = self.kernelMatrix[i].value; + } +} + +- (int)getTargetX:(int)orderX +{ + return (self.targetX != nil) ? self.targetX.value : orderX / 2; +} + +- (int)getTargetY:(int)orderY +{ + return (self.targetY != nil) ? self.targetY.value : orderY / 2; +} + +#ifdef RCT_NEW_ARCH_ENABLED +Class RNSVGFeConvolveMatrixCls(void) +{ + return RNSVGFeConvolveMatrix.class; +} +#endif // RCT_NEW_ARCH_ENABLED + +@end diff --git a/apple/Utils/RNSVGConvert.h b/apple/Utils/RNSVGConvert.h index 320007c43..e225e4dc5 100644 --- a/apple/Utils/RNSVGConvert.h +++ b/apple/Utils/RNSVGConvert.h @@ -16,6 +16,7 @@ namespace react = facebook::react; + (RNSVGColorMatrixType)RNSVGColorMatrixTypeFromCppEquivalent:(react::RNSVGFeColorMatrixType)type; + (RNSVGCompositeOperator)RNSVGRNSVGCompositeOperatorFromCppEquivalent:(react::RNSVGFeCompositeOperator1)operator1; + (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeMode)edgeMode; ++ (RNSVGEdgeMode)RNSVGConvolveMatrixEdgeModeFromCppEquivalent:(react::RNSVGFeConvolveMatrixEdgeMode)edgeMode; @end diff --git a/apple/Utils/RNSVGConvert.mm b/apple/Utils/RNSVGConvert.mm index 967521491..b7e5d4f4c 100644 --- a/apple/Utils/RNSVGConvert.mm +++ b/apple/Utils/RNSVGConvert.mm @@ -87,6 +87,20 @@ + (RNSVGEdgeMode)RNSVGEdgeModeFromCppEquivalent:(react::RNSVGFeGaussianBlurEdgeM } } ++ (RNSVGEdgeMode)RNSVGConvolveMatrixEdgeModeFromCppEquivalent:(react::RNSVGFeConvolveMatrixEdgeMode)edgeMode +{ + switch (edgeMode) { + case react::RNSVGFeConvolveMatrixEdgeMode::Duplicate: + return SVG_EDGEMODE_DUPLICATE; + case react::RNSVGFeConvolveMatrixEdgeMode::Wrap: + return SVG_EDGEMODE_WRAP; + case react::RNSVGFeConvolveMatrixEdgeMode::None: + return SVG_EDGEMODE_NONE; + default: + return SVG_EDGEMODE_UNKNOWN; + } +} + @end #endif // RCT_NEW_ARCH_ENABLED diff --git a/apple/ViewManagers/RNSVGFeConvolveMatrixManager.h b/apple/ViewManagers/RNSVGFeConvolveMatrixManager.h new file mode 100644 index 000000000..9d36267ad --- /dev/null +++ b/apple/ViewManagers/RNSVGFeConvolveMatrixManager.h @@ -0,0 +1,5 @@ +#import "RNSVGRenderableManager.h" + +@interface RNSVGFeConvolveMatrixManager : RNSVGRenderableManager + +@end diff --git a/apple/ViewManagers/RNSVGFeConvolveMatrixManager.mm b/apple/ViewManagers/RNSVGFeConvolveMatrixManager.mm new file mode 100644 index 000000000..c3106e2f8 --- /dev/null +++ b/apple/ViewManagers/RNSVGFeConvolveMatrixManager.mm @@ -0,0 +1,24 @@ +#import "RNSVGFeConvolveMatrixManager.h" +#import "RNSVGEdgeMode.h" +#import "RNSVGFeConvolveMatrix.h" + +@implementation RNSVGFeConvolveMatrixManager + +RCT_EXPORT_MODULE() + +- (RNSVGFeConvolveMatrix *)node +{ + return [RNSVGFeConvolveMatrix new]; +} + +RCT_EXPORT_VIEW_PROPERTY(in1, NSString) +RCT_EXPORT_VIEW_PROPERTY(order, NSArray) +RCT_EXPORT_VIEW_PROPERTY(kernelMatrix, NSArray) +RCT_EXPORT_VIEW_PROPERTY(divisor, RNSVGLength *) +RCT_EXPORT_VIEW_PROPERTY(bias, RNSVGLength *) +RCT_EXPORT_VIEW_PROPERTY(targetX, RNSVGLength *) +RCT_EXPORT_VIEW_PROPERTY(targetY, RNSVGLength *) +RCT_EXPORT_VIEW_PROPERTY(edgeMode, RNSVGEdgeMode) +RCT_EXPORT_VIEW_PROPERTY(preserveAlpha, BOOL) + +@end diff --git a/apps/common/example/examples/Filters/FeConvolveMatrix.tsx b/apps/common/example/examples/Filters/FeConvolveMatrix.tsx new file mode 100644 index 000000000..db86988e4 --- /dev/null +++ b/apps/common/example/examples/Filters/FeConvolveMatrix.tsx @@ -0,0 +1,157 @@ +import React from 'react'; +import { + Defs, + FeConvolveMatrix, + Filter, + G, + LinearGradient, + Path, + Stop, + Svg, + Text, +} from 'react-native-svg'; + +CustomExample.title = 'Custom example'; +function CustomExample() { + return ( + + + + + + + + Convolve + + + Convolve + + + + ); +} + +MDNExample.title = 'MDN example'; +function MDNExample() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +const icon = ( + + + + + + + + + + + + + + + + + + + + + + + + + +); +const samples = [MDNExample, CustomExample]; + +export {icon, samples}; diff --git a/apps/common/example/examples/Filters/index.tsx b/apps/common/example/examples/Filters/index.tsx index 36069573c..b958fef99 100644 --- a/apps/common/example/examples/Filters/index.tsx +++ b/apps/common/example/examples/Filters/index.tsx @@ -3,6 +3,7 @@ import Svg, {Circle} from 'react-native-svg'; import * as FeBlend from './FeBlend'; import * as FeColorMatrix from './FeColorMatrix'; import * as FeComposite from './FeComposite'; +import * as FeConvolveMatrix from './FeConvolveMatrix'; import * as FeDropShadow from './FeDropShadow'; import * as FeFlood from './FeFlood'; import * as FeGaussianBlur from './FeGaussianBlur'; @@ -14,6 +15,7 @@ const samples = { FeBlend, FeColorMatrix, FeComposite, + FeConvolveMatrix, FeDropShadow, FeFlood, FeGaussianBlur, diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h b/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h index 61c44e7a6..f1318735c 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGComponentDescriptors.h @@ -20,6 +20,8 @@ using RNSVGFeColorMatrixComponentDescriptor = ConcreteComponentDescriptor; using RNSVGFeCompositeComponentDescriptor = ConcreteComponentDescriptor; +using RNSVGFeConvolveMatrixComponentDescriptor = + ConcreteComponentDescriptor; using RNSVGFeFloodComponentDescriptor = ConcreteComponentDescriptor; using RNSVGFeGaussianBlurComponentDescriptor = diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp index 70f1abd43..520917db7 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.cpp @@ -9,6 +9,7 @@ extern const char RNSVGEllipseComponentName[] = "RNSVGEllipse"; extern const char RNSVGFeBlendComponentName[] = "RNSVGFeBlend"; extern const char RNSVGFeColorMatrixComponentName[] = "RNSVGFeColorMatrix"; extern const char RNSVGFeCompositeComponentName[] = "RNSVGFeComposite"; +extern const char RNSVGFeConvolveMatrixComponentName[] = "RNSVGFeConvolveMatrix"; extern const char RNSVGFeFloodComponentName[] = "RNSVGFeFlood"; extern const char RNSVGFeGaussianBlurComponentName[] = "RNSVGFeGaussianBlur"; extern const char RNSVGFeMergeComponentName[] = "RNSVGFeMerge"; diff --git a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h index 71addc842..a209d9f59 100644 --- a/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h +++ b/common/cpp/react/renderer/components/rnsvg/RNSVGShadowNodes.h @@ -66,6 +66,15 @@ using RNSVGFeCompositeShadowNode = RNSVGConcreteShadowNode< RNSVGFeCompositeComponentName, RNSVGFeCompositeProps>; +JSI_EXPORT extern const char RNSVGFeConvolveMatrixComponentName[]; + +/* +* `ShadowNode` for component. +*/ +using RNSVGFeConvolveMatrixShadowNode = RNSVGConcreteShadowNode< + RNSVGFeConvolveMatrixComponentName, + RNSVGFeConvolveMatrixProps>; + JSI_EXPORT extern const char RNSVGFeFloodComponentName[]; /* diff --git a/package.json b/package.json index a058ce201..50ea8a0d9 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "RNSVGFeBlend": "RNSVGFeBlend", "RNSVGFeColorMatrix": "RNSVGFeColorMatrix", "RNSVGFeComposite": "RNSVGFeComposite", + "RNSVGFeConvolveMatrix": "RNSVGFeConvolveMatrix", "RNSVGFeFlood": "RNSVGFeFlood", "RNSVGFeGaussianBlur": "RNSVGFeGaussianBlur", "RNSVGFeMerge": "RNSVGFeMerge", diff --git a/react-native.config.js b/react-native.config.js index 96657a69c..013e55855 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -9,6 +9,7 @@ module.exports = { 'RNSVGFeBlendComponentDescriptor', 'RNSVGFeColorMatrixComponentDescriptor', 'RNSVGFeCompositeComponentDescriptor', + 'RNSVGFeConvolveMatrixComponentDescriptor', 'RNSVGFeFloodComponentDescriptor', 'RNSVGFeGaussianBlurComponentDescriptor', 'RNSVGFeMergeComponentDescriptor', diff --git a/src/ReactNativeSVG.ts b/src/ReactNativeSVG.ts index 67d8ba3d9..29f86de30 100644 --- a/src/ReactNativeSVG.ts +++ b/src/ReactNativeSVG.ts @@ -27,6 +27,7 @@ import { RNSVGEllipse, RNSVGFeColorMatrix, RNSVGFeComposite, + RNSVGFeConvolveMatrix, RNSVGFeGaussianBlur, RNSVGFeMerge, RNSVGFeOffset, @@ -127,6 +128,7 @@ export { RNSVGEllipse, RNSVGFeColorMatrix, RNSVGFeComposite, + RNSVGFeConvolveMatrix, RNSVGFeGaussianBlur, RNSVGFeMerge, RNSVGFeOffset, diff --git a/src/elements/filters/FeConvolveMatrix.tsx b/src/elements/filters/FeConvolveMatrix.tsx index 4974d75ca..5860bf53c 100644 --- a/src/elements/filters/FeConvolveMatrix.tsx +++ b/src/elements/filters/FeConvolveMatrix.tsx @@ -1,7 +1,13 @@ +import { NativeMethods } from 'react-native'; +import { RNSVGFeConvolveMatrix } from '../../fabric'; import { BooleanProp, NumberArray, NumberProp } from '../../lib/extract/types'; -import { warnUnimplementedFilter } from '../../lib/util'; import FilterPrimitive from './FilterPrimitive'; import { EdgeMode } from './types'; +import { + extractFeConvolveMatrix, + extractFilter, + extractIn, +} from '../../lib/extract/extractFilter'; export interface FeConvolveMatrixProps { in?: string; @@ -21,10 +27,21 @@ export default class FeConvolveMatrix extends FilterPrimitive + this.refMethod(ref as (FeConvolveMatrix & NativeMethods) | null) + } + {...extractFilter(this.props)} + {...extractIn(this.props)} + {...extractFeConvolveMatrix(this.props)} + /> + ); } } diff --git a/src/fabric/FeConvolveMatrixNativeComponent.ts b/src/fabric/FeConvolveMatrixNativeComponent.ts new file mode 100644 index 000000000..c4d071f69 --- /dev/null +++ b/src/fabric/FeConvolveMatrixNativeComponent.ts @@ -0,0 +1,32 @@ +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import type { ViewProps } from './utils'; + +import type { NumberArray, NumberProp } from '../lib/extract/types'; +import type { UnsafeMixed } from './codegenUtils'; +import { WithDefault } from 'react-native/Libraries/Types/CodegenTypes'; + +interface FilterPrimitiveCommonProps { + x?: UnsafeMixed; + y?: UnsafeMixed; + width?: UnsafeMixed; + height?: UnsafeMixed; + result?: string; +} + +type FilterEdgeMode = 'duplicate' | 'wrap' | 'none'; + +export interface NativeProps extends ViewProps, FilterPrimitiveCommonProps { + in1?: string; + order?: UnsafeMixed; + kernelMatrix?: UnsafeMixed; + divisor?: UnsafeMixed; + bias?: WithDefault, 0>; + targetX?: UnsafeMixed; + targetY?: UnsafeMixed; + edgeMode?: WithDefault; + preserveAlpha?: WithDefault; +} + +export default codegenNativeComponent('RNSVGFeConvolveMatrix', { + interfaceOnly: true, +}); diff --git a/src/fabric/index.ts b/src/fabric/index.ts index 71147c7da..65714b993 100644 --- a/src/fabric/index.ts +++ b/src/fabric/index.ts @@ -24,6 +24,7 @@ import RNSVGFilter from './FilterNativeComponent'; import RNSVGFeBlend from './FeBlendNativeComponent'; import RNSVGFeColorMatrix from './FeColorMatrixNativeComponent'; import RNSVGFeComposite from './FeCompositeNativeComponent'; +import RNSVGFeConvolveMatrix from './FeConvolveMatrixNativeComponent'; import RNSVGFeFlood from './FeFloodNativeComponent'; import RNSVGFeGaussianBlur from './FeGaussianBlurNativeComponent'; import RNSVGFeMerge from './FeMergeNativeComponent'; @@ -56,6 +57,7 @@ export { RNSVGFeBlend, RNSVGFeColorMatrix, RNSVGFeComposite, + RNSVGFeConvolveMatrix, RNSVGFeFlood, RNSVGFeGaussianBlur, RNSVGFeMerge, diff --git a/src/lib/extract/extractFilter.ts b/src/lib/extract/extractFilter.ts index e921f6c50..dec8a330c 100644 --- a/src/lib/extract/extractFilter.ts +++ b/src/lib/extract/extractFilter.ts @@ -2,12 +2,14 @@ import React from 'react'; import { ColorValue, processColor } from 'react-native'; import { FeBlendProps as FeBlendComponentProps } from '../../elements/filters/FeBlend'; import { FeColorMatrixProps as FeColorMatrixComponentProps } from '../../elements/filters/FeColorMatrix'; +import { FeConvolveMatrixProps as FeConvolveMatrixComponentProps } from '../../elements/filters/FeConvolveMatrix'; import { FeCompositeProps as FeCompositeComponentProps } from '../../elements/filters/FeComposite'; import { FeFloodProps as FeFloodComponentProps } from '../../elements/filters/FeFlood'; import { FeGaussianBlurProps as FeGaussianBlurComponentProps } from '../../elements/filters/FeGaussianBlur'; import { FeMergeProps as FeMergeComponentProps } from '../../elements/filters/FeMerge'; import { NativeProps as FeBlendNativeProps } from '../../fabric/FeBlendNativeComponent'; import { NativeProps as FeColorMatrixNativeProps } from '../../fabric/FeColorMatrixNativeComponent'; +import { NativeProps as FeConvolveMatrixNativeProps } from '../../fabric/FeConvolveMatrixNativeComponent'; import { NativeProps as FeCompositeNativeProps } from '../../fabric/FeCompositeNativeComponent'; import { NativeProps as FeFloodNativeProps } from '../../fabric/FeFloodNativeComponent'; import { NativeProps as FeGaussianBlurNativeProps } from '../../fabric/FeGaussianBlurNativeComponent'; @@ -180,3 +182,49 @@ export const extractFeMerge = ( return { nodes }; }; + +export const extractFeConvolveMatrix = ( + props: FeConvolveMatrixComponentProps +) => { + const extracted: FeConvolveMatrixNativeProps = {}; + + if (props.order !== undefined) { + extracted.order = props.order; + } + + if (props.kernelMatrix !== undefined) { + extracted.kernelMatrix = props.kernelMatrix; + } + + if (props.divisor === 0 || props.divisor === '0') { + extracted.divisor = 1; + } else { + extracted.divisor = props.divisor; + } + + if (props.bias !== undefined) { + extracted.bias = props.bias; + } + + if (props.targetX !== undefined) { + extracted.targetX = props.targetX; + } + + if (props.targetY !== undefined) { + extracted.targetY = props.targetY; + } + + if (props.edgeMode !== undefined) { + extracted.edgeMode = props.edgeMode; + } + + if (props.preserveAlpha === 'false') { + extracted.preserveAlpha = false; + } else if (props.preserveAlpha === 'true') { + extracted.preserveAlpha = true; + } else { + extracted.preserveAlpha = props.preserveAlpha; + } + + return extracted; +};