From b6d8b49fbc7c5402479a47069345944086920c13 Mon Sep 17 00:00:00 2001 From: William Candillon Date: Sun, 18 May 2025 11:50:48 +0200 Subject: [PATCH 01/10] :wrench: --- .../skia/cpp/api/JsiSkImageFilterFactory.h | 99 ++++++++----- .../types/ImageFilter/ImageFilterFactory.ts | 55 +++++--- .../src/skia/web/JsiSkImageFilterFactory.ts | 133 ++++++++++++++---- .../sksg/Recorder/commands/ImageFilters.ts | 2 +- 4 files changed, 202 insertions(+), 87 deletions(-) diff --git a/packages/skia/cpp/api/JsiSkImageFilterFactory.h b/packages/skia/cpp/api/JsiSkImageFilterFactory.h index 3488401b04..6bc5eb1c3c 100644 --- a/packages/skia/cpp/api/JsiSkImageFilterFactory.h +++ b/packages/skia/cpp/api/JsiSkImageFilterFactory.h @@ -20,46 +20,64 @@ namespace RNSkia { namespace jsi = facebook::jsi; +inline bool hasOptionalArgument(const jsi::Value *arguments, size_t count, + size_t index) { + return (index < count && !arguments[index].isNull() && + !arguments[index].isUndefined()); +} + class JsiSkImageFilterFactory : public JsiSkHostObject { public: JSI_HOST_FUNCTION(MakeBlur) { float sigmaX = arguments[0].asNumber(); float sigmaY = arguments[1].asNumber(); int tileMode = arguments[2].asNumber(); - sk_sp imageFilter; - if (!arguments[3].isNull()) { + sk_sp imageFilter = nullptr; + if (hasOptionalArgument(arguments, count, 3)) { imageFilter = JsiSkImageFilter::fromValue(runtime, arguments[3]); } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 4)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[4]); + } return jsi::Object::createFromHostObject( runtime, std::make_shared( getContext(), SkImageFilters::Blur(sigmaX, sigmaY, (SkTileMode)tileMode, - imageFilter))); + imageFilter, cropRect))); } JSI_HOST_FUNCTION(MakeColorFilter) { auto cf = JsiSkColorFilter::fromValue(runtime, arguments[0]); - sk_sp input; - if (!arguments[1].isNull()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 1)) { input = JsiSkImageFilter::fromValue(runtime, arguments[1]); } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 2)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[2]); + } return jsi::Object::createFromHostObject( runtime, std::make_shared( getContext(), SkImageFilters::ColorFilter( - std::move(cf), std::move(input)))); + std::move(cf), std::move(input), cropRect))); } JSI_HOST_FUNCTION(MakeOffset) { auto x = arguments[0].asNumber(); auto y = arguments[1].asNumber(); - sk_sp input; - if (!arguments[2].isNull()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 2)) { input = JsiSkImageFilter::fromValue(runtime, arguments[2]); } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 3)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[3]); + } return jsi::Object::createFromHostObject( runtime, std::make_shared( - getContext(), SkImageFilters::Offset(x, y, std::move(input)))); + getContext(), SkImageFilters::Offset(x, y, std::move(input), cropRect))); } JSI_HOST_FUNCTION(MakeDisplacementMap) { @@ -69,32 +87,45 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { static_cast(arguments[1].asNumber()); auto scale = arguments[2].asNumber(); auto in2 = JsiSkImageFilter::fromValue(runtime, arguments[3]); - sk_sp input; - if (!arguments[4].isNull()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 4)) { input = JsiSkImageFilter::fromValue(runtime, arguments[4]); } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 5)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[5]); + } return jsi::Object::createFromHostObject( runtime, std::make_shared( getContext(), SkImageFilters::DisplacementMap( fXChannelSelector, fYChannelSelector, scale, - std::move(in2), std::move(input)))); + std::move(in2), std::move(input), cropRect))); } JSI_HOST_FUNCTION(MakeShader) { auto shader = JsiSkShader::fromValue(runtime, arguments[0]); + SkImageFilters::Dither dither = SkImageFilters::Dither::kNo; + if (hasOptionalArgument(arguments, count, 1)) { + dither = arguments[1].asBool() ? SkImageFilters::Dither::kYes + : SkImageFilters::Dither::kNo; + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 2)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[2]); + } return jsi::Object::createFromHostObject( runtime, std::make_shared( - getContext(), SkImageFilters::Shader(std::move(shader)))); + getContext(), SkImageFilters::Shader(std::move(shader), dither, cropRect))); } JSI_HOST_FUNCTION(MakeCompose) { - sk_sp outer; - if (!arguments[0].isNull() && !arguments[0].isUndefined()) { + sk_sp outer = nullptr; + if (hasOptionalArgument(arguments, count, 0)) { outer = JsiSkImageFilter::fromValue(runtime, arguments[0]); } - sk_sp inner; - if (!arguments[1].isNull() && !arguments[1].isUndefined()) { + sk_sp inner = nullptr; + if (hasOptionalArgument(arguments, count, 1)) { inner = JsiSkImageFilter::fromValue(runtime, arguments[1]); } return jsi::Object::createFromHostObject( @@ -109,12 +140,12 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { JsiSkImageFilter::fromValue(runtime, arguments[1]); sk_sp foreground = nullptr; - if (count > 2 && !arguments[2].isNull()) { + if (hasOptionalArgument(arguments, count, 2)) { foreground = JsiSkImageFilter::fromValue(runtime, arguments[2]); } SkImageFilters::CropRect cropRect = {}; - if (count > 3 && !arguments[3].isUndefined()) { + if (hasOptionalArgument(arguments, count, 3)) { cropRect = *JsiSkRect::fromValue(runtime, arguments[3]); } @@ -131,12 +162,12 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { auto sigmaX = arguments[2].asNumber(); auto sigmaY = arguments[3].asNumber(); auto color = JsiSkColor::fromValue(runtime, arguments[4]); - sk_sp input; - if (!arguments[5].isNull() && !arguments[5].isUndefined()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 5)) { input = JsiSkImageFilter::fromValue(runtime, arguments[5]); } SkImageFilters::CropRect cropRect = {}; - if (count > 6 && !arguments[6].isUndefined()) { + if (hasOptionalArgument(arguments, count, 6)) { cropRect = *JsiSkRect::fromValue(runtime, arguments[6]); } return jsi::Object::createFromHostObject( @@ -152,12 +183,12 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { auto sigmaX = arguments[2].asNumber(); auto sigmaY = arguments[3].asNumber(); auto color = JsiSkColor::fromValue(runtime, arguments[4]); - sk_sp input; - if (!arguments[5].isNull() && !arguments[5].isUndefined()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 5)) { input = JsiSkImageFilter::fromValue(runtime, arguments[5]); } SkImageFilters::CropRect cropRect = {}; - if (count > 6 && !arguments[6].isUndefined()) { + if (hasOptionalArgument(arguments, count, 6)) { cropRect = *JsiSkRect::fromValue(runtime, arguments[6]); } return jsi::Object::createFromHostObject( @@ -170,12 +201,12 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { JSI_HOST_FUNCTION(MakeErode) { auto rx = arguments[0].asNumber(); auto ry = arguments[1].asNumber(); - sk_sp input; - if (!arguments[2].isNull() && !arguments[2].isUndefined()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 2)) { input = JsiSkImageFilter::fromValue(runtime, arguments[2]); } SkImageFilters::CropRect cropRect = {}; - if (count > 3 && !arguments[3].isUndefined()) { + if (hasOptionalArgument(arguments, count, 3)) { cropRect = *JsiSkRect::fromValue(runtime, arguments[3]); } return jsi::Object::createFromHostObject( @@ -187,12 +218,12 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { JSI_HOST_FUNCTION(MakeDilate) { auto rx = arguments[0].asNumber(); auto ry = arguments[1].asNumber(); - sk_sp input; - if (!arguments[2].isNull() && !arguments[2].isUndefined()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 2)) { input = JsiSkImageFilter::fromValue(runtime, arguments[2]); } SkImageFilters::CropRect cropRect = {}; - if (count > 3 && !arguments[3].isUndefined()) { + if (hasOptionalArgument(arguments, count, 3)) { cropRect = *JsiSkRect::fromValue(runtime, arguments[3]); } return jsi::Object::createFromHostObject( @@ -205,12 +236,12 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { auto rtb = JsiSkRuntimeShaderBuilder::fromValue(runtime, arguments[0]); const char *childName = ""; - if (!arguments[1].isNull() && !arguments[1].isUndefined()) { + if (hasOptionalArgument(arguments, count, 1)) { childName = arguments[1].asString(runtime).utf8(runtime).c_str(); } - sk_sp input; - if (!arguments[2].isNull() && !arguments[2].isUndefined()) { + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 2)) { input = JsiSkImageFilter::fromValue(runtime, arguments[2]); } return jsi::Object::createFromHostObject( diff --git a/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts b/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts index ee691877c5..c9025c5836 100644 --- a/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts +++ b/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts @@ -21,11 +21,13 @@ export interface ImageFilterFactory { * @param dx - Offset along the X axis * @param dy - Offset along the X axis * @param input - if null, it will use the dynamic source image + * @param cropRect - Optional rectangle that crops the input and output */ MakeOffset( dx: number, dy: number, - input: SkImageFilter | null + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter; /** * Spatially displace pixel values of the filtered image @@ -35,45 +37,56 @@ export interface ImageFilterFactory { * @param scale - Scale factor to be used in the displacement * @param in1 - Source image filter to use for the displacement * @param input - if null, it will use the dynamic source image + * @param cropRect - Optional rectangle that crops the input and output */ MakeDisplacementMap( channelX: ColorChannel, channelY: ColorChannel, scale: number, in1: SkImageFilter, - input: SkImageFilter | null + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter; /** * Transforms a shader into an impage filter * * @param shader - The Shader to be transformed - * @param input - if null, it will use the dynamic source image + * @param dither - Whether to apply dithering to the shader + * @param cropRect - Optional rectangle that crops the input and output */ - MakeShader(shader: SkShader, input: SkImageFilter | null): SkImageFilter; + MakeShader( + shader: SkShader, + dither?: boolean, + cropRect?: SkRect | null + ): SkImageFilter; /** * Create a filter that blurs its input by the separate X and Y sigmas. The provided tile mode * is used when the blur kernel goes outside the input image. * * @param sigmaX - The Gaussian sigma value for blurring along the X axis. * @param sigmaY - The Gaussian sigma value for blurring along the Y axis. - * @param mode + * @param mode - The tile mode to use when blur kernel goes outside the image * @param input - if null, it will use the dynamic source image (e.g. a saved layer) + * @param cropRect - Optional rectangle that crops the input and output */ MakeBlur( sigmaX: number, sigmaY: number, mode: TileMode, - input: SkImageFilter | null + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter; /** * Create a filter that applies the color filter to the input filter results. - * @param cf + * @param colorFilter - The color filter to apply * @param input - if null, it will use the dynamic source image (e.g. a saved layer) + * @param cropRect - Optional rectangle that crops the input and output */ MakeColorFilter( - cf: SkColorFilter, - input: SkImageFilter | null + colorFilter: SkColorFilter, + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter; /** @@ -105,8 +118,8 @@ export interface ImageFilterFactory { sigmaX: number, sigmaY: number, color: SkColor, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ) => SkImageFilter; /** * Create a filter that renders a drop shadow, in exactly the same manner as ::DropShadow, except @@ -126,8 +139,8 @@ export interface ImageFilterFactory { sigmaX: number, sigmaY: number, color: SkColor, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ) => SkImageFilter; /** * Create a filter that erodes each input pixel's channel values to the minimum channel value @@ -140,8 +153,8 @@ export interface ImageFilterFactory { MakeErode: ( rx: number, ry: number, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ) => SkImageFilter; /** * Create a filter that dilates each input pixel's channel values to the max value within the @@ -154,21 +167,21 @@ export interface ImageFilterFactory { MakeDilate: ( rx: number, ry: number, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ) => SkImageFilter; /** * This filter takes an SkBlendMode and uses it to composite the two filters together. * @param mode The blend mode that defines the compositing operation * @param background The Dst pixels used in blending, if null the source bitmap is used. * @param foreground The Src pixels used in blending, if null the source bitmap is used. - * @cropRect Optional rectangle to crop input and output. + * @param cropRect Optional rectangle to crop input and output. */ MakeBlend: ( mode: BlendMode, background: SkImageFilter, - foreground: SkImageFilter | null, - cropRect?: SkRect + foreground?: SkImageFilter | null, + cropRect?: SkRect | null ) => SkImageFilter; /** * Create a filter that fills the output with the per-pixel evaluation of the SkShader produced @@ -187,6 +200,6 @@ export interface ImageFilterFactory { MakeRuntimeShader: ( builder: SkRuntimeShaderBuilder, childShaderName: string | null, - input: SkImageFilter | null + input?: SkImageFilter | null ) => SkImageFilter; } diff --git a/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts b/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts index 89c58f2e9a..3c62551488 100644 --- a/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts +++ b/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts @@ -25,9 +25,21 @@ export class JsiSkImageFilterFactory super(CanvasKit); } - MakeOffset(dx: number, dy: number, input: SkImageFilter | null) { + MakeOffset( + dx: number, + dy: number, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ) { const inputFilter = - input === null ? null : JsiSkImageFilter.fromValue(input); + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input); + if (cropRect) { + console.warn( + "cropRect is not supported on React Native Web for MakeOffset" + ); + } const filter = this.CanvasKit.ImageFilter.MakeOffset(dx, dy, inputFilter); return new JsiSkImageFilter(this.CanvasKit, filter); } @@ -37,10 +49,18 @@ export class JsiSkImageFilterFactory channelY: ColorChannel, scale: number, in1: SkImageFilter, - input: SkImageFilter | null + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter { const inputFilter = - input === null ? null : JsiSkImageFilter.fromValue(input); + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input); + if (cropRect) { + console.warn( + "cropRect is not supported on React Native Web for MakeDisplacementMap" + ); + } const filter = this.CanvasKit.ImageFilter.MakeDisplacementMap( getEnum(this.CanvasKit, "ColorChannel", channelX), getEnum(this.CanvasKit, "ColorChannel", channelY), @@ -51,7 +71,21 @@ export class JsiSkImageFilterFactory return new JsiSkImageFilter(this.CanvasKit, filter); } - MakeShader(shader: SkShader, _input: SkImageFilter | null): SkImageFilter { + MakeShader( + shader: SkShader, + dither?: boolean, + cropRect?: SkRect | null + ): SkImageFilter { + if (dither !== undefined) { + console.warn( + "dither parameter is not supported on React Native Web for MakeShader" + ); + } + if (cropRect) { + console.warn( + "cropRect is not supported on React Native Web for MakeShader" + ); + } const filter = this.CanvasKit.ImageFilter.MakeShader( JsiSkImageFilter.fromValue(shader) ); @@ -62,25 +96,44 @@ export class JsiSkImageFilterFactory sigmaX: number, sigmaY: number, mode: TileMode, - input: SkImageFilter | null + input?: SkImageFilter | null, + cropRect?: SkRect | null ) { + if (cropRect) { + console.warn( + "cropRect is not supported on React Native Web for MakeBlur" + ); + } return new JsiSkImageFilter( this.CanvasKit, this.CanvasKit.ImageFilter.MakeBlur( sigmaX, sigmaY, getEnum(this.CanvasKit, "TileMode", mode), - input === null ? null : JsiSkImageFilter.fromValue(input) + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input) ) ); } - MakeColorFilter(cf: SkColorFilter, input: SkImageFilter | null) { + MakeColorFilter( + colorFilter: SkColorFilter, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ) { + if (cropRect) { + console.warn( + "cropRect is not supported on React Native Web for MakeColorFilter" + ); + } return new JsiSkImageFilter( this.CanvasKit, this.CanvasKit.ImageFilter.MakeColorFilter( - JsiSkColorFilter.fromValue(cf), - input === null ? null : JsiSkImageFilter.fromValue(input) + JsiSkColorFilter.fromValue(colorFilter), + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input) ) ); } @@ -101,13 +154,17 @@ export class JsiSkImageFilterFactory sigmaX: number, sigmaY: number, color: SkColor, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter { const inputFilter = - input === null ? null : JsiSkImageFilter.fromValue(input); + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input); if (cropRect) { - throwNotImplementedOnRNWeb(); + console.warn( + "cropRect is not supported on React Native Web for MakeDropShadow" + ); } const filter = this.CanvasKit.ImageFilter.MakeDropShadow( dx, @@ -126,13 +183,17 @@ export class JsiSkImageFilterFactory sigmaX: number, sigmaY: number, color: SkColor, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter { const inputFilter = - input === null ? null : JsiSkImageFilter.fromValue(input); + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input); if (cropRect) { - throwNotImplementedOnRNWeb(); + console.warn( + "cropRect is not supported on React Native Web for MakeDropShadowOnly" + ); } const filter = this.CanvasKit.ImageFilter.MakeDropShadowOnly( dx, @@ -148,13 +209,17 @@ export class JsiSkImageFilterFactory MakeErode( rx: number, ry: number, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter { const inputFilter = - input === null ? null : JsiSkImageFilter.fromValue(input); + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input); if (cropRect) { - throwNotImplementedOnRNWeb(); + console.warn( + "cropRect is not supported on React Native Web for MakeErode" + ); } const filter = this.CanvasKit.ImageFilter.MakeErode(rx, ry, inputFilter); return new JsiSkImageFilter(this.CanvasKit, filter); @@ -163,13 +228,17 @@ export class JsiSkImageFilterFactory MakeDilate( rx: number, ry: number, - input: SkImageFilter | null, - cropRect?: SkRect + input?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter { const inputFilter = - input === null ? null : JsiSkImageFilter.fromValue(input); + input === null || input === undefined + ? null + : JsiSkImageFilter.fromValue(input); if (cropRect) { - throwNotImplementedOnRNWeb(); + console.warn( + "cropRect is not supported on React Native Web for MakeDilate" + ); } const filter = this.CanvasKit.ImageFilter.MakeDilate(rx, ry, inputFilter); return new JsiSkImageFilter(this.CanvasKit, filter); @@ -178,15 +247,17 @@ export class JsiSkImageFilterFactory MakeBlend( mode: BlendMode, background: SkImageFilter, - foreground: SkImageFilter | null, - cropRect?: SkRect + foreground?: SkImageFilter | null, + cropRect?: SkRect | null ): SkImageFilter { const inputFilter = - foreground === null + foreground === null || foreground === undefined ? null : JsiSkImageFilter.fromValue(foreground); if (cropRect) { - throwNotImplementedOnRNWeb(); + console.warn( + "cropRect is not supported on React Native Web for MakeBlend" + ); } const filter = this.CanvasKit.ImageFilter.MakeBlend( getEnum(this.CanvasKit, "BlendMode", mode), @@ -199,7 +270,7 @@ export class JsiSkImageFilterFactory MakeRuntimeShader( _builder: SkRuntimeShaderBuilder, _childShaderName: string | null, - _input: SkImageFilter | null + _input?: SkImageFilter | null ) { return throwNotImplementedOnRNWeb(); } diff --git a/packages/skia/src/sksg/Recorder/commands/ImageFilters.ts b/packages/skia/src/sksg/Recorder/commands/ImageFilters.ts index c2f0424e8a..b3731f18d2 100644 --- a/packages/skia/src/sksg/Recorder/commands/ImageFilters.ts +++ b/packages/skia/src/sksg/Recorder/commands/ImageFilters.ts @@ -151,7 +151,7 @@ const declareDisplacementMapImageFilter = ( if (!shader) { throw new Error("DisplacementMap expects a shader as child"); } - const map = ctx.Skia.ImageFilter.MakeShader(shader, null); + const map = ctx.Skia.ImageFilter.MakeShader(shader); const imgf = ctx.Skia.ImageFilter.MakeDisplacementMap( ColorChannel[enumKey(channelX)], ColorChannel[enumKey(channelY)], From 03a0b6a68450aa641183cd52d4b508bdafa10adf Mon Sep 17 00:00:00 2001 From: William Candillon Date: Sun, 18 May 2025 13:07:28 +0200 Subject: [PATCH 02/10] :new: --- apps/example/ios/Podfile.lock | 159 +++---- .../skia/cpp/api/JsiSkImageFilterFactory.h | 408 +++++++++++++++++- .../types/ImageFilter/ImageFilterFactory.ts | 357 +++++++++++++++ .../src/skia/web/JsiSkImageFilterFactory.ts | 164 +++++++ 4 files changed, 984 insertions(+), 104 deletions(-) diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock index 8980bbf320..1af6dee8d6 100644 --- a/apps/example/ios/Podfile.lock +++ b/apps/example/ios/Podfile.lock @@ -1327,49 +1327,6 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-slider (4.5.6): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - react-native-slider/common (= 4.5.6) - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - - react-native-slider/common (4.5.6): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.11.18.00) - - RCTRequired - - RCTTypeSafety - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCodegen - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - React-NativeModulesApple (0.78.0): - glog - hermes-engine @@ -1957,7 +1914,6 @@ DEPENDENCIES: - React-microtasksnativemodule (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) - react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`) - "react-native-skia (from `../../../node_modules/@shopify/react-native-skia`)" - - "react-native-slider (from `../../../node_modules/@react-native-community/slider`)" - React-NativeModulesApple (from `../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-perflogger (from `../../../node_modules/react-native/ReactCommon/reactperflogger`) - React-performancetimeline (from `../../../node_modules/react-native/ReactCommon/react/performance/timeline`) @@ -2079,8 +2035,6 @@ EXTERNAL SOURCES: :path: "../../../node_modules/react-native-safe-area-context" react-native-skia: :path: "../../../node_modules/@shopify/react-native-skia" - react-native-slider: - :path: "../../../node_modules/@react-native-community/slider" React-NativeModulesApple: :path: "../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-perflogger: @@ -2162,77 +2116,76 @@ SPEC CHECKSUMS: fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8 hermes-engine: b417d2b2aee3b89b58e63e23a51e02be91dc876d - RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35 RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea RCTTypeSafety: c135dd2bf50402d87fd12884cbad5d5e64850edd React: b229c49ed5898dab46d60f61ed5a0bfa2ee2fadb React-callinvoker: 2ac508e92c8bd9cf834cc7d7787d94352e4af58f - React-Core: 13cdd1558d0b3f6d9d5a22e14d89150280e79f02 - React-CoreModules: b07a6744f48305405e67c845ebf481b6551b712a - React-cxxreact: 1055a86c66ac35b4e80bd5fb766aed5f494dfff4 + React-Core: 325b4f6d9162ae8b9a6ff42fe78e260eb124180d + React-CoreModules: 558041e5258f70cd1092f82778d07b8b2ff01897 + React-cxxreact: 8fff17cbe76e6a8f9991b59552e1235429f9c74b React-debug: 0a5fcdbacc6becba0521e910c1bcfdb20f32a3f6 - React-defaultsnativemodule: 4bb28fc97fee5be63a9ebf8f7a435cfe8ba69459 - React-domnativemodule: b36a11c2597243d7563985028c51ece988d8ae33 - React-Fabric: afc561718f25b2cd800b709d934101afe376a12c - React-FabricComponents: f4e0a4e18a27bf6d39cbf2a0b42f37a92fa4e37f - React-FabricImage: 37d8e8b672eda68a19d71143eb65148084efb325 + React-defaultsnativemodule: 618dc50a0fad41b489997c3eb7aba3a74479fd14 + React-domnativemodule: 7ba599afb6c2a7ec3eb6450153e2efe0b8747e9a + React-Fabric: 252112089d2c63308f4cbfade4010b6606db67d1 + React-FabricComponents: 3c0f75321680d14d124438ab279c64ec2a3d13c4 + React-FabricImage: 728b8061cdec2857ca885fd605ee03ad43ffca98 React-featureflags: 19682e02ef5861d96b992af16a19109c3dfc1200 - React-featureflagsnativemodule: d7cddf6d907b4e5ab84f9e744b7e88461656e48c - React-graphics: b0f78580cdaf5800d25437e3d41cc6c3d83b7aea - React-hermes: 71186f872c932e4574d5feb3ed754dda63a0b3bd - React-idlecallbacksnativemodule: dd2af19cdd3bc55149d17a2409ed72b694dfbe9c - React-ImageManager: a77dde8d5aa6a2b6962c702bf3a47695ef0aa32b - React-jserrorhandler: 9c14e89f12d5904257a79aaf84a70cd2e5ac07ba - React-jsi: 0775a66820496769ad83e629f0f5cce621a57fc7 - React-jsiexecutor: 2cf5ba481386803f3c88b85c63fa102cba5d769e - React-jsinspector: 8052d532bb7a98b6e021755674659802fb140cc5 - React-jsinspectortracing: bdd8fd0adcb4813663562e7874c5842449df6d8a - React-jsitracing: 2bab3bf55de3d04baf205def375fa6643c47c794 - React-logger: 795cd5055782db394f187f9db0477d4b25b44291 - React-Mapbuffer: 0502faf46cab8fb89cfc7bf3e6c6109b6ef9b5de - React-microtasksnativemodule: 663bc64e3a96c5fc91081923ae7481adc1359a78 - react-native-safe-area-context: 286b3e7b5589795bb85ffc38faf4c0706c48a092 - react-native-skia: 86f943730f6a64eea42fcebc02a9d9040370ce57 - react-native-slider: e7f302c8d3296ddb49c642473f77f8f98809d53b - React-NativeModulesApple: 16fbd5b040ff6c492dacc361d49e63cba7a6a7a1 - React-perflogger: ab51b7592532a0ea45bf6eed7e6cae14a368b678 - React-performancetimeline: bc2e48198ec814d578ac8401f65d78a574358203 + React-featureflagsnativemodule: 23528c7e7d50782b7ef0804168ba40bbaf1e86ab + React-graphics: fefe48f71bfe6f48fd037f59e8277b12e91b6be1 + React-hermes: a9a0c8377627b5506ef9a7b6f60a805c306e3f51 + React-idlecallbacksnativemodule: 7e2b6a3b70e042f89cd91dbd73c479bb39a72a7e + React-ImageManager: e3300996ac2e2914bf821f71e2f2c92ae6e62ae2 + React-jserrorhandler: fa75876c662e5d7e79d6efc763fc9f4c88e26986 + React-jsi: f3f51595cc4c089037b536368f016d4742bf9cf7 + React-jsiexecutor: cca6c232db461e2fd213a11e9364cfa6fdaa20eb + React-jsinspector: 2bd4c9fddf189d6ec2abf4948461060502582bef + React-jsinspectortracing: a417d8a0ad481edaa415734b4dac81e3e5ee7dc6 + React-jsitracing: 1ff7172c5b0522cbf6c98d82bdbb160e49b5804e + React-logger: 018826bfd51b9f18e87f67db1590bc510ad20664 + React-Mapbuffer: 3c11cee7737609275c7b66bd0b1de475f094cedf + React-microtasksnativemodule: 843f352b32aacbe13a9c750190d34df44c3e6c2c + react-native-safe-area-context: 0f14bce545abcdfbff79ce2e3c78c109f0be283e + react-native-skia: 8ea98bfb08c756d04fa37803fcc4ff049d47ab42 + React-NativeModulesApple: 88433b6946778bea9c153e27b671de15411bf225 + React-perflogger: 9e8d3c0dc0194eb932162812a168aa5dc662f418 + React-performancetimeline: 5a2d6efef52bdcefac079c7baa30934978acd023 React-RCTActionSheet: 592674cf61142497e0e820688f5a696e41bf16dd - React-RCTAnimation: 8fbb8dba757b49c78f4db403133ab6399a4ce952 - React-RCTAppDelegate: 7f88baa8cb4e5d6c38bb4d84339925c70c9ac864 - React-RCTBlob: f89b162d0fe6b570a18e755eb16cbe356d3c6d17 - React-RCTFabric: 8ad6d875abe6e87312cef90e4b15ef7f6bed72e6 - React-RCTFBReactNativeSpec: 8c29630c2f379c729300e4c1e540f3d1b78d1936 - React-RCTImage: ccac9969940f170503857733f9a5f63578e106e1 - React-RCTLinking: d82427bbf18415a3732105383dff119131cadd90 - React-RCTNetwork: 12ad4d0fbde939e00251ca5ca890da2e6825cc3c - React-RCTSettings: e7865bf9f455abf427da349c855f8644b5c39afa - React-RCTText: 2cdfd88745059ec3202a0842ea75a956c7d6f27d - React-RCTVibration: a3a1458e6230dfd64b3768ebc0a4aac430d9d508 + React-RCTAnimation: e6d669872f9b3b4ab9527aab283b7c49283236b7 + React-RCTAppDelegate: de2343fe08be4c945d57e0ecce44afcc7dd8fc03 + React-RCTBlob: 3e2dce94c56218becc4b32b627fc2293149f798d + React-RCTFabric: cac2c033381d79a5956e08550b0220cb2d78ea93 + React-RCTFBReactNativeSpec: d10ca5e0ccbfeac8c047361fedf8e4ac653887b6 + React-RCTImage: dc04b176c022d12a8f55ae7a7279b1e091066ae0 + React-RCTLinking: 88f5e37fe4f26fbc80791aa2a5f01baf9b9a3fd5 + React-RCTNetwork: f213693565efbd698b8e9c18d700a514b49c0c8e + React-RCTSettings: a2d32a90c45a3575568cad850abc45924999b8a5 + React-RCTText: 54cdcd1cbf6f6a91dc6317f5d2c2b7fc3f6bf7a0 + React-RCTVibration: 11dae0e7f577b5807bb7d31e2e881eb46f854fd4 React-rendererconsistency: 64e897e00d2568fd8dfe31e2496f80e85c0aaad1 - React-rendererdebug: a3f6d3ae7d2fa0035885026756281c07ee32479e + React-rendererdebug: 41ce452460c44bba715d9e41d5493a96de277764 React-rncore: 58748c2aa445f56b99e5118dad0aedb51c40ce9f - React-RuntimeApple: f0fda7bacabd32daa099cfda8f07466c30acd149 - React-RuntimeCore: 683ee0b6a76d4b4bf6fbf83a541895b4887cc636 + React-RuntimeApple: 7785ed0d8ae54da65a88736bb63ca97608a6d933 + React-RuntimeCore: 6029ea70bc77f98cfd43ebe69217f14e93ba1f12 React-runtimeexecutor: a188df372373baf5066e6e229177836488799f80 - React-RuntimeHermes: 907c8e9bec13ea6466b94828c088c24590d4d0b6 - React-runtimescheduler: a2e2a39125dd6426b5d8b773f689d660cd7c5f60 + React-RuntimeHermes: a264609c28b796edfffc8ae4cb8fad1773ab948b + React-runtimescheduler: 23ec3a1e0fb1ec752d1a9c1fb15258c30bfc7222 React-timing: bb220a53a795ed57976a4855c521f3de2f298fe5 - React-utils: 300d8bbb6555dcffaca71e7a0663201b5c7edbbc - ReactAppDependencyProvider: f2e81d80afd71a8058589e19d8a134243fa53f17 - ReactCodegen: 50b6e45bbbef9b39d9798820cdbe87bfc7922e22 - ReactCommon: 3d39389f8e2a2157d5c999f8fba57bd1c8f226f0 - ReactNativeHost: f2ecc49200441384efb6c6e8bffe62ba29ee16ae - ReactTestApp-DevSupport: 15d2ef4884e8f5fd30ded3dec59b010f76384f37 + React-utils: 3b054aaebe658fc710a8d239d0e4b9fd3e0b78f9 + ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8 + ReactCodegen: e232f8db3a40721044ec81b9388f95a7afaad36a + ReactCommon: 0c097b53f03d6bf166edbcd0915da32f3015dd90 + ReactNativeHost: f9584a700dc379cfa223203d0d51e492df84a7a8 + ReactTestApp-DevSupport: 16672810b0675a3bab6be3b3e85f1ce4b93144da ReactTestApp-Resources: 1bd9ff10e4c24f2ad87101a32023721ae923bccf - RNGestureHandler: 66e593addd8952725107cfaa4f5e3378e946b541 - RNReanimated: b292a2aee945230a9c5e01889043ba088b5fb9b8 - RNScreens: 0f01bbed9bd8045a8d58e4b46993c28c7f498f3c - RNSVG: 8588ee1ca9b2e6fd2c99466e35b3db0e9f81bb40 + RNGestureHandler: dcb1b1db024f3744b03af56d132f4f72c4c27195 + RNReanimated: 3b2312c84f8c747ab1e9d9c3ce879e93a5ba96f3 + RNScreens: 790123c4a28783d80a342ce42e8c7381bed62db1 + RNSVG: 8126581b369adf6a0004b6a6cab1a55e3002d5b0 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: 9b7fb56e7b08cde60e2153344fa6afbd88e5d99f + Yoga: afd04ff05ebe0121a00c468a8a3c8080221cb14c PODFILE CHECKSUM: 87506345285a0371afb28b9c3e6daaa999c214f3 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/packages/skia/cpp/api/JsiSkImageFilterFactory.h b/packages/skia/cpp/api/JsiSkImageFilterFactory.h index 6bc5eb1c3c..b9017b106b 100644 --- a/packages/skia/cpp/api/JsiSkImageFilterFactory.h +++ b/packages/skia/cpp/api/JsiSkImageFilterFactory.h @@ -7,12 +7,14 @@ #include "JsiSkHostObjects.h" #include "JsiSkImageFilter.h" +#include "JsiSkPicture.h" #include "JsiSkRuntimeShaderBuilder.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdocumentation" #include "include/core/SkImageFilter.h" +#include "include/core/SkPoint3.h" #pragma clang diagnostic pop @@ -250,6 +252,393 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { *rtb, childName, std::move(input)))); } + JSI_HOST_FUNCTION(MakeArithmetic) { + float k1 = arguments[0].asNumber(); + float k2 = arguments[1].asNumber(); + float k3 = arguments[2].asNumber(); + float k4 = arguments[3].asNumber(); + bool enforcePMColor = arguments[4].asBool(); + sk_sp background = nullptr; + if (hasOptionalArgument(arguments, count, 5)) { + background = JsiSkImageFilter::fromValue(runtime, arguments[5]); + } + sk_sp foreground = nullptr; + if (hasOptionalArgument(arguments, count, 6)) { + foreground = JsiSkImageFilter::fromValue(runtime, arguments[6]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 7)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[7]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), + SkImageFilters::Arithmetic( + k1, k2, k3, k4, enforcePMColor, std::move(background), + std::move(foreground), cropRect))); + } + + JSI_HOST_FUNCTION(MakeCrop) { + SkRect rect = *JsiSkRect::fromValue(runtime, arguments[0]); + SkTileMode tileMode = SkTileMode::kDecal; + if (hasOptionalArgument(arguments, count, 1)) { + tileMode = (SkTileMode)arguments[1].asNumber(); + } + sk_sp imageFilter = nullptr; + if (hasOptionalArgument(arguments, count, 2)) { + imageFilter = JsiSkImageFilter::fromValue(runtime, arguments[2]); + } + return jsi::Object::createFromHostObject( + runtime, + std::make_shared( + getContext(), + SkImageFilters::Crop(rect, tileMode, std::move(imageFilter)))); + } + + JSI_HOST_FUNCTION(MakeEmpty) { + return jsi::Object::createFromHostObject( + runtime, std::make_shared(getContext(), + SkImageFilters::Empty())); + } + + inline SkPoint3 SkPoint3FromValue(jsi::Runtime &runtime, + const jsi::Value &obj) { + const auto &object = obj.asObject(runtime); + auto x = object.getProperty(runtime, "x").asNumber(); + auto y = object.getProperty(runtime, "y").asNumber(); + auto z = object.getProperty(runtime, "z").asNumber(); + return SkPoint3::Make(x, y, z); + } + + JSI_HOST_FUNCTION(MakeDistantLitDiffuse) { + SkPoint3 direction = SkPoint3FromValue(runtime, arguments[0]); + SkColor lightColor = JsiSkColor::fromValue(runtime, arguments[1]); + float surfaceScale = arguments[2].asNumber(); + float kd = arguments[3].asNumber(); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 4)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[4]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 5)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[5]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), SkImageFilters::DistantLitDiffuse( + direction, lightColor, surfaceScale, kd, + std::move(input), cropRect))); + } + + JSI_HOST_FUNCTION(MakePointLitDiffuse) { + SkPoint3 location = SkPoint3FromValue(runtime, arguments[0]); + SkColor lightColor = JsiSkColor::fromValue(runtime, arguments[1]); + float surfaceScale = arguments[2].asNumber(); + float kd = arguments[3].asNumber(); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 4)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[4]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 5)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[5]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), SkImageFilters::PointLitDiffuse( + location, lightColor, surfaceScale, kd, + std::move(input), cropRect))); + } + + JSI_HOST_FUNCTION(MakeSpotLitDiffuse) { + SkPoint3 location = SkPoint3FromValue(runtime, arguments[0]); + SkPoint3 target = SkPoint3FromValue(runtime, arguments[1]); + float falloffExponent = arguments[2].asNumber(); + float cutoffAngle = arguments[3].asNumber(); + SkColor lightColor = JsiSkColor::fromValue(runtime, arguments[4]); + float surfaceScale = arguments[5].asNumber(); + float kd = arguments[6].asNumber(); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 7)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[7]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 8)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[8]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), SkImageFilters::SpotLitDiffuse( + location, target, falloffExponent, + cutoffAngle, lightColor, surfaceScale, + kd, std::move(input), cropRect))); + } + + JSI_HOST_FUNCTION(MakeDistantLitSpecular) { + SkPoint3 direction = SkPoint3FromValue(runtime, arguments[0]); + SkColor lightColor = JsiSkColor::fromValue(runtime, arguments[1]); + float surfaceScale = arguments[2].asNumber(); + float ks = arguments[3].asNumber(); + float shininess = arguments[4].asNumber(); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 5)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[5]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 6)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[6]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), SkImageFilters::DistantLitSpecular( + direction, lightColor, surfaceScale, ks, + shininess, std::move(input), cropRect))); + } + + JSI_HOST_FUNCTION(MakePointLitSpecular) { + SkPoint3 location = SkPoint3FromValue(runtime, arguments[0]); + SkColor lightColor = JsiSkColor::fromValue(runtime, arguments[1]); + float surfaceScale = arguments[2].asNumber(); + float ks = arguments[3].asNumber(); + float shininess = arguments[4].asNumber(); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 5)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[5]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 6)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[6]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), SkImageFilters::PointLitSpecular( + location, lightColor, surfaceScale, ks, + shininess, std::move(input), cropRect))); + } + + JSI_HOST_FUNCTION(MakeSpotLitSpecular) { + SkPoint3 location = SkPoint3FromValue(runtime, arguments[0]); + SkPoint3 target = SkPoint3FromValue(runtime, arguments[1]); + float falloffExponent = arguments[2].asNumber(); + float cutoffAngle = arguments[3].asNumber(); + SkColor lightColor = JsiSkColor::fromValue(runtime, arguments[4]); + float surfaceScale = arguments[5].asNumber(); + float ks = arguments[6].asNumber(); + float shininess = arguments[7].asNumber(); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 8)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[8]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 9)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[9]); + } + return jsi::Object::createFromHostObject( + runtime, + std::make_shared( + getContext(), + SkImageFilters::SpotLitSpecular( + location, target, falloffExponent, cutoffAngle, lightColor, + surfaceScale, ks, shininess, std::move(input), cropRect))); + } + + JSI_HOST_FUNCTION(MakeImage) { + sk_sp image = JsiSkImage::fromValue(runtime, arguments[0]); + SkRect srcRect; + if (hasOptionalArgument(arguments, count, 1)) { + srcRect = *JsiSkRect::fromValue(runtime, arguments[1]); + } else { + srcRect = SkRect::Make(image->bounds()); + } + SkRect dstRect; + if (hasOptionalArgument(arguments, count, 2)) { + dstRect = *JsiSkRect::fromValue(runtime, arguments[2]); + } else { + dstRect = srcRect; + } + SkFilterMode filterMode = SkFilterMode::kNearest; + if (hasOptionalArgument(arguments, count, 3)) { + filterMode = (SkFilterMode)arguments[3].asNumber(); + } + SkMipmapMode mipmap = SkMipmapMode::kNone; + if (hasOptionalArgument(arguments, count, 4)) { + mipmap = (SkMipmapMode)arguments[4].asNumber(); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), SkImageFilters::Image( + std::move(image), srcRect, dstRect, + SkSamplingOptions(filterMode, mipmap)))); + } + + JSI_HOST_FUNCTION(MakeMagnifier) { + SkRect lensBounds = *JsiSkRect::fromValue(runtime, arguments[0]); + float zoomAmount = arguments[1].asNumber(); + float inset = arguments[2].asNumber(); + SkFilterMode filterMode = SkFilterMode::kNearest; + if (hasOptionalArgument(arguments, count, 3)) { + filterMode = (SkFilterMode)arguments[3].asNumber(); + } + SkMipmapMode mipmap = SkMipmapMode::kNone; + if (hasOptionalArgument(arguments, count, 4)) { + mipmap = (SkMipmapMode)arguments[4].asNumber(); + } + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 5)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[5]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 6)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[6]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), SkImageFilters::Magnifier( + lensBounds, zoomAmount, inset, + SkSamplingOptions(filterMode, mipmap), + input, cropRect))); + } + + JSI_HOST_FUNCTION(MakeMatrixConvolution) { + SkISize kernelSize = + SkISize(arguments[0].asNumber(), arguments[1].asNumber()); + std::vector kernel; + auto kernelArray = arguments[2].asObject(runtime).asArray(runtime); + auto size = kernelArray.size(runtime); + for (size_t i = 0; i < size; i++) { + kernel.push_back(kernelArray.getValueAtIndex(runtime, i).asNumber()); + } + auto gain = arguments[3].asNumber(); + auto bias = arguments[4].asNumber(); + SkIPoint kernelOffset = + SkIPoint(arguments[5].asNumber(), arguments[6].asNumber()); + SkTileMode tileMode = (SkTileMode)arguments[7].asNumber(); + bool convolveAlpha = arguments[8].asBool(); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 9)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[9]); + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 10)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[10]); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), + SkImageFilters::MatrixConvolution( + kernelSize, kernel.data(), gain, bias, kernelOffset, + tileMode, convolveAlpha, std::move(input), cropRect))); + } + + JSI_HOST_FUNCTION(MakeMatrixTransform) { + SkMatrix matrix = *JsiSkMatrix::fromValue(runtime, arguments[0]); + SkFilterMode filterMode = SkFilterMode::kNearest; + if (hasOptionalArgument(arguments, count, 1)) { + filterMode = (SkFilterMode)arguments[1].asNumber(); + } + SkMipmapMode mipmap = SkMipmapMode::kNone; + if (hasOptionalArgument(arguments, count, 2)) { + mipmap = (SkMipmapMode)arguments[2].asNumber(); + } + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 3)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[3]); + } + return jsi::Object::createFromHostObject( + runtime, + std::make_shared( + getContext(), SkImageFilters::MatrixTransform( + matrix, SkSamplingOptions(filterMode, mipmap), + std::move(input)))); + } + + JSI_HOST_FUNCTION(MakeMerge) { + std::vector> filters; + auto filtersArray = arguments[0].asObject(runtime).asArray(runtime); + auto filtersCount = filtersArray.size(runtime); + for (size_t i = 0; i < filtersCount; ++i) { + auto element = filtersArray.getValueAtIndex(runtime, i); + if (element.isNull()) { + filters.push_back(nullptr); + } else { + filters.push_back(JsiSkImageFilter::fromValue(runtime, element)); + } + } + SkImageFilters::CropRect cropRect = {}; + if (hasOptionalArgument(arguments, count, 1)) { + cropRect = *JsiSkRect::fromValue(runtime, arguments[1]); + } + return jsi::Object::createFromHostObject( + runtime, + std::make_shared( + getContext(), + SkImageFilters::Merge(filters.data(), filtersCount, cropRect))); + } + + JSI_HOST_FUNCTION(MakePicture) { + sk_sp picture = JsiSkPicture::fromValue(runtime, arguments[0]); + SkRect targetRect; + if (hasOptionalArgument(arguments, count, 1)) { + targetRect = *JsiSkRect::fromValue(runtime, arguments[1]); + } else { + targetRect = picture ? picture->cullRect() : SkRect::MakeEmpty(); + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), + SkImageFilters::Picture(std::move(picture), targetRect))); + } + + JSI_HOST_FUNCTION(MakeRuntimeShaderWithChildren) { + auto rtb = JsiSkRuntimeShaderBuilder::fromValue(runtime, arguments[0]); + float maxSampleRadius = arguments[1].asNumber(); + std::vector childNames; + auto childNamesJS = arguments[2].asObject(runtime).asArray(runtime); + size_t length = childNamesJS.size(runtime); + for (size_t i = 0; i < length; ++i) { + auto element = childNamesJS.getValueAtIndex(runtime, i); + childNames.push_back(element.asString(runtime).utf8(runtime).c_str()); + } + std::vector childNamesStringView; + childNamesStringView.reserve(childNames.size()); + for (const auto &name : childNames) { + childNamesStringView.push_back(std::string_view(name)); + } + + std::vector> inputs; + auto inputsJS = arguments[3].asObject(runtime).asArray(runtime); + if (inputsJS.size(runtime) != length) { + return jsi::Value::null(); + } + for (size_t i = 0; i < length; ++i) { + auto element = inputsJS.getValueAtIndex(runtime, i); + if (element.isNull()) { + inputs.push_back(nullptr); + } else { + inputs.push_back(JsiSkImageFilter::fromValue(runtime, element)); + } + } + return jsi::Object::createFromHostObject( + runtime, std::make_shared( + getContext(), + SkImageFilters::RuntimeShader(*rtb, maxSampleRadius, + childNamesStringView.data(), + inputs.data(), length))); + } + + JSI_HOST_FUNCTION(MakeTile) { + SkRect src = *JsiSkRect::fromValue(runtime, arguments[0]); + SkRect dst = *JsiSkRect::fromValue(runtime, arguments[1]); + sk_sp input = nullptr; + if (hasOptionalArgument(arguments, count, 2)) { + input = JsiSkImageFilter::fromValue(runtime, arguments[2]); + } + return jsi::Object::createFromHostObject( + runtime, + std::make_shared( + getContext(), SkImageFilters::Tile(src, dst, std::move(input)))); + } + JSI_EXPORT_FUNCTIONS( JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeBlur), JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeOffset), @@ -262,7 +651,24 @@ class JsiSkImageFilterFactory : public JsiSkHostObject { JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeBlend), JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeDropShadow), JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeDropShadowOnly), - JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeRuntimeShader)) + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeRuntimeShader), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeArithmetic), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeCrop), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeEmpty), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeImage), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeMagnifier), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeMatrixConvolution), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeMatrixTransform), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeMerge), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakePicture), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeRuntimeShaderWithChildren), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeTile), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeDistantLitDiffuse), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakePointLitDiffuse), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeSpotLitDiffuse), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeDistantLitSpecular), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakePointLitSpecular), + JSI_EXPORT_FUNC(JsiSkImageFilterFactory, MakeSpotLitSpecular)) explicit JsiSkImageFilterFactory(std::shared_ptr context) : JsiSkHostObject(std::move(context)) {} diff --git a/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts b/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts index c9025c5836..8cee3b0752 100644 --- a/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts +++ b/packages/skia/src/skia/types/ImageFilter/ImageFilterFactory.ts @@ -1,6 +1,9 @@ import type { SkColor } from "../Color"; import type { SkColorFilter } from "../ColorFilter/ColorFilter"; +import type { FilterMode, MipmapMode, SkImage } from "../Image/Image"; +import type { SkMatrix } from "../Matrix"; import type { BlendMode } from "../Paint"; +import type { SkPicture } from "../Picture"; import type { SkRect } from "../Rect"; import type { SkRuntimeShaderBuilder } from "../RuntimeEffect"; import type { SkShader } from "../Shader"; @@ -79,6 +82,7 @@ export interface ImageFilterFactory { /** * Create a filter that applies the color filter to the input filter results. + * * @param colorFilter - The color filter to apply * @param input - if null, it will use the dynamic source image (e.g. a saved layer) * @param cropRect - Optional rectangle that crops the input and output @@ -202,4 +206,357 @@ export interface ImageFilterFactory { childShaderName: string | null, input?: SkImageFilter | null ) => SkImageFilter; + + /** + * Create a filter that implements a custom blend mode. Each output pixel is the result of + * combining the corresponding background and foreground pixels using the 4 coefficients: + * k1 * foreground * background + k2 * foreground + k3 * background + k4 + * + * @param k1, k2, k3, k4 The four coefficients used to combine the foreground and background. + * @param enforcePMColor If true, the RGB channels will be clamped to the calculated alpha. + * @param background The background content, using the source bitmap when this is null. + * @param foreground The foreground content, using the source bitmap when this is null. + * @param cropRect Optional rectangle that crops the inputs and output. + */ + MakeArithmetic( + k1: number, + k2: number, + k3: number, + k4: number, + enforcePMColor: boolean, + background?: SkImageFilter | null, + foreground?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that applies a crop to the result of the 'input' filter. Pixels within the + * crop rectangle are unmodified from what 'input' produced. Pixels outside of crop match the + * provided SkTileMode (defaulting to kDecal). + * + * NOTE: The optional CropRect argument for many of the factories is equivalent to creating the + * filter without a CropRect and then wrapping it in ::Crop(rect, kDecal). Explicitly adding + * Crop filters lets you control their tiling and use different geometry for the input and the + * output of another filter. + * + * @param rect The cropping rect + * @param tileMode The TileMode applied to pixels *outside* of 'crop' @default TileMode.Decal + * @param input The input filter that is cropped, uses source image if this is null + */ + MakeCrop( + rect: SkRect, + tileMode?: TileMode | null, + input?: SkImageFilter | null + ): SkImageFilter; + + /** + * Create a filter that always produces transparent black. + */ + MakeEmpty(): SkImageFilter; + + /** + * Create a filter that draws the 'srcRect' portion of image into 'dstRect' using the given + * filter quality. Similar to SkCanvas::drawImageRect. The returned image filter evaluates + * to transparent black if 'image' is null. + * + * @param image The image that is output by the filter, subset by 'srcRect'. + * @param srcRect The source pixels sampled into 'dstRect', if null the image bounds are used. + * @param dstRect The local rectangle to draw the image into, if null the srcRect is used. + * @param filterMode The filter mode to use when sampling the image @default FilterMode.Nearest + * @param mipmap The mipmap mode to use when sampling the image @default MipmapMode.None + */ + MakeImage( + image: SkImage, + srcRect?: SkRect | null, + dstRect?: SkRect | null, + filterMode?: FilterMode, + mipmap?: MipmapMode + ): SkImageFilter; + + /** + * Create a filter that fills 'lensBounds' with a magnification of the input. + * + * @param lensBounds The outer bounds of the magnifier effect + * @param zoomAmount The amount of magnification applied to the input image + * @param inset The size or width of the fish-eye distortion around the magnified content + * @param filterMode The filter mode to use when sampling the image @default FilterMode.Nearest + * @param mipmap The mipmap mode to use when sampling the image @default MipmapMode.None + * @param input The input filter that is magnified; if null the source bitmap is used + * @param cropRect Optional rectangle that crops the input and output. + */ + MakeMagnifier( + lensBounds: SkRect, + zoomAmount: number, + inset: number, + filterMode?: FilterMode, + mipmap?: MipmapMode, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that applies an NxM image processing kernel to the input image. This can be + * used to produce effects such as sharpening, blurring, edge detection, etc. + * @param kernelSizeX The width of the kernel. Must be greater than zero. + * @param kernelSizeY The height of the kernel. Must be greater than zero. + * @param kernel The image processing kernel. Must contain kernelSizeX * kernelSizeY elements, in row order. + * @param gain A scale factor applied to each pixel after convolution. This can be + * used to normalize the kernel, if it does not already sum to 1. + * @param bias A bias factor added to each pixel after convolution. + * @param kernelOffsetX An offset applied to each pixel coordinate before convolution. + * This can be used to center the kernel over the image + * (e.g., a 3x3 kernel should have an offset of {1, 1}). + * @param kernelOffsetY An offset applied to each pixel coordinate before convolution. + * This can be used to center the kernel over the image + * (e.g., a 3x3 kernel should have an offset of {1, 1}). + * @param tileMode How accesses outside the image are treated. TileMode.Mirror is not supported. + * @param convolveAlpha If true, all channels are convolved. If false, only the RGB channels + * are convolved, and alpha is copied from the source image. + * @param input The input image filter, if null the source bitmap is used instead. + * @param cropRect Optional rectangle to which the output processing will be limited. + */ + MakeMatrixConvolution( + kernelSizeX: number, + kernelSizeY: number, + kernel: number[], + gain: number, + bias: number, + kernelOffsetX: number, + kernelOffsetY: number, + tileMode: TileMode, + convolveAlpha: boolean, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that transforms the input image by 'matrix'. This matrix transforms the + * local space, which means it effectively happens prior to any transformation coming from the + * SkCanvas initiating the filtering. + * @param matrix The matrix to apply to the original content. + * @param filterMode The filter mode to use when sampling the image @default FilterMode.Nearest + * @param mipmap The mipmap mode to use when sampling the image @default MipmapMode.None + * @param input The image filter to transform, or null to use the source image. + */ + MakeMatrixTransform( + matrix: SkMatrix, + filterMode?: FilterMode, + mipmap?: MipmapMode, + input?: SkImageFilter | null + ): SkImageFilter; + + /** + * Create a filter that merges filters together by drawing their results in order + * with src-over blending. + * @param filters The input filter array to merge. Any null + * filter pointers will use the source bitmap instead. + * @param cropRect Optional rectangle that crops all input filters and the output. + */ + MakeMerge( + filters: Array, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that produces the SkPicture as its output, clipped to both 'targetRect' and + * the picture's internal cull rect. + * + * If 'pic' is null, the returned image filter produces transparent black. + * + * @param picture The picture that is drawn for the filter output. + * @param targetRect The drawing region for the picture. If null, the picture's bounds are used. + */ + MakePicture(picture: SkPicture, targetRect?: SkRect | null): SkImageFilter; + + /** + * Create a filter that fills the output with the per-pixel evaluation of the SkShader produced + * by the SkRuntimeEffectBuilder. The shader is defined in the image filter's local coordinate + * system, so it will automatically be affected by SkCanvas' transform. + * + * This requires a GPU backend or SkSL to be compiled in. + * + * @param builder The builder used to produce the runtime shader, that will in turn + * fill the result image + * @param sampleRadius defines the sampling radius of 'childShaderName' relative to + * the runtime shader produced by 'builder'. + * If greater than 0, the coordinate passed to childShader.eval() will + * be up to 'sampleRadius' away (maximum absolute offset in 'x' or 'y') + * from the coordinate passed into the runtime shader. + * @param childShaderNames The names of the child shaders defined in the builder that will be + * bound to the input params (or the source image if the input param + * is null). If any name is null, or appears more than once, factory + * fails and returns nullptr. + * @param inputs The image filters that will be provided as input to the runtime + * shader. If any are null, the implicit source image is used instead. + */ + MakeRuntimeShaderWithChildren: ( + builder: SkRuntimeShaderBuilder, + sampleRadius: number, + childShaderNames: string[], + inputs: Array + ) => SkImageFilter; + + /** + * Create a tile image filter. + * @param src Defines the pixels to tile + * @param dst Defines the pixel region that the tiles will be drawn to + * @param input The input that will be tiled, if null the source bitmap is used instead. + */ + MakeTile( + src: SkRect, + dst: SkRect, + input?: SkImageFilter | null + ): SkImageFilter; + + /** + * Create a filter that calculates the diffuse illumination from a distant light source, + * interpreting the alpha channel of the input as the height profile of the surface (to + * approximate normal vectors). + * @param direction The direction to the distance light. + * @param lightColor The color of the diffuse light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param kd Diffuse reflectance coefficient. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + MakeDistantLitDiffuse( + direction: SkPoint3, + lightColor: SkColor, + surfaceScale: number, + kd: number, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that calculates the diffuse illumination from a point light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). + * @param location The location of the point light. + * @param lightColor The color of the diffuse light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param kd Diffuse reflectance coefficient. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + MakePointLitDiffuse( + location: SkPoint3, + lightColor: SkColor, + surfaceScale: number, + kd: number, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that calculates the diffuse illumination from a spot light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). The spot light is restricted to be within 'cutoffAngle' of the vector between + * the location and target. + * @param location The location of the spot light. + * @param target The location that the spot light is point towards + * @param falloffExponent Exponential falloff parameter for illumination outside of cutoffAngle + * @param cutoffAngle Maximum angle from lighting direction that receives full light + * @param lightColor The color of the diffuse light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param kd Diffuse reflectance coefficient. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + MakeSpotLitDiffuse( + location: SkPoint3, + target: SkPoint3, + falloffExponent: number, + cutoffAngle: number, + lightColor: SkColor, + surfaceScale: number, + kd: number, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that calculates the specular illumination from a distant light source, + * interpreting the alpha channel of the input as the height profile of the surface (to + * approximate normal vectors). + * @param direction The direction to the distance light. + * @param lightColor The color of the specular light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param ks Specular reflectance coefficient. + * @param shininess The specular exponent determining how shiny the surface is. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + MakeDistantLitSpecular( + direction: SkPoint3, + lightColor: SkColor, + surfaceScale: number, + ks: number, + shininess: number, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that calculates the specular illumination from a point light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). + * @param location The location of the point light. + * @param lightColor The color of the specular light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param ks Specular reflectance coefficient. + * @param shininess The specular exponent determining how shiny the surface is. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + MakePointLitSpecular( + location: SkPoint3, + lightColor: SkColor, + surfaceScale: number, + ks: number, + shininess: number, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; + + /** + * Create a filter that calculates the specular illumination from a spot light source, using + * alpha channel of the input as the height profile of the surface (to approximate normal + * vectors). The spot light is restricted to be within 'cutoffAngle' of the vector between + * the location and target. + * @param location The location of the spot light. + * @param target The location that the spot light is point towards + * @param falloffExponent Exponential falloff parameter for illumination outside of cutoffAngle + * @param cutoffAngle Maximum angle from lighting direction that receives full light + * @param lightColor The color of the specular light source. + * @param surfaceScale Scale factor to transform from alpha values to physical height. + * @param ks Specular reflectance coefficient. + * @param shininess The specular exponent determining how shiny the surface is. + * @param input The input filter that defines surface normals (as alpha), or uses the + * source bitmap when null. + * @param cropRect Optional rectangle that crops the input and output. + */ + MakeSpotLitSpecular( + location: SkPoint3, + target: SkPoint3, + falloffExponent: number, + cutoffAngle: number, + lightColor: SkColor, + surfaceScale: number, + ks: number, + shininess: number, + input?: SkImageFilter | null, + cropRect?: SkRect | null + ): SkImageFilter; } + +export type SkPoint3 = { + x: number; + y: number; + z: number; +}; diff --git a/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts b/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts index 3c62551488..71ec3484a1 100644 --- a/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts +++ b/packages/skia/src/skia/web/JsiSkImageFilterFactory.ts @@ -11,6 +11,12 @@ import type { SkRuntimeShaderBuilder, SkShader, TileMode, + FilterMode, + MipmapMode, + SkImage, + SkMatrix, + SkPicture, + SkPoint3, } from "../types"; import { Host, throwNotImplementedOnRNWeb, getEnum } from "./Host"; @@ -24,6 +30,164 @@ export class JsiSkImageFilterFactory constructor(CanvasKit: CanvasKit) { super(CanvasKit); } + MakeRuntimeShaderWithChildren( + _builder: SkRuntimeShaderBuilder, + _sampleRadius: number, + _childShaderNames: string[], + _inputs: Array + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeArithmetic( + _k1: number, + _k2: number, + _k3: number, + _k4: number, + _enforcePMColor: boolean, + _background?: SkImageFilter | null, + _foreground?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeCrop( + _rect: SkRect, + _tileMode?: TileMode | null, + _input?: SkImageFilter | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeEmpty(): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeImage( + _image: SkImage, + _srcRect?: SkRect | null, + _dstRect?: SkRect | null, + _filterMode?: FilterMode, + _mipmap?: MipmapMode + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeMagnifier( + _lensBounds: SkRect, + _zoomAmount: number, + _inset: number, + _filterMode?: FilterMode, + _mipmap?: MipmapMode, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeMatrixConvolution( + _kernelSizeX: number, + _kernelSizeY: number, + _kernel: number[], + _gain: number, + _bias: number, + _kernelOffsetX: number, + _kernelOffsetY: number, + _tileMode: TileMode, + _convolveAlpha: boolean, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeMatrixTransform( + _matrix: SkMatrix, + _filterMode?: FilterMode, + _mipmap?: MipmapMode, + _input?: SkImageFilter | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeMerge( + _filters: Array, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakePicture(_picture: SkPicture, _targetRect?: SkRect | null): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeTile( + _src: SkRect, + _dst: SkRect, + _input?: SkImageFilter | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeDistantLitDiffuse( + _direction: SkPoint3, + _lightColor: SkColor, + _surfaceScale: number, + _kd: number, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakePointLitDiffuse( + _location: SkPoint3, + _lightColor: SkColor, + _surfaceScale: number, + _kd: number, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeSpotLitDiffuse( + _location: SkPoint3, + _target: SkPoint3, + _falloffExponent: number, + _cutoffAngle: number, + _lightColor: SkColor, + _surfaceScale: number, + _kd: number, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeDistantLitSpecular( + _direction: SkPoint3, + _lightColor: SkColor, + _surfaceScale: number, + _ks: number, + _shininess: number, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakePointLitSpecular( + _location: SkPoint3, + _lightColor: SkColor, + _surfaceScale: number, + _ks: number, + _shininess: number, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } + MakeSpotLitSpecular( + _location: SkPoint3, + _target: SkPoint3, + _falloffExponent: number, + _cutoffAngle: number, + _lightColor: SkColor, + _surfaceScale: number, + _ks: number, + _shininess: number, + _input?: SkImageFilter | null, + _cropRect?: SkRect | null + ): SkImageFilter { + throw throwNotImplementedOnRNWeb(); + } MakeOffset( dx: number, From 23c1f2001f5ebfaeef78aa5d1bce0dda57ff5d55 Mon Sep 17 00:00:00 2001 From: William Candillon Date: Sun, 18 May 2025 21:02:47 +0200 Subject: [PATCH 03/10] :wrench: --- .../img/advanced-image-filters/arthimetic.png | Bin 0 -> 227408 bytes apps/example/ios/Podfile.lock | 47 ++++++++++++++++++ .../e2e/AdvancedImageFilters.spec.tsx | 47 ++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 apps/docs/static/img/advanced-image-filters/arthimetic.png create mode 100644 packages/skia/src/renderer/__tests__/e2e/AdvancedImageFilters.spec.tsx diff --git a/apps/docs/static/img/advanced-image-filters/arthimetic.png b/apps/docs/static/img/advanced-image-filters/arthimetic.png new file mode 100644 index 0000000000000000000000000000000000000000..acb21a0ca6a764fb10d32c3db96dae35179c4609 GIT binary patch literal 227408 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