Skip to content

Commit

Permalink
Backdrop filters (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored Mar 2, 2022
1 parent 4f8f831 commit 179fcb2
Show file tree
Hide file tree
Showing 63 changed files with 920 additions and 266 deletions.
Binary file added docs/docs/assets/backdrop-filters/blur.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/docs/assets/backdrop-filters/filter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 89 additions & 0 deletions docs/docs/backdrop-filters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
id: backdrops-filters
title: Backdrop Filters
sidebar_label: Backdrop Filters
slug: /backdrops-filters
---

In Skia, backdrop filters are equivalent to their [CSS counterpart](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter). They allow you to apply image filters such as blurring to the area behind a [clipping mask](/docs/group#clipping-operations). A backdrop filter extends the [Group component](/docs/group#clipping-operations). All properties from the [group component](/docs/group) can be applied to a backdrop filter.

## Backdrop Filter
Applies an image filter to the area behind the whole canvas or behind a defined clipping mask. The first child of a backdrop filter is the image filter to apply. All properties from the [group component](/docs/group) can be applied to a backdrop filter.

### Example

Apply a black and white color matrix to the clipping area

```tsx twoslash
import { Canvas, BackdropFilter, Fill, Image, ColorMatrix, useImage } from "@shopify/react-native-skia";

// https://kazzkiq.github.io/svg-color-filter/
const BLACK_AND_WHITE = [
0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0,
];

const Filter = () => {
const image = useImage(require("./assets/oslo.jpg"));
if (!image) {
return null;
}
return (
<Canvas style={{ width: 256, height: 256 }}>
<Image
image={image}
x={0}
y={0}
width={256}
height={256}
fit="cover"
/>
<BackdropFilter clip={{ x: 0, y: 128, width: 256, height: 128 }}>
<ColorMatrix matrix={BLACK_AND_WHITE} />
</BackdropFilter>
</Canvas>
);
};
```

![BackdropBlur](assets/backdrop-filters/filter.png)

## Backdrop Blur

Creates a backdrop blur. All properties from the [group component](/docs/group) can be applied to a backdrop filter.

| Name | Type | Description |
|:----------|:--------------------|:---------------------------------------------------------|
| intensity | `number` | intensity of the blur |

## Example

```tsx twoslash
import { Canvas, Fill, Image, BackdropBlur, ColorMatrix, useImage } from "@shopify/react-native-skia";

const Filter = () => {
const image = useImage(require("./assets/oslo.jpg"));
if (!image) {
return null;
}
return (
<Canvas style={{ width: 256, height: 256 }}>
<Image
image={image}
x={0}
y={0}
width={256}
height={256}
fit="cover"
/>
<BackdropBlur
intensity={4}
clip={{ x: 0, y: 128, width: 256, height: 128 }}
>
<Fill color="rgba(0, 0, 0, 0.2)" />
</BackdropBlur>
</Canvas>
);
};
```

![BackdropBlur](assets/backdrop-filters/blur.png)
9 changes: 4 additions & 5 deletions docs/docs/group.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ It can apply the following operations to its children:
|:-----------|:-------------------|:--------------------------------------------------------------|
| transform? | `Transform2d` | [Same API than in React Native](https://reactnative.dev/docs/transforms). The default origin of the transformation is however different. It is the center object in React Native and the top-left corner in Skia. |
| origin? | `Point` | Sets the origin of the transformation. This property is not inherited by its children. |
| clipRect? | `RectOrRRect` | Rectangle or rounded rectangle to use to clip the children. |
| clipPath? | `Path or string` | Path to use to clip the children |
| clip? | `RectOrRRectOrPath` | Rectangle, rounded rectangle, or Path to use to clip the children. |
| invertClip? | `boolean` | Invert the clipping region: parts outside the clipping region will be shown and, inside will be hidden. |
| rasterize? | `RefObject<Paint>` | Draws the children as a bitmap and applies the effects provided by the paint. |

Expand Down Expand Up @@ -97,7 +96,7 @@ const SimpleTransform = () => {

## Clipping Operations

`clipRect` or `clipPath` provide a clipping region that sets what part of the children should be shown.
`clip` provides a clipping region that sets what part of the children should be shown.
Parts inside the region are shown, while those outside are hidden.
When using `invertClip`, everything outside the clipping region will be shown and, parts inside the clipping region will be hidden.

Expand All @@ -115,7 +114,7 @@ const Clip = () => {
}
return (
<Canvas style={{ flex: 1 }}>
<Group clipPath={star}>
<Group clip={star}>
<Image
image={image}
x={0}
Expand Down Expand Up @@ -146,7 +145,7 @@ const Clip = () => {
}
return (
<Canvas style={{ flex: 1 }}>
<Group clipPath={star} invertClip>
<Group clip={star} invertClip>
<Image
image={image}
x={0}
Expand Down
99 changes: 95 additions & 4 deletions docs/docs/image-filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ The provided tile mode is used when the blur kernel goes outside the input image

| Name | Type | Description |
|:----------|:--------------|:--------------------------------------------------------------|
| sigmaX | `number` | The Gaussian sigma blur value along the X axis. |
| sigmaY | `number` | The Gaussian sigma blur value along the Y axis. |
| sigmaX | `number` | The Gaussian sigma blur value along the X axis. |
| sigmaY | `number` | The Gaussian sigma blur value along the Y axis. |
| mode? | `TileMode` | `mirror`, `repeat`, `clamp`, or `decal` (default is `decal`). |
| children? | `ImageFilter` | Optional image filter to be applied first. |

## Simple Blur
### Simple Blur

```tsx twoslash
import { Canvas, Paint, Blur, Image, useImage } from "@shopify/react-native-skia";
Expand Down Expand Up @@ -53,7 +53,98 @@ const BlurImageFilter = () => {

![Clamp Blur](assets/image-filters/clamp-blur.png)

### Composing Filters
## Displacement Map

The displacement map image filter is identical to its [SVG counterpart](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap). The pixel values from the child image is used to spatially displace the filtered image.
The formula for the transformation looks like this:

```
P'(x,y) ← P( x + scale * (XC(x,y) - 0.5), y + scale * (YC(x,y) - 0.5))
```

where `P(x,y)` is the child image, in, and `P'(x,y)` is the destination. `XC(x,y)` and `YC(x,y)` are the component values of the channel designated by `channelX` and `channelY`.

| Name | Type | Description |
|:----------|:---------------|:--------------------------------------------------------------------------------------|
| channelX | `ColorChannel` | Color channel to be used along the X axis. Possible values are `r`, `g`, `b`, or `a`. |
| channelY | `ColorChannel` | Color channel to be used along the X axis. Possible values are `r`, `g`, `b`, or `a`. |
| scale | `number` | Displacement scale factor to be used |
| children? | `ImageFilter` | Optional image filter to be applied first. |

### Example

In the example below, we use a [Perlin Noise](/docs/shaders/perlin-noise) as a displacement map.

```tsx twoslash
import { Canvas, Paint, Image, Turbulence, DisplacementMap, useImage } from "@shopify/react-native-skia";

const Filter = () => {
const image = useImage(require("./assets/oslo.jpg"));
if (!image) {
return null;
}
return (
<Canvas style={{ width: 256, height: 256 }}>
<Paint>
<DisplacementMap channelX="r" channelY="a" scale={20}>
<Turbulence freqX={0.01} freqY={0.05} octaves={2} />
</DisplacementMap>
</Paint>
<Image
image={image}
x={0}
y={0}
width={256}
height={256}
fit="cover"
/>
</Canvas>
);
};
```

## Offset

This offset filter is identical to its [SVG counterpart](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap). It allow to offset the filtered image.

| Name | Type | Description |
|:----------|:---------------|:-------------------------------------------|
| x | `number` | Offset along the X axis. |
| y | `number` | Offset along the Y axis. |
| children? | `ImageFilter` | Optional image filter to be applied first. |

### Example

```tsx twoslash
import { Canvas, Paint, Image, Offset, useImage } from "@shopify/react-native-skia";

const Filter = () => {
const image = useImage(require("./assets/oslo.jpg"));
if (!image) {
return null;
}
return (
<Canvas style={{ width: 256, height: 256 }}>
<Paint>
<Offset x={30} />
</Paint>
<Image
image={image}
x={0}
y={0}
width={256}
height={256}
fit="cover"
/>
</Canvas>
);
};
```

## Composing Filters

Color Filters and Shaders and also be used as Image filters.
In the example below, we first apply a color matrix to the content and then a blur image filter.

```tsx twoslash
import { Canvas, Paint, Blur, Image, ColorMatrix, useImage } from "@shopify/react-native-skia";
Expand Down
8 changes: 7 additions & 1 deletion docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ const sidebars = {
collapsed: true,
type: "category",
label: "Effects",
items: ["mask-filters", "color-filters", "image-filters", "path-effects"],
items: [
"mask-filters",
"color-filters",
"image-filters",
"path-effects",
"backdrops-filters",
],
},
{
collapsed: true,
Expand Down
8 changes: 8 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Gooey } from "./Examples/Gooey";
import { Hue } from "./Examples/Hue";
import { Matrix } from "./Examples/Matrix";
import { Aurora } from "./Examples/Aurora";
import { Glassmorphism } from "./Examples/Glassmorphism";
import { HomeScreen } from "./Home";

const App = () => {
Expand Down Expand Up @@ -47,6 +48,13 @@ const App = () => {
header: () => null,
}}
/>
<Stack.Screen
name="Glassmorphism"
component={Glassmorphism}
options={{
header: () => null,
}}
/>
<Stack.Screen name="Drawing" component={DrawingExample} />
<Stack.Screen name="Graphs" component={GraphsScreen} />
<Stack.Screen name="Animation" component={AnimationExample} />
Expand Down
4 changes: 2 additions & 2 deletions example/src/Examples/API/Clipping2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const Clipping = () => {
height={SIZE}
fit="cover"
/>
<Group clipRect={clipRRect} invertClip>
<Group clip={clipRRect} invertClip>
<Image
image={oslo}
x={SIZE + 2 * PADDING}
Expand All @@ -61,7 +61,7 @@ export const Clipping = () => {
fit="cover"
/>
</Group>
<Group clipPath={star}>
<Group clip={star}>
<Image
image={oslo}
x={2 * SIZE + 3 * PADDING}
Expand Down
4 changes: 2 additions & 2 deletions example/src/Examples/Aurora/components/BilinearGradient.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import type { ColorProp, Vector } from "@shopify/react-native-skia";
import type { Vector, Color } from "@shopify/react-native-skia";
import {
processColorAsUnitArray,
Shader,
Expand All @@ -22,7 +22,7 @@ vec4 main(vec2 pos) {

interface BilinearGradientProps {
size: Vector;
colors: ColorProp[];
colors: Color[];
}

export const BilinearGradient = ({ size, colors }: BilinearGradientProps) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Point } from "@shopify/react-native-skia";
import type { IPoint } from "@shopify/react-native-skia";

import type { DrawingElements } from "../types";

import { getBounds } from "./getBounds";

export const findClosestElementToPoint = (
point: Point,
point: IPoint,
elements: DrawingElements
) => {
// Empty elements returns undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Point } from "@shopify/react-native-skia";
import type { IPoint } from "@shopify/react-native-skia";

import type { DrawingElements, ResizeMode } from "../types";

Expand All @@ -7,7 +7,7 @@ import { getBoundingBox } from "./getBoundingBox";
const hitSlop = 8;

export const findResizeMode = (
point: Point,
point: IPoint,
selectedElements: DrawingElements
): ResizeMode | undefined => {
const bounds = getBoundingBox(selectedElements);
Expand Down
4 changes: 2 additions & 2 deletions example/src/Examples/Drawing/Context/functions/pointInRect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { IRect, Point } from "@shopify/react-native-skia";
import type { IRect, IPoint } from "@shopify/react-native-skia";

export const pointInRect = (p: Point, rect: IRect, offset = 10) => {
export const pointInRect = (p: IPoint, rect: IRect, offset = 10) => {
return (
p.x + offset >= rect.x &&
p.x - offset <= rect.x + rect.width &&
Expand Down
4 changes: 2 additions & 2 deletions example/src/Examples/Drawing/Hooks/useTouchDrawing.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef } from "react";
import type { Point } from "@shopify/react-native-skia";
import type { IPoint } from "@shopify/react-native-skia";
import { useImage, useTouchHandler } from "@shopify/react-native-skia";

import {
Expand All @@ -21,7 +21,7 @@ import { useUxContext } from "./useUxContext";
const osloImg = require("../../../assets/card.png");

export const useTouchDrawing = () => {
const prevPointRef = useRef<Point>();
const prevPointRef = useRef<IPoint>();
const drawContext = useDrawContext();
const uxContext = useUxContext();
const oslo = useImage(osloImg);
Expand Down
Loading

0 comments on commit 179fcb2

Please sign in to comment.