diff --git a/apps/docs/docs/image.md b/apps/docs/docs/image.md
index 0239cc4b62..c4d34ce8ae 100644
--- a/apps/docs/docs/image.md
+++ b/apps/docs/docs/image.md
@@ -85,6 +85,7 @@ Images can be drawn by specifying the output rectangle and how the image should
| width | `number` | The width of the destination image. |
| height | `number` | The height of the destination image. |
| fit? | `Fit` | The method used to fit the image into the rectangle. Values can be `contain`, `fill`, `cover`, `fitHeight`, `fitWidth`, `scaleDown`, or `none` (the default is `contain`). |
+| sampling? | `Sampling` | The method used to sample the image. see ([sampling options](/docs/images#sampling-options)). |
### Example
@@ -101,6 +102,43 @@ const ImageDemo = () => {
};
```
+### Sampling Options
+
+The `sampling` prop allows you to control how the image is sampled when it is drawn.
+Use cubic sampling for best quality: you can use the default `sampling={CubicSampling}` (defaults to `{ B: 0, C: 0 }`) or any value you would like: `sampling={{ B: 0, C: 0.5 }}`.
+
+You can also use filter modes (`nearest` or `linear`) and mimap modes (`none`, `nearest`, or `linear`). Default is `nearest`.
+
+```tsx twoslash
+import { Canvas, Image, useImage, CubicSampling, FilterMode, MipmapMode } from "@shopify/react-native-skia";
+
+const ImageDemo = () => {
+ const image = useImage(require("./assets/oslo.jpg"));
+ return (
+
+ );
+};
+```
+
### fit="contain"
![fit="contain"](assets/images/contain.png)
diff --git a/apps/docs/docs/shaders/images.md b/apps/docs/docs/shaders/images.md
index e348429834..9ee952708a 100644
--- a/apps/docs/docs/shaders/images.md
+++ b/apps/docs/docs/shaders/images.md
@@ -15,11 +15,10 @@ It will use cubic sampling.
| image | `SkImage` | Image instance. |
| tx? | `TileMode` | Can be `clamp`, `repeat`, `mirror`, or `decal`. |
| ty? | `TileMode` | Can be `clamp`, `repeat`, `mirror`, or `decal`. |
-| fm? | `FilterMode` | Can be `linear` or `nearest`. |
-| mm? | `MipmapMode` | Can be `none`, `linear` or `nearest`. |
| fit? | `Fit` | Calculate the transformation matrix to fit the rectangle defined by `fitRect`. See [images](/docs/images). |
| rect? | `SkRect` | The destination rectangle to calculate the transformation matrix via the `fit` property. |
| transform? | `Transforms2d` | see [transformations](/docs/group#transformations). |
+| sampling? | `Sampling` | The method used to sample the image. see ([sampling options](/docs/images#sampling-options)). |
### Example
```tsx twoslash
diff --git a/apps/docs/docs/shapes/atlas.md b/apps/docs/docs/shapes/atlas.md
index 7270fa5b52..39f052578c 100644
--- a/apps/docs/docs/shapes/atlas.md
+++ b/apps/docs/docs/shapes/atlas.md
@@ -16,6 +16,7 @@ Its design particularly useful when using with [Reanimated](#animations).
| transforms | `RSXform[]` | Rotation/scale transforms to be applied for each sprite. |
| colors? | `SkColor[]` | Optional. Color to blend the sprites with. |
| blendMode? | `BlendMode` | Optional. Blend mode used to combine sprites and colors together. |
+| sampling? | `Sampling` | The method used to sample the image. see ([sampling options](/docs/images#sampling-options)). |
## RSXform
diff --git a/apps/docs/static/img/atlas/hello-world.png b/apps/docs/static/img/atlas/hello-world.png
index 7f9b4eed66..615f779a43 100644
Binary files a/apps/docs/static/img/atlas/hello-world.png and b/apps/docs/static/img/atlas/hello-world.png differ
diff --git a/apps/docs/static/img/mask/blend-mode-mask.png b/apps/docs/static/img/mask/blend-mode-mask.png
index 47cd28a665..2de47ce965 100644
Binary files a/apps/docs/static/img/mask/blend-mode-mask.png and b/apps/docs/static/img/mask/blend-mode-mask.png differ
diff --git a/apps/paper/ios/Podfile.lock b/apps/paper/ios/Podfile.lock
index 8c192df5e4..75dbdfd1bd 100644
--- a/apps/paper/ios/Podfile.lock
+++ b/apps/paper/ios/Podfile.lock
@@ -1694,6 +1694,8 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - RNSVG (15.9.0):
+ - React-Core
- SocketRocket (0.7.0)
- Yoga (0.0.0)
@@ -1768,6 +1770,7 @@ DEPENDENCIES:
- RNGestureHandler (from `../../../node_modules/react-native-gesture-handler`)
- RNReanimated (from `../../../node_modules/react-native-reanimated`)
- RNScreens (from `../../../node_modules/react-native-screens`)
+ - RNSVG (from `../node_modules/react-native-svg`)
- Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@@ -1912,6 +1915,8 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-reanimated"
RNScreens:
:path: "../../../node_modules/react-native-screens"
+ RNSVG:
+ :path: "../node_modules/react-native-svg"
Yoga:
:path: "../../../node_modules/react-native/ReactCommon/yoga"
@@ -1952,7 +1957,7 @@ SPEC CHECKSUMS:
React-Mapbuffer: 1c08607305558666fd16678b85ef135e455d5c96
React-microtasksnativemodule: 87b8de96f937faefece8afd2cb3a518321b2ef99
react-native-safe-area-context: ab8f4a3d8180913bd78ae75dd599c94cce3d5e9a
- react-native-skia: b9a7bb46f52a1b9a06b89d477ba6a8c1fe96a69d
+ react-native-skia: abdd2ba5abed61445a25d9461b0a87bc08c50f64
react-native-slider: 97ce0bd921f40de79cead9754546d5e4e7ba44f8
react-native-wgpu: 8d0437a304318e0e3d6ccbfed2a39880f8eae4dd
React-nativeconfig: 57781b79e11d5af7573e6f77cbf1143b71802a6d
@@ -1984,6 +1989,7 @@ SPEC CHECKSUMS:
RNGestureHandler: 939f21fabf5d45a725c0bf175eb819dd25cf2e70
RNReanimated: 9d20a811e6987cba268ef4f56242dfabd40e85c1
RNScreens: b03d696c70cc5235ce4587fcc27ae1a93a48f98c
+ RNSVG: 3d2bdcaef51c8071880a9c0072fe324f4423a3ba
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae
diff --git a/apps/remotion/package.json b/apps/remotion/package.json
index 47c243b2cc..edb13f7fd2 100644
--- a/apps/remotion/package.json
+++ b/apps/remotion/package.json
@@ -4,7 +4,7 @@
"description": "My Remotion video",
"scripts": {
"start": "remotion preview src/index.tsx",
- "build": "remotion render src/index.tsx Playground current.mov --gl=angle --pixel-format=yuva444p10le --codec=prores --prores-profile=4444",
+ "build-remotion": "remotion render src/index.tsx Playground current.mov --gl=angle --pixel-format=yuva444p10le --codec=prores --prores-profile=4444",
"upgrade": "remotion upgrade",
"build:sequence": "remotion render src/index.tsx Playground stills --sequence --gl=angle",
"lint": "eslint . --ext .ts,.tsx --max-warnings 0 --cache",
diff --git a/packages/skia/cpp/api/JsiSkCanvas.h b/packages/skia/cpp/api/JsiSkCanvas.h
index 38b70bdd22..8395e5edd6 100644
--- a/packages/skia/cpp/api/JsiSkCanvas.h
+++ b/packages/skia/cpp/api/JsiSkCanvas.h
@@ -546,8 +546,10 @@ class JsiSkCanvas : public JsiSkHostObject {
colors.push_back(color);
}
}
-
- SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+ SkSamplingOptions sampling(SkFilterMode::kLinear);
+ if (count > 6) {
+ sampling = SamplingOptionsFromValue(runtime, arguments[5]);
+ }
_canvas->drawAtlas(atlas.get(), xforms.data(), skRects.data(),
colors.data(), skRects.size(), blendMode, sampling,
nullptr, paint.get());
diff --git a/packages/skia/cpp/api/JsiSkImage.h b/packages/skia/cpp/api/JsiSkImage.h
index 23061e6417..8c949b3b22 100644
--- a/packages/skia/cpp/api/JsiSkImage.h
+++ b/packages/skia/cpp/api/JsiSkImage.h
@@ -36,6 +36,30 @@ namespace RNSkia {
namespace jsi = facebook::jsi;
+inline SkSamplingOptions SamplingOptionsFromValue(jsi::Runtime &runtime,
+ const jsi::Value &val) {
+ SkSamplingOptions samplingOptions(SkFilterMode::kLinear);
+ if (val.isObject()) {
+ auto object = val.asObject(runtime);
+ if (object.hasProperty(runtime, "B") && object.hasProperty(runtime, "C")) {
+ auto B = static_cast(object.getProperty(runtime, "B").asNumber());
+ auto C = static_cast(object.getProperty(runtime, "C").asNumber());
+ samplingOptions = SkSamplingOptions({B, C});
+ } else if (object.hasProperty(runtime, "filter")) {
+ auto filter = static_cast(
+ object.getProperty(runtime, "filter").asNumber());
+ if (object.hasProperty(runtime, "mipmap")) {
+ auto mipmap = static_cast(
+ object.getProperty(runtime, "mipmap").asNumber());
+ samplingOptions = SkSamplingOptions(filter, mipmap);
+ } else {
+ samplingOptions = SkSamplingOptions(filter);
+ }
+ }
+ }
+ return samplingOptions;
+}
+
class JsiSkImage : public JsiSkWrappingSkPtrHostObject {
public:
// TODO-API: Properties?
diff --git a/packages/skia/cpp/rnskia/dom/nodes/JsiAtlasNode.h b/packages/skia/cpp/rnskia/dom/nodes/JsiAtlasNode.h
index f57ac047fb..f0abef0a3c 100644
--- a/packages/skia/cpp/rnskia/dom/nodes/JsiAtlasNode.h
+++ b/packages/skia/cpp/rnskia/dom/nodes/JsiAtlasNode.h
@@ -3,6 +3,7 @@
#include "JsiDomDrawingNode.h"
#include "RSXformProp.h"
#include "RectProp.h"
+#include "SamplingProp.h"
#include "SkImageProps.h"
#include
@@ -28,7 +29,8 @@ class JsiAtlasNode : public JsiDomDrawingNode,
? *_blendModeProp->getDerivedValue()
: SkBlendMode::kDstOver;
auto paint = *context->getPaint();
- SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+ auto sampling = _samplingProp->isSet() ? *_samplingProp->getDerivedValue()
+ : SkSamplingOptions(SkFilterMode::kLinear);
context->getCanvas()->drawAtlas(
image.get(), transforms->data(), sprites->data(),
colors == nullptr ? nullptr : colors->data(), sprites->size(),
@@ -43,6 +45,7 @@ class JsiAtlasNode : public JsiDomDrawingNode,
_imageProp = container->defineProperty("image");
_colorsProp = container->defineProperty("colors");
_blendModeProp = container->defineProperty("blendMode");
+ _samplingProp = container->defineProperty("sampling");
_rectsProp->require();
_rsxFormsProp->require();
@@ -54,6 +57,7 @@ class JsiAtlasNode : public JsiDomDrawingNode,
RSXFormsProp *_rsxFormsProp;
ColorsProp *_colorsProp;
BlendModeProp *_blendModeProp;
+ SamplingProp *_samplingProp;
};
} // namespace RNSkia
diff --git a/packages/skia/cpp/rnskia/dom/nodes/JsiImageNode.h b/packages/skia/cpp/rnskia/dom/nodes/JsiImageNode.h
index bcdcf93b23..21268683ab 100644
--- a/packages/skia/cpp/rnskia/dom/nodes/JsiImageNode.h
+++ b/packages/skia/cpp/rnskia/dom/nodes/JsiImageNode.h
@@ -1,6 +1,7 @@
#pragma once
#include "JsiDomDrawingNode.h"
+#include "SamplingProp.h"
#include "SkImageProps.h"
#include
@@ -20,19 +21,22 @@ class JsiImageNode : public JsiDomDrawingNode,
if (image == nullptr) {
return;
}
-
- context->getCanvas()->drawImageRect(
- image, rects->src, rects->dst, SkSamplingOptions(),
- context->getPaint().get(), SkCanvas::kStrict_SrcRectConstraint);
+ auto sampling = _samplingProp->isSet() ? *_samplingProp->getDerivedValue()
+ : SkSamplingOptions(SkFilterMode::kLinear);
+ context->getCanvas()->drawImageRect(image, rects->src, rects->dst, sampling,
+ context->getPaint().get(),
+ SkCanvas::kStrict_SrcRectConstraint);
}
void defineProperties(NodePropsContainer *container) override {
JsiDomDrawingNode::defineProperties(container);
_imageProps = container->defineProperty();
+ _samplingProp = container->defineProperty("sampling");
}
private:
ImageProps *_imageProps;
+ SamplingProp *_samplingProp;
};
} // namespace RNSkia
diff --git a/packages/skia/cpp/rnskia/dom/nodes/JsiShaderNodes.h b/packages/skia/cpp/rnskia/dom/nodes/JsiShaderNodes.h
index 39f16f52d8..c18621d7ff 100644
--- a/packages/skia/cpp/rnskia/dom/nodes/JsiShaderNodes.h
+++ b/packages/skia/cpp/rnskia/dom/nodes/JsiShaderNodes.h
@@ -6,6 +6,7 @@
#include "ColorProp.h"
#include "NodeProp.h"
#include "NumbersProp.h"
+#include "SamplingProp.h"
#include "TileModeProp.h"
#include "TransformsProps.h"
#include "UniformsProp.h"
@@ -120,12 +121,12 @@ class JsiImageShaderNode : public JsiDomDeclarationNode,
}
}
- context->getShaders()->push(image->makeShader(
- *_txProp->getDerivedValue(), *_tyProp->getDerivedValue(),
- SkSamplingOptions(
- getFilterModeFromString(_filterModeProp->value().getAsString()),
- getMipmapModeFromString(_mipmapModeProp->value().getAsString())),
- &_matrix));
+ auto samplingOptions = _samplingProp->isSet()
+ ? *_samplingProp->getDerivedValue()
+ : SkSamplingOptions(SkFilterMode::kLinear);
+ context->getShaders()->push(image->makeShader(*_txProp->getDerivedValue(),
+ *_tyProp->getDerivedValue(),
+ samplingOptions, &_matrix));
}
protected:
@@ -133,17 +134,14 @@ class JsiImageShaderNode : public JsiDomDeclarationNode,
JsiDomDeclarationNode::defineProperties(container);
_txProp = container->defineProperty("tx");
_tyProp = container->defineProperty("ty");
- _filterModeProp = container->defineProperty("fm");
- _mipmapModeProp = container->defineProperty("mm");
+ _samplingProp = container->defineProperty("sampling");
_imageProps = container->defineProperty();
_transformProp = container->defineProperty("transform");
_originProp = container->defineProperty("origin");
_txProp->require();
_tyProp->require();
- _filterModeProp->require();
- _mipmapModeProp->require();
_transformProp->require();
@@ -152,41 +150,14 @@ class JsiImageShaderNode : public JsiDomDeclarationNode,
}
private:
- SkFilterMode getFilterModeFromString(const std::string &value) {
- if (value == "last") {
- return SkFilterMode::kLast;
- } else if (value == "linear") {
- return SkFilterMode::kLinear;
- } else if (value == "nearest") {
- return SkFilterMode::kNearest;
- }
- throw std::runtime_error("The value \"" + value +
- "\" is not a valid Filter Mode.");
- }
-
- SkMipmapMode getMipmapModeFromString(const std::string &value) {
- if (value == "last") {
- return SkMipmapMode::kLast;
- } else if (value == "last") {
- return SkMipmapMode::kLast;
- } else if (value == "last") {
- return SkMipmapMode::kLast;
- } else if (value == "none") {
- return SkMipmapMode::kNone;
- }
- throw std::runtime_error("The value \"" + value +
- "\" is not a valid Mipmap Mode.");
- }
-
SkMatrix _matrix;
TileModeProp *_txProp;
TileModeProp *_tyProp;
- NodeProp *_filterModeProp;
- NodeProp *_mipmapModeProp;
ImageProps *_imageProps;
TransformProp *_transformProp;
PointProp *_originProp;
+ SamplingProp *_samplingProp;
};
class JsiColorShaderNode : public JsiDomDeclarationNode,
diff --git a/packages/skia/cpp/rnskia/dom/props/SamplingProp.h b/packages/skia/cpp/rnskia/dom/props/SamplingProp.h
new file mode 100644
index 0000000000..4d5e5ed050
--- /dev/null
+++ b/packages/skia/cpp/rnskia/dom/props/SamplingProp.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "include/core/SkSamplingOptions.h"
+
+namespace RNSkia {
+
+static PropId PropNameCubicB = JsiPropId::get("B");
+static PropId PropNameCubicC = JsiPropId::get("C");
+static PropId PropNameFilter = JsiPropId::get("filter");
+static PropId PropNameMipmap = JsiPropId::get("mipmap");
+
+class SamplingProp : public DerivedProp {
+public:
+ explicit SamplingProp(PropId name,
+ const std::function &onChange)
+ : DerivedProp(onChange) {
+ _prop = defineProperty(name);
+ }
+
+ static SkSamplingOptions processSamplingOptions(const JsiValue &value) {
+ SkSamplingOptions samplingOptions(SkFilterMode::kLinear);
+ if (value.getType() == PropType::Object) {
+ if (value.hasValue(PropNameCubicB) && value.hasValue(PropNameCubicC)) {
+ auto B =
+ static_cast(value.getValue(PropNameCubicB).getAsNumber());
+ auto C =
+ static_cast(value.getValue(PropNameCubicC).getAsNumber());
+ samplingOptions = SkSamplingOptions({B, C});
+ } else if (value.hasValue(PropNameFilter)) {
+ auto filter = static_cast(
+ value.getValue(PropNameFilter).getAsNumber());
+ if (value.hasValue(PropNameMipmap)) {
+ auto mipmap = static_cast(
+ value.getValue(PropNameMipmap).getAsNumber());
+ samplingOptions = SkSamplingOptions(filter, mipmap);
+ } else {
+ samplingOptions = SkSamplingOptions(filter);
+ }
+ }
+ }
+
+ return samplingOptions;
+ }
+
+ void updateDerivedValue() override {
+ if (_prop->isSet()) {
+ setDerivedValue(SamplingProp::processSamplingOptions(_prop->value()));
+ }
+ }
+
+private:
+ NodeProp *_prop;
+};
+} // namespace RNSkia
diff --git a/packages/skia/src/__tests__/snapshots/animated-images/bird.png b/packages/skia/src/__tests__/snapshots/animated-images/bird.png
index 47b2f06994..b29f7ca30f 100644
Binary files a/packages/skia/src/__tests__/snapshots/animated-images/bird.png and b/packages/skia/src/__tests__/snapshots/animated-images/bird.png differ
diff --git a/packages/skia/src/__tests__/snapshots/demos/product.png b/packages/skia/src/__tests__/snapshots/demos/product.png
index ad507d62b0..528b8fc309 100644
Binary files a/packages/skia/src/__tests__/snapshots/demos/product.png and b/packages/skia/src/__tests__/snapshots/demos/product.png differ
diff --git a/packages/skia/src/__tests__/snapshots/demos/product2.png b/packages/skia/src/__tests__/snapshots/demos/product2.png
index 26345b9318..5f40bca177 100644
Binary files a/packages/skia/src/__tests__/snapshots/demos/product2.png and b/packages/skia/src/__tests__/snapshots/demos/product2.png differ
diff --git a/packages/skia/src/__tests__/snapshots/images/bundle-android.png b/packages/skia/src/__tests__/snapshots/images/bundle-android.png
index de558fd7c7..dd152a4e5c 100644
Binary files a/packages/skia/src/__tests__/snapshots/images/bundle-android.png and b/packages/skia/src/__tests__/snapshots/images/bundle-android.png differ
diff --git a/packages/skia/src/__tests__/snapshots/images/bundle-ios.png b/packages/skia/src/__tests__/snapshots/images/bundle-ios.png
index 2f5603cf78..6968b4c78e 100644
Binary files a/packages/skia/src/__tests__/snapshots/images/bundle-ios.png and b/packages/skia/src/__tests__/snapshots/images/bundle-ios.png differ
diff --git a/packages/skia/src/__tests__/snapshots/images/bundle-node.png b/packages/skia/src/__tests__/snapshots/images/bundle-node.png
index 649f04fa2a..34cd2a7e8b 100644
Binary files a/packages/skia/src/__tests__/snapshots/images/bundle-node.png and b/packages/skia/src/__tests__/snapshots/images/bundle-node.png differ
diff --git a/packages/skia/src/__tests__/snapshots/images/filter.png b/packages/skia/src/__tests__/snapshots/images/filter.png
index c1c81fb573..9022990275 100644
Binary files a/packages/skia/src/__tests__/snapshots/images/filter.png and b/packages/skia/src/__tests__/snapshots/images/filter.png differ
diff --git a/packages/skia/src/dom/types/Drawings.ts b/packages/skia/src/dom/types/Drawings.ts
index d53a88b0ed..8dcfb9fc63 100644
--- a/packages/skia/src/dom/types/Drawings.ts
+++ b/packages/skia/src/dom/types/Drawings.ts
@@ -17,6 +17,7 @@ import type {
SkRect,
SkRSXform,
SkColor,
+ SamplingOptions,
} from "../../skia/types";
import type {
@@ -37,6 +38,7 @@ export type ImageProps = DrawingNodeProps &
RectDef & {
fit?: Fit;
image: SkImage | null;
+ sampling?: SamplingOptions;
};
export type CircleProps = CircleDef & DrawingNodeProps;
@@ -65,6 +67,7 @@ export interface AtlasProps extends DrawingNodeProps {
sprites: SkRect[];
transforms: SkRSXform[];
colors?: SkColor[];
+ sampling?: SamplingOptions;
}
export interface CubicBezierHandle {
diff --git a/packages/skia/src/dom/types/Shaders.ts b/packages/skia/src/dom/types/Shaders.ts
index 68c4d4c5f4..821158ae0e 100644
--- a/packages/skia/src/dom/types/Shaders.ts
+++ b/packages/skia/src/dom/types/Shaders.ts
@@ -1,7 +1,6 @@
import type {
Color,
- FilterMode,
- MipmapMode,
+ SamplingOptions,
SkImage,
SkRect,
SkRuntimeEffect,
@@ -26,11 +25,10 @@ export interface ShaderProps extends TransformProps, ChildrenProps {
export interface ImageShaderProps extends TransformProps, Partial {
tx: SkEnum;
ty: SkEnum;
- fm: SkEnum;
- mm: SkEnum;
fit: Fit;
rect?: SkRect;
image: SkImage | null;
+ sampling?: SamplingOptions;
}
export interface ColorProps {
diff --git a/packages/skia/src/renderer/components/image/ImageShader.tsx b/packages/skia/src/renderer/components/image/ImageShader.tsx
index 8a7d691644..09431c1492 100644
--- a/packages/skia/src/renderer/components/image/ImageShader.tsx
+++ b/packages/skia/src/renderer/components/image/ImageShader.tsx
@@ -6,24 +6,11 @@ import type { SkiaDefaultProps } from "../../processors";
export const ImageShader = ({
tx = "decal",
ty = "decal",
- fm = "nearest",
- mm = "none",
fit = "none",
transform = [],
...props
-}: SkiaDefaultProps<
- ImageShaderProps,
- "tx" | "ty" | "fm" | "mm" | "fit" | "transform"
->) => {
+}: SkiaDefaultProps) => {
return (
-
+
);
};
diff --git a/packages/skia/src/skia/types/Canvas.ts b/packages/skia/src/skia/types/Canvas.ts
index b59b4a56f1..85420c786a 100644
--- a/packages/skia/src/skia/types/Canvas.ts
+++ b/packages/skia/src/skia/types/Canvas.ts
@@ -7,8 +7,7 @@ import type {
MipmapMode,
FilterMode,
ImageInfo,
- CubicResampler,
- FilterOptions,
+ SamplingOptions,
} from "./Image";
import type { SkSVG } from "./SVG";
import type { SkColor } from "./Color";
@@ -521,7 +520,7 @@ export interface SkCanvas {
paint: SkPaint,
blendMode?: BlendMode,
colors?: SkColor[],
- sampling?: CubicResampler | FilterOptions
+ sampling?: SamplingOptions
): void;
/** Read Image pixels
diff --git a/packages/skia/src/skia/types/Image/Image.ts b/packages/skia/src/skia/types/Image/Image.ts
index 3af4b08568..59135e2963 100644
--- a/packages/skia/src/skia/types/Image/Image.ts
+++ b/packages/skia/src/skia/types/Image/Image.ts
@@ -32,6 +32,20 @@ export enum ImageFormat {
WEBP = 6,
}
+export type SamplingOptions = CubicResampler | FilterOptions;
+
+export const isCubicSampling = (
+ sampling: SamplingOptions
+): sampling is CubicResampler => {
+ "worklet";
+ return "B" in sampling && "C" in sampling;
+};
+
+export const MitchellCubicSampling = { B: 1 / 3.0, C: 1 / 3.0 };
+export const CatmullRomCubicSampling = { B: 0, C: 1 / 2.0 };
+export const CubicSampling = { B: 0, C: 0 };
+export const MakeCubic = (B: number, C: number) => ({ B, C });
+
export interface SkImage extends SkJSIInstance<"Image"> {
/**
* Returns the possibly scaled height of the image.
diff --git a/packages/skia/src/skia/web/JsiSkCanvas.ts b/packages/skia/src/skia/web/JsiSkCanvas.ts
index 4ef41dc681..43b690691b 100644
--- a/packages/skia/src/skia/web/JsiSkCanvas.ts
+++ b/packages/skia/src/skia/web/JsiSkCanvas.ts
@@ -1,31 +1,37 @@
-import type { Canvas, CanvasKit } from "canvaskit-wasm";
-
import type {
- BlendMode,
- ClipOp,
- FilterMode,
- MipmapMode,
- PointMode,
- SaveLayerFlag,
- ImageInfo,
- SkCanvas,
- SkColor,
- SkFont,
- SkImage,
- SkImageFilter,
- SkMatrix,
- SkPaint,
- SkPath,
- SkPicture,
- SkPoint,
- SkRect,
- InputRRect,
- SkSVG,
- SkTextBlob,
- SkVertices,
- SkRSXform,
- CubicResampler,
- FilterOptions,
+ Canvas,
+ CanvasKit,
+ CubicResampler as CKCubicResampler,
+ FilterOptions as CKFilterOptions,
+} from "canvaskit-wasm";
+
+import {
+ type BlendMode,
+ type ClipOp,
+ type FilterMode,
+ type MipmapMode,
+ type PointMode,
+ type SaveLayerFlag,
+ type ImageInfo,
+ type SkCanvas,
+ type SkColor,
+ type SkFont,
+ type SkImage,
+ type SkImageFilter,
+ type SkMatrix,
+ type SkPaint,
+ type SkPath,
+ type SkPicture,
+ type SkPoint,
+ type SkRect,
+ type InputRRect,
+ type SkSVG,
+ type SkTextBlob,
+ type SkVertices,
+ type SkRSXform,
+ type CubicResampler,
+ type FilterOptions,
+ isCubicSampling,
} from "../types";
import { getEnum, HostObject } from "./Host";
@@ -398,7 +404,7 @@ export class JsiSkCanvas
paint: SkPaint,
blendMode?: BlendMode,
colors?: SkColor[],
- _sampling?: CubicResampler | FilterOptions
+ sampling?: CubicResampler | FilterOptions
) {
const src = srcs.flatMap((s) =>
Array.from(JsiSkRect.fromValue(this.CanvasKit, s))
@@ -412,6 +418,20 @@ export class JsiSkCanvas
cls[i] = this.CanvasKit.ColorAsInt(r * 255, g * 255, b * 255, a * 255);
}
}
+ let ckSampling: CKCubicResampler | CKFilterOptions = {
+ filter: this.CanvasKit.FilterMode.Linear,
+ mipmap: this.CanvasKit.MipmapMode.None,
+ };
+ if (sampling && isCubicSampling(sampling)) {
+ ckSampling = sampling;
+ } else if (sampling) {
+ ckSampling = {
+ filter: getEnum(this.CanvasKit.FilterMode, sampling.filter),
+ mipmap: sampling.mipmap
+ ? getEnum(this.CanvasKit.MipmapMode, sampling.mipmap)
+ : this.CanvasKit.MipmapMode.None,
+ };
+ }
this.ref.drawAtlas(
JsiSkImage.fromValue(atlas),
src,
@@ -420,7 +440,8 @@ export class JsiSkCanvas
blendMode
? getEnum(this.CanvasKit.BlendMode, blendMode)
: this.CanvasKit.BlendMode.DstOver,
- cls
+ cls,
+ ckSampling
);
}
diff --git a/packages/skia/src/sksg/Recorder/commands/Drawing.ts b/packages/skia/src/sksg/Recorder/commands/Drawing.ts
index 94565bb988..c196a8b885 100644
--- a/packages/skia/src/sksg/Recorder/commands/Drawing.ts
+++ b/packages/skia/src/sksg/Recorder/commands/Drawing.ts
@@ -40,7 +40,10 @@ import {
BlurStyle,
ClipOp,
FillType,
+ FilterMode,
+ isCubicSampling,
isRRect,
+ MipmapMode,
PointMode,
VertexMode,
} from "../../../skia/types";
@@ -117,7 +120,7 @@ export const drawBox = (
export const drawImage = (ctx: DrawingContext, props: ImageProps) => {
"worklet";
- const { image } = props;
+ const { image, sampling } = props;
if (image) {
const fit = props.fit ?? "contain";
const rect = processRect(ctx.Skia, props);
@@ -131,7 +134,25 @@ export const drawImage = (ctx: DrawingContext, props: ImageProps) => {
},
rect
);
- ctx.canvas.drawImageRect(image, src, dst, ctx.paint);
+ if (sampling && isCubicSampling(sampling)) {
+ ctx.canvas.drawImageRectCubic(
+ image,
+ src,
+ dst,
+ sampling.B,
+ sampling.C,
+ ctx.paint
+ );
+ } else {
+ ctx.canvas.drawImageRectOptions(
+ image,
+ src,
+ dst,
+ sampling?.filter ?? FilterMode.Linear,
+ sampling?.mipmap ?? MipmapMode.None,
+ ctx.paint
+ );
+ }
}
};
@@ -349,10 +370,18 @@ export const drawPicture = (ctx: DrawingContext, props: PictureProps) => {
export const drawAtlas = (ctx: DrawingContext, props: AtlasProps) => {
"worklet";
- const { image, sprites, transforms, colors, blendMode } = props;
+ const { image, sprites, transforms, colors, blendMode, sampling } = props;
const blend = blendMode ? BlendMode[enumKey(blendMode)] : undefined;
if (image) {
- ctx.canvas.drawAtlas(image, sprites, transforms, ctx.paint, blend, colors);
+ ctx.canvas.drawAtlas(
+ image,
+ sprites,
+ transforms,
+ ctx.paint,
+ blend,
+ colors,
+ sampling
+ );
}
};
diff --git a/packages/skia/src/sksg/Recorder/commands/Shaders.ts b/packages/skia/src/sksg/Recorder/commands/Shaders.ts
index 1304d6f5ec..9ef6546339 100644
--- a/packages/skia/src/sksg/Recorder/commands/Shaders.ts
+++ b/packages/skia/src/sksg/Recorder/commands/Shaders.ts
@@ -19,9 +19,11 @@ import type {
TurbulenceProps,
TwoPointConicalGradientProps,
} from "../../../dom/types";
+import type { SkShader } from "../../../skia/types";
import {
BlendMode,
FilterMode,
+ isCubicSampling,
MipmapMode,
processUniforms,
TileMode,
@@ -179,7 +181,7 @@ const declareTurbulenceShader = (
const declareImageShader = (ctx: DrawingContext, props: ImageShaderProps) => {
"worklet";
- const { fit, image, tx, ty, fm, mm, ...imageShaderProps } = props;
+ const { fit, image, tx, ty, sampling, ...imageShaderProps } = props;
if (!image) {
return;
}
@@ -199,13 +201,24 @@ const declareImageShader = (ctx: DrawingContext, props: ImageShaderProps) => {
const lm = ctx.Skia.Matrix();
lm.concat(m3);
processTransformProps(lm, imageShaderProps);
- const shader = image.makeShaderOptions(
- TileMode[enumKey(tx)],
- TileMode[enumKey(ty)],
- FilterMode[enumKey(fm)],
- MipmapMode[enumKey(mm)],
- lm
- );
+ let shader: SkShader;
+ if (sampling && isCubicSampling(sampling)) {
+ shader = image.makeShaderCubic(
+ TileMode[enumKey(tx)],
+ TileMode[enumKey(ty)],
+ sampling.B,
+ sampling.C,
+ lm
+ );
+ } else {
+ shader = image.makeShaderCubic(
+ TileMode[enumKey(tx)],
+ TileMode[enumKey(ty)],
+ sampling?.filter ?? FilterMode.Linear,
+ sampling?.mipmap ?? MipmapMode.None,
+ lm
+ );
+ }
ctx.shaders.push(shader);
};