diff --git a/docs/docs/assets/mask/alpha.png b/docs/docs/assets/mask/alpha.png
new file mode 100644
index 0000000000..d83786d357
Binary files /dev/null and b/docs/docs/assets/mask/alpha.png differ
diff --git a/docs/docs/assets/mask/luminance.png b/docs/docs/assets/mask/luminance.png
new file mode 100644
index 0000000000..cf2f207c33
Binary files /dev/null and b/docs/docs/assets/mask/luminance.png differ
diff --git a/docs/docs/backdrop-filters.md b/docs/docs/backdrop-filters.md
index 5ed8bda826..d0a8dad88c 100644
--- a/docs/docs/backdrop-filters.md
+++ b/docs/docs/backdrop-filters.md
@@ -37,9 +37,10 @@ const Filter = () => {
height={256}
fit="cover"
/>
-
-
-
+ }
+ />
);
};
diff --git a/docs/docs/image.md b/docs/docs/image.md
index 2dc0d60cfa..d92278fc8c 100644
--- a/docs/docs/image.md
+++ b/docs/docs/image.md
@@ -9,7 +9,7 @@ Images can be draw by specifying the output rectangle and how the image should f
| Name | Type | Description |
| :----- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| image | `IImage` | Image instance. |
+| image | `SkImage` | Image instance. |
| x | `number` | Left position of the destination image. |
| y | `number` | Right position of the destination image. |
| width | `number` | Width of the destination image. |
diff --git a/docs/docs/mask.md b/docs/docs/mask.md
new file mode 100644
index 0000000000..68a2429e4c
--- /dev/null
+++ b/docs/docs/mask.md
@@ -0,0 +1,77 @@
+---
+id: mask
+title: Mask
+sidebar_label: Mask
+slug: /mask
+---
+
+The `Mask` component hides an element by masking the content at specific points.
+Just like its [CSS counterpart](https://developer.mozilla.org/en-US/docs/Web/CSS/mask), there are two modes available:
+* `alpha`: This mode indicates that the transparency (alpha channel) values of the mask layer image should be used as the mask values. This is how masks work in Figma.
+* `luminance`: This mode indicates that the luminance values of the mask layer image should be used as the mask values. This is how masks work in SVG.
+
+The first child of `Mask` is the drawing to be used as a mask, and the remaining children are the drawings to mask.
+
+By default, the mask is not clipped. If you want to clip the mask with the bounds of the content it is masking, use the `bounds` property.
+
+| Name | Type | Description |
+|:----------|:--------------------------|:--------------------------------------------------------------|
+| mode? | `alpha` or `luminance` | Is it a luminance or alpha mask (default is `alpha`) |
+| bounds? | `SkRect` | Optional rectangle to clip the mask with |
+| mask | `ReactNode[] | ReactNode` | Mask definition |
+| children | `ReactNode[] | ReactNode` | Content to mask |
+
+## Alpha Mask
+
+Opaque pixels will be visible and transparent pixels invisible.
+
+```tsx twoslash
+import {Canvas, Mask, Group, Circle, Rect} from "@shopify/react-native-skia";
+
+const Demo = () => (
+
+);
+```
+
+### Result
+
+![Alpha Mask](assets/mask/alpha.png)
+
+## Luminance Mask
+
+White pixels will be visible and black pixels invisible.
+
+```tsx twoslash
+import {Canvas, Mask, Group, Circle, Rect} from "@shopify/react-native-skia";
+
+const Demo = () => (
+
+);
+```
+
+### Result
+
+![Luminance Mask](assets/mask/luminance.png)
\ No newline at end of file
diff --git a/docs/docs/path-effects.md b/docs/docs/path-effects.md
index 58fe63deb0..d026c34863 100644
--- a/docs/docs/path-effects.md
+++ b/docs/docs/path-effects.md
@@ -159,7 +159,7 @@ Stamp the specified path to fill the shape, using the matrix to define the latic
| Name | Type | Description |
|:----------|:-------------|:------------------------------|
| path | `PathDef` | The path to use |
-| matrix | `IMatrix` | Matrix to be applied |
+| matrix | `SkMatrix` | Matrix to be applied |
| children? | `PathEffect` | Optional path effect to apply |
### Example
diff --git a/docs/docs/shaders/images.md b/docs/docs/shaders/images.md
index a379685cd6..9d0147b18f 100644
--- a/docs/docs/shaders/images.md
+++ b/docs/docs/shaders/images.md
@@ -12,13 +12,13 @@ It will use cubic sampling.
| Name | Type | Description |
|:-----------|:---------------|:-----------------------------------|
-| image | `IImage` | Image instance. |
+| 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](images). |
-| rect? | `IRect` | The destination reactangle to calculate the transformation matrix via the `fit` property. |
+| rect? | SkRect` | The destination reactangle to calculate the transformation matrix via the `fit` property. |
| transform? | `Transforms2d` | see [transformations](/docs/group#transformations). |
### Example
diff --git a/docs/sidebars.js b/docs/sidebars.js
index 39111775e4..1d12d19852 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -37,6 +37,11 @@ const sidebars = {
label: "Group",
id: "group",
},
+ {
+ type: "doc",
+ label: "Mask",
+ id: "mask",
+ },
{
collapsed: true,
type: "category",
diff --git a/example/src/Examples/API/BlendModes.tsx b/example/src/Examples/API/BlendModes.tsx
index 31d0f3b84c..0ae2dfa251 100644
--- a/example/src/Examples/API/BlendModes.tsx
+++ b/example/src/Examples/API/BlendModes.tsx
@@ -1,6 +1,13 @@
/* eslint-disable max-len */
import React from "react";
-import { Canvas, Circle, Group, Path, Skia } from "@shopify/react-native-skia";
+import {
+ Canvas,
+ Circle,
+ Group,
+ Path,
+ Skia,
+ Text,
+} from "@shopify/react-native-skia";
import { Dimensions } from "react-native";
const { width } = Dimensions.get("window");
@@ -72,8 +79,17 @@ export const BlendModes = () => {
]}
key={blendMode}
>
-
-
+
+
+
+
+
))}
diff --git a/example/src/Examples/API/Clipping2.tsx b/example/src/Examples/API/Clipping2.tsx
index a9aae45ada..8e0c5753ae 100644
--- a/example/src/Examples/API/Clipping2.tsx
+++ b/example/src/Examples/API/Clipping2.tsx
@@ -2,22 +2,17 @@ import React from "react";
import { StyleSheet, Dimensions, ScrollView } from "react-native";
import {
Skia,
- PaintStyle,
Canvas,
Image,
Group,
+ Circle,
+ Rect,
+ Mask,
useImage,
} from "@shopify/react-native-skia";
const { width } = Dimensions.get("window");
const SIZE = width / 4;
-const paint = Skia.Paint();
-paint.setAntiAlias(true);
-paint.setColor(Skia.Color("#61DAFB"));
-
-const strokePaint = paint.copy();
-strokePaint.setStyle(PaintStyle.Stroke);
-strokePaint.setStrokeWidth(2);
const star = Skia.Path.MakeFromSVGString(
// eslint-disable-next-line max-len
@@ -72,6 +67,30 @@ export const Clipping = () => {
/>
+
);
};
diff --git a/example/src/Examples/API/List.tsx b/example/src/Examples/API/List.tsx
index 4dbc060634..b08e69062b 100644
--- a/example/src/Examples/API/List.tsx
+++ b/example/src/Examples/API/List.tsx
@@ -16,7 +16,7 @@ const examples = [
},
{
screen: "Clipping",
- title: "✂️ Clipping",
+ title: "✂️ & 🎭 Clipping & Masking",
},
{
screen: "PathEffect",
diff --git a/example/src/Examples/API/index.tsx b/example/src/Examples/API/index.tsx
index 8cdc3cfa20..955746847d 100644
--- a/example/src/Examples/API/index.tsx
+++ b/example/src/Examples/API/index.tsx
@@ -59,7 +59,7 @@ export const API = () => {
name="Clipping"
component={Clipping}
options={{
- title: "✂️ Clipping",
+ title: "🎭 Clipping & Masking",
}}
/>
{
/>
-
-
+ } clip={rect}>
diff --git a/package/cpp/api/JsiSkColorFilterFactory.h b/package/cpp/api/JsiSkColorFilterFactory.h
index bdb0dfe4ba..02b9f339a2 100644
--- a/package/cpp/api/JsiSkColorFilterFactory.h
+++ b/package/cpp/api/JsiSkColorFilterFactory.h
@@ -8,6 +8,7 @@
#pragma clang diagnostic ignored "-Wdocumentation"
#include
+#include
#pragma clang diagnostic pop
@@ -59,28 +60,36 @@ class JsiSkColorFilterFactory : public JsiSkHostObject {
getContext(), SkColorFilters::Lerp(t, dst, src)));
}
- JSI_HOST_FUNCTION(MakeSRGBToLinearGamma) {
+ JSI_HOST_FUNCTION(MakeSRGBToLinearGamma) {
+ // Return the newly constructed object
+ return jsi::Object::createFromHostObject(
+ runtime, std::make_shared(
+ getContext(), SkColorFilters::SRGBToLinearGamma()));
+ }
+
+ JSI_HOST_FUNCTION(MakeLinearToSRGBGamma) {
// Return the newly constructed object
return jsi::Object::createFromHostObject(
runtime, std::make_shared(
- getContext(), SkColorFilters::SRGBToLinearGamma()));
+ getContext(), SkColorFilters::LinearToSRGBGamma()));
}
- JSI_HOST_FUNCTION(MakeLinearToSRGBGamma) {
+ JSI_HOST_FUNCTION(MakeLumaColorFilter) {
// Return the newly constructed object
return jsi::Object::createFromHostObject(
runtime, std::make_shared(
- getContext(), SkColorFilters::LinearToSRGBGamma()));
+ getContext(), SkLumaColorFilter::Make()));
}
- JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeMatrix),
- JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeBlend),
- JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeCompose),
- JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeLerp),
- JSI_EXPORT_FUNC(JsiSkColorFilterFactory,
- MakeSRGBToLinearGamma),
- JSI_EXPORT_FUNC(JsiSkColorFilterFactory,
- MakeLinearToSRGBGamma))
+ JSI_EXPORT_FUNCTIONS(
+ JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeMatrix),
+ JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeBlend),
+ JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeCompose),
+ JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeLerp),
+ JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeSRGBToLinearGamma),
+ JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeLinearToSRGBGamma),
+ JSI_EXPORT_FUNC(JsiSkColorFilterFactory, MakeLumaColorFilter)
+ )
JsiSkColorFilterFactory(std::shared_ptr context)
: JsiSkHostObject(context) {}
diff --git a/package/src/renderer/components/Mask.tsx b/package/src/renderer/components/Mask.tsx
new file mode 100644
index 0000000000..5a7a19dc5d
--- /dev/null
+++ b/package/src/renderer/components/Mask.tsx
@@ -0,0 +1,38 @@
+import type { ReactNode } from "react";
+import React from "react";
+
+import type { SkRect } from "../../skia/Rect";
+
+import { usePaintRef, Paint } from "./Paint";
+import { Defs } from "./Defs";
+import { LumaColorFilter } from "./colorFilters/LumaColorFilter";
+import { Group } from "./Group";
+
+// Here we ask the user to provide the bounds of content
+// We could compute it ourselve but prefer not to unless
+// other similar use-cases come up
+interface MaskProps {
+ mode: "luminance" | "alpha";
+ bounds?: SkRect;
+ mask: ReactNode | ReactNode[];
+ children: ReactNode | ReactNode[];
+}
+
+export const Mask = ({ children, mask, mode, bounds }: MaskProps) => {
+ const paint = usePaintRef();
+ return (
+ <>
+
+ {mode === "luminance" && }
+
+
+ {mask}
+
+ {children}
+ >
+ );
+};
+
+Mask.defaultProps = {
+ mode: "alpha",
+};
diff --git a/package/src/renderer/components/backdrop/BackdropBlur.tsx b/package/src/renderer/components/backdrop/BackdropBlur.tsx
index 1ecfcc1bf8..ba20787064 100644
--- a/package/src/renderer/components/backdrop/BackdropBlur.tsx
+++ b/package/src/renderer/components/backdrop/BackdropBlur.tsx
@@ -6,7 +6,7 @@ import type { AnimatedProps } from "../../processors";
import type { BackdropFilterProps } from "./BackdropFilter";
import { BackdropFilter } from "./BackdropFilter";
-interface BackdropBlurProps extends BackdropFilterProps {
+interface BackdropBlurProps extends Omit {
blur: number;
}
@@ -16,8 +16,7 @@ export const BackdropBlur = ({
...props
}: AnimatedProps) => {
return (
-
-
+ } {...props}>
{children}
);
diff --git a/package/src/renderer/components/backdrop/BackdropFilter.tsx b/package/src/renderer/components/backdrop/BackdropFilter.tsx
index 4bba6de119..c0be9b35c1 100644
--- a/package/src/renderer/components/backdrop/BackdropFilter.tsx
+++ b/package/src/renderer/components/backdrop/BackdropFilter.tsx
@@ -1,4 +1,5 @@
-import React, { Children } from "react";
+import type { ReactNode } from "react";
+import React from "react";
import type { AnimatedProps } from "../../processors";
import { useDrawing } from "../../nodes";
@@ -15,13 +16,15 @@ const disableFilterMemoization = (children: SkNode[]) => {
});
};
-export type BackdropFilterProps = GroupProps;
+export interface BackdropFilterProps extends GroupProps {
+ filter: ReactNode | ReactNode[];
+}
-export const BackdropFilter = (
- allProps: AnimatedProps
-) => {
- const { children: allChildren, ...props } = allProps;
- const [filterChild, ...groupChildren] = Children.toArray(allChildren);
+export const BackdropFilter = ({
+ filter: filterChild,
+ children: groupChildren,
+ ...props
+}: AnimatedProps) => {
const onDraw = useDrawing(props, (ctx, _, children) => {
disableFilterMemoization(children);
const toFilter = processChildren(ctx, children);
diff --git a/package/src/renderer/components/colorFilters/Compose.ts b/package/src/renderer/components/colorFilters/Compose.ts
index 2f4d7332d4..440235551d 100644
--- a/package/src/renderer/components/colorFilters/Compose.ts
+++ b/package/src/renderer/components/colorFilters/Compose.ts
@@ -1,9 +1,9 @@
-import type { IColorFilter } from "../../../skia";
+import type { SkColorFilter } from "../../../skia";
import { isColorFilter, isImageFilter, Skia } from "../../../skia";
import type { DeclarationResult } from "../../nodes";
export const composeColorFilter = (
- cf: IColorFilter,
+ cf: SkColorFilter,
children: DeclarationResult[]
) => {
const [col] = children.filter(isColorFilter);
diff --git a/package/src/renderer/components/colorFilters/LumaColorFilter.tsx b/package/src/renderer/components/colorFilters/LumaColorFilter.tsx
new file mode 100644
index 0000000000..43471c8d85
--- /dev/null
+++ b/package/src/renderer/components/colorFilters/LumaColorFilter.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+
+import { Skia } from "../../../skia";
+import { useDeclaration } from "../../nodes/Declaration";
+import type { AnimatedProps } from "../../processors/Animations/Animations";
+
+import { composeColorFilter } from "./Compose";
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface LumaColorFilterProps {}
+
+export const LumaColorFilter = (props: AnimatedProps) => {
+ const declaration = useDeclaration(props, (_props, children) => {
+ const cf = Skia.ColorFilter.MakeLumaColorFilter();
+ return composeColorFilter(cf, children);
+ });
+ return ;
+};
diff --git a/package/src/renderer/components/colorFilters/index.ts b/package/src/renderer/components/colorFilters/index.ts
index 42662197ef..d9c515d9ec 100644
--- a/package/src/renderer/components/colorFilters/index.ts
+++ b/package/src/renderer/components/colorFilters/index.ts
@@ -3,3 +3,4 @@ export * from "./Blend";
export * from "./Lerp";
export * from "./LinearToSRGBGamma";
export * from "./SRGBToLinearGamma";
+export * from "./LumaColorFilter";
diff --git a/package/src/renderer/components/index.ts b/package/src/renderer/components/index.ts
index 3bc024177b..e47bdb32eb 100644
--- a/package/src/renderer/components/index.ts
+++ b/package/src/renderer/components/index.ts
@@ -10,6 +10,7 @@ export * from "./pathEffects";
export * from "../processors";
export * from "./Group";
+export * from "./Mask";
export * from "./Paint";
export * from "./Compose";
export * from "./Defs";
diff --git a/package/src/skia/ColorFilter/ColorFilter.ts b/package/src/skia/ColorFilter/ColorFilter.ts
index 1bba6a006b..aa46216638 100644
--- a/package/src/skia/ColorFilter/ColorFilter.ts
+++ b/package/src/skia/ColorFilter/ColorFilter.ts
@@ -2,6 +2,6 @@ import type { SkJSIInstance } from "../JsiInstance";
export const isColorFilter = (
obj: SkJSIInstance | null
-): obj is IColorFilter => obj !== null && obj.__typename__ === "ColorFilter";
+): obj is SkColorFilter => obj !== null && obj.__typename__ === "ColorFilter";
-export type IColorFilter = SkJSIInstance<"ColorFilter">;
+export type SkColorFilter = SkJSIInstance<"ColorFilter">;
diff --git a/package/src/skia/ColorFilter/ColorFilterFactory.ts b/package/src/skia/ColorFilter/ColorFilterFactory.ts
index 54b13453e4..a16f85ad53 100644
--- a/package/src/skia/ColorFilter/ColorFilterFactory.ts
+++ b/package/src/skia/ColorFilter/ColorFilterFactory.ts
@@ -1,7 +1,7 @@
import type { SkColor } from "../Color";
import type { BlendMode } from "../Paint/BlendMode";
-import type { IColorFilter } from "./ColorFilter";
+import type { SkColorFilter } from "./ColorFilter";
export type InputColorMatrix = number[];
@@ -10,21 +10,21 @@ export interface ColorFilterFactory {
* Creates a color filter using the provided color matrix.
* @param cMatrix
*/
- MakeMatrix(cMatrix: InputColorMatrix): IColorFilter;
+ MakeMatrix(cMatrix: InputColorMatrix): SkColorFilter;
/**
* Makes a color filter with the given color and blend mode.
* @param color
* @param mode
*/
- MakeBlend(color: SkColor, mode: BlendMode): IColorFilter;
+ MakeBlend(color: SkColor, mode: BlendMode): SkColorFilter;
/**
* Makes a color filter composing two color filters.
* @param outer
* @param inner
*/
- MakeCompose(outer: IColorFilter, inner: IColorFilter): IColorFilter;
+ MakeCompose(outer: SkColorFilter, inner: SkColorFilter): SkColorFilter;
/**
* Makes a color filter that is linearly interpolated between two other color filters.
@@ -32,15 +32,21 @@ export interface ColorFilterFactory {
* @param dst
* @param src
*/
- MakeLerp(t: number, dst: IColorFilter, src: IColorFilter): IColorFilter;
+ MakeLerp(t: number, dst: SkColorFilter, src: SkColorFilter): SkColorFilter;
/**
* Makes a color filter that converts between linear colors and sRGB colors.
*/
- MakeLinearToSRGBGamma(): IColorFilter;
+ MakeLinearToSRGBGamma(): SkColorFilter;
/**
* Makes a color filter that converts between sRGB colors and linear colors.
*/
- MakeSRGBToLinearGamma(): IColorFilter;
+ MakeSRGBToLinearGamma(): SkColorFilter;
+
+ /**
+ * Makes a color filter that multiplies the luma of its input into the alpha channel,
+ * and sets the red, green, and blue channels to zero.
+ */
+ MakeLumaColorFilter(): SkColorFilter;
}
diff --git a/package/src/skia/ImageFilter/ImageFilterFactory.ts b/package/src/skia/ImageFilter/ImageFilterFactory.ts
index a850e330a3..bddb619e60 100644
--- a/package/src/skia/ImageFilter/ImageFilterFactory.ts
+++ b/package/src/skia/ImageFilter/ImageFilterFactory.ts
@@ -1,5 +1,5 @@
import type { SkColor } from "../Color";
-import type { IColorFilter } from "../ColorFilter/ColorFilter";
+import type { SkColorFilter } from "../ColorFilter/ColorFilter";
import type { IShader } from "../Shader/Shader";
import type { SkRect } from "../Rect";
@@ -69,7 +69,10 @@ export interface ImageFilterFactory {
* @param cf
* @param input - if null, it will use the dynamic source image (e.g. a saved layer)
*/
- MakeColorFilter(cf: IColorFilter, input: SkImageFilter | null): SkImageFilter;
+ MakeColorFilter(
+ cf: SkColorFilter,
+ input: SkImageFilter | null
+ ): SkImageFilter;
/**
* Create a filter that composes 'inner' with 'outer', such that the results of 'inner' are
diff --git a/package/src/skia/Paint/Paint.ts b/package/src/skia/Paint/Paint.ts
index 3080c6b47b..2057372b60 100644
--- a/package/src/skia/Paint/Paint.ts
+++ b/package/src/skia/Paint/Paint.ts
@@ -1,6 +1,6 @@
import type { SkImageFilter } from "../ImageFilter";
import type { IMaskFilter } from "../MaskFilter";
-import type { IColorFilter } from "../ColorFilter";
+import type { SkColorFilter } from "../ColorFilter";
import type { IShader } from "../Shader";
import type { SkColor } from "../Color";
import type { IPathEffect } from "../PathEffect";
@@ -95,7 +95,7 @@ export interface SkPaint extends SkJSIInstance<"Paint"> {
* Sets the current color filter, replacing the existing one if there was one.
* @param filter
*/
- setColorFilter(filter: IColorFilter | null): void;
+ setColorFilter(filter: SkColorFilter | null): void;
/**
* Sets the current image filter, replacing the existing one if there was one.