Skip to content

Commit

Permalink
Merge branch 'main' into onsize
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored May 23, 2023
2 parents b03f346 + c37c429 commit 0e162e5
Show file tree
Hide file tree
Showing 109 changed files with 1,710 additions and 567 deletions.
20 changes: 11 additions & 9 deletions docs/docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ slug: /getting-started/installation
React Native Skia brings the [Skia Graphics Library](https://skia.org/) to React Native.
Skia serves as the graphics engine for Google Chrome and Chrome OS, Android, Flutter, Mozilla Firefox, Firefox OS, and many other products.

> **Version compatibility**: `react-native@>=0.66` and `react@>=18` are required.
> **Version compatibility:**
> `react-native@>=0.66` and `react@>=18` are required.
> In addition you should make sure you're on at least `iOS 13` and `Android API level 16` or above.
**Install the library using yarn:**

```sh
yarn add @shopify/react-native-skia
```

Or using npm:
**Or using npm:**

```sh
npm install @shopify/react-native-skia
Expand All @@ -24,9 +28,9 @@ npm install @shopify/react-native-skia

Below is the app size increase to be expected when adding React Native Skia to your project ([learn more](bundle-size)).

| iOS | Android | Web |
| ---- | ------- | ----- |
| 6 MB | 4 MB | 7,2 MB |
| iOS | Android | Web |
| ---- | ------- | ------ |
| 6 MB | 4 MB | 7,2 MB |

## iOS

Expand Down Expand Up @@ -70,7 +74,7 @@ yarn bootstrap
cd example && yarn start
```

To run the example project on iOS, you will need to run `pod install`, and on Android, you will also need Android NDK to be installed ([see here](#android)).
To run the example project on iOS, you will need to run `pod install`, and on Android, you will also need Android NDK to be installed ([see here](#android)).

## Debugging

Expand All @@ -85,9 +89,7 @@ In order to load CanvasKit and in turn loading the React Native Skia mock, you n

```js
// This is needed to load CanvasKit
"globalSetup": "@shopify/react-native-skia/globalJestSetup.js",
"globalSetup": "@shopify/react-native-skia/globalJestSetup.js",
// This is needed to load the mock
"setupFiles": ["@shopify/react-native-skia/jestSetup.js"]
```


46 changes: 35 additions & 11 deletions docs/docs/getting-started/web.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ While this is a substantial file size, you have control over the user experience
We provide direct integrations with [Expo](#Expo) and [Remotion](#Remotion).
Below you will also find the manual installation steps to run the module on any React Native Web projects.

You can use React Native Skia without React Native Web.

## Expo

Using React Native Skia on Expo web is reasonably straightforward.
Expand Down Expand Up @@ -49,29 +51,51 @@ To use React Native Skia with Remotion, please follow [the following installatio
## Manual Webpack Installation

To run React Native Skia on Web, you need to do three things:
* Make sure that the WebAssembly file is available from the build system. This can easily be done using the [webpack copy plugin](https://webpack.js.org/plugins/copy-webpack-plugin/). Another option is to [load CanvasKit from a CDN](#using-a-cdn).
* Configure the build system to resolve the following two node modules: `fs` and `path`. One way to do it is to use the [node polyfill plugin](https://www.npmjs.com/package/node-polyfill-webpack-plugin).
* If you are not using the `react-native-reanimated`, Webpack will throw a warning since React Native Skia refers to that module.

- Make sure that the WebAssembly file is available from the build system.
- Configure the build system to resolve the following two node modules: `fs` and `path`. One way to do it is to use the [node polyfill plugin](https://www.npmjs.com/package/node-polyfill-webpack-plugin).
- If you are not using the `react-native-reanimated`, Webpack will throw a warning since React Native Skia refers to that module.

So following is an example of a Webpack v5 configuration that supports React Native Skia.
These three steps can easily be adapted to your build system.

```tsx
import CopyPlugin from "copy-webpack-plugin";
import fs from "fs";
import { sources } from "webpack";
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";

const newConfiguration = {
...currentConfiguration,
plugins: [
...currentConfiguration.plugins,
// 1. Make the wasm file available to the build system
new CopyPlugin({
patterns: [
{
from: "node_modules/canvaskit-wasm/bin/full/canvaskit.wasm",
},
],
}),
new (class CopySkiaPlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap("AddSkiaPlugin", (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: "copy-skia",
stage:
compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
async () => {
const src = require.resolve(
"canvaskit-wasm/bin/full/canvaskit.wasm"
);
if (compilation.getAsset(src)) {
// Skip emitting the asset again because it's immutable
return;
}

compilation.emitAsset(
"/canvaskit.wasm",
new sources.RawSource(await fs.promises.readFile(src))
);
}
);
});
}
})(),
// 2. Polyfill fs and path module from node
new NodePolyfillPlugin()
],
Expand Down
53 changes: 48 additions & 5 deletions docs/docs/image-svg.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ If the root dimensions are in absolute units, the width/height properties have n
| svg | `SVG` | SVG Image. |
| width? | `number` | Width of the destination image. This is used to resolve the initial viewport when the root SVG width is specified in relative units. |
| height? | `number` | Height of the destination image. This is used to resolve the initial viewport when the root SVG height is specified in relative units. |

| x? | `number` | Optional displayed x coordinate of the svg container. |
| y? | `number` | Optional displayed y coordinate of the svg container. |

### Example

Expand All @@ -34,8 +35,6 @@ const ImageSVGDemo = () => {
{ svg && (
<ImageSVG
svg={svg}
x={0}
y={0}
width={256}
height={256}
/>)
Expand Down Expand Up @@ -76,9 +75,13 @@ export const SVG = () => {

As mentionned above, if the root dimensions are in absolute units, the width/height properties have no effect since the initial viewport is fixed. However you can access these values and use the fitbox function.

```tsx
### Example

In the example below we scale the SVG to the canvas width and height.

```tsx twoslash
import React from "react";
import { Canvas, ImageSVG, Skia, rect, fitbox } from "@shopify/react-native-skia";
import { Canvas, ImageSVG, Skia, rect, fitbox, Group } from "@shopify/react-native-skia";

const svg = Skia.SVG.MakeFromString(
`<svg viewBox='0 0 20 20' width="20" height="20" xmlns='http://www.w3.org/2000/svg'>
Expand All @@ -102,8 +105,48 @@ export const SVG = () => {
};
```

### Result

<img src={require("/static/img/svg.png").default} width="256" height="256" />

## Applying Filters

The `ImageSVG` component doesn't follow the same painting rules as other components.
This is because behind the scene, we use the SVG module from Skia.
However you can apply image filters using the `layer` property.

### Example

In the example below we apply a blur image filter to the SVG.

```tsx twoslash
import React from "react";
import { Canvas, ImageSVG, Skia, rect, fitbox, useSVG, Group, Paint, Blur } from "@shopify/react-native-skia";

const width = 256;
const height = 256;

export const SVG = () => {
const tiger = useSVG(require("./tiger.svg"));
if (!tiger) {
return null;
}
const src = rect(0, 0, tiger.width(), tiger.height());
const dst = rect(0, 0, width, height);
return (
<Canvas style={{ flex: 1 }}>
<Group transform={fitbox("contain", src, dst)} layer={<Paint><Blur blur={10} /></Paint>}>
<ImageSVG svg={tiger} x={0} y={0} width={800} height={800} />
</Group>
</Canvas>
);
};
```

### Result

<img src={require("/static/img/blurred-tiger.png").default} width="256" height="256" />

## SVG Support

The [SVG module from Skia](https://github.com/google/skia/tree/main/modules/svg) displays SVGs as images.
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/snapshot-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ import { View } from "react-native";
import type { SkImage } from "@shopify/react-native-skia";
import { makeImageFromView } from "@shopify/react-native-skia";

// Create a ref for the view you'd like to take a screenshot of
// Create a ref for the view you'd like to take a snapshot of
const viewRef = useRef<View>(null);

// Create a state variable to store the screenshot
// Create a state variable to store the snapshot
const [image, setImage] = useState<SkImage | null>(null);

// Create a function to take the snapshot
const takeSnapshot = async () => {
if (viewRef.current == null) {
return;
}
// Take the screenshot of the view
// Take the snapshot of the view
const snapshot = await makeImageFromView(viewRef);
setImage(snapshot);
};
Expand Down
Binary file added docs/static/img/blurred-tiger.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/static/img/mask/blend-mode-mask.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/static/img/svg2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions example/jestSetup.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ jest.mock("react-native-reanimated", () => {
jest.mock("react-native/Libraries/Animated/NativeAnimatedHelper");

jest.mock("@shopify/react-native-skia", () => {
jest.mock("../package/src/Platform", () => {
const Noop = () => undefined;
return {
OS: "web",
PixelRatio: 1,
requireNativeComponent: Noop,
resolveAsset: Noop,
findNodeHandle: Noop,
NativeModules: Noop,
View: Noop,
};
});
return require("../package/src/mock").Mock(global.CanvasKit);
});

Expand Down
1 change: 0 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"babel-jest": "^29.2.1",
"babel-loader": "^8.2.5",
"babel-plugin-react-native-web": "^0.17.7",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8.19.0",
"eslint-config-react-native-wcandillon": "3.9.0",
"eslint-plugin-reanimated": "2.0.0",
Expand Down
8 changes: 4 additions & 4 deletions example/src/Examples/Animation/AnimateTextOnPath.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ export const AnimateTextOnPath = () => {

// Create a derived value that interpolates between
// the start and end path
const path = useComputedValue(
() => path1.interpolate(path2, progress.current)!,
[progress]
);
const path = useComputedValue(() => {
path?.current?.dispose();
return path1.interpolate(path2, progress.current)!;
}, [progress]);

return (
<AnimationDemo title={"Interpolating text on path."}>
Expand Down
Binary file added example/src/Tests/assets/mask.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion example/src/Tests/useAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const NotoColorEmojiSrc =
export const useAssets = () => {
const [error, setError] = useState<Error | null>(null);
const errorHandler = useCallback((e: Error) => setError(e), []);
const mask = useImage(require("./assets/mask.png"), errorHandler);
const oslo = useImage(require("./assets/oslo.jpg"), errorHandler);
const skiaLogoJpeg = useImage(SkiaLogoJpeg, errorHandler);
const skiaLogoPng = useImage(SkiaLogo, errorHandler);
Expand All @@ -39,7 +40,8 @@ export const useAssets = () => {
!NotoColorEmoji ||
!NotoSansSCRegular ||
!skiaLogoJpeg ||
!skiaLogoPng
!skiaLogoPng ||
!mask
) {
return null;
}
Expand All @@ -50,5 +52,6 @@ export const useAssets = () => {
oslo,
skiaLogoJpeg,
skiaLogoPng,
mask,
};
};
36 changes: 28 additions & 8 deletions example/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// webpack.config.js
const path = require("path");
const fs = require("fs");

const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");

const { presets, plugins } = require(`${__dirname}/babel.config.js`);

Expand Down Expand Up @@ -108,12 +108,32 @@ module.exports = {
// See: <https://github.com/necolas/react-native-web/issues/349>
__DEV__: JSON.stringify(true),
}),
new CopyPlugin({
patterns: [
{
from: "node_modules/canvaskit-wasm/bin/full/canvaskit.wasm",
},
],
}),
new (class CopySkiaPlugin {
apply(compiler) {
compiler.hooks.thisCompilation.tap("AddSkiaPlugin", (compilation) => {
compilation.hooks.processAssets.tapPromise(
{
name: "copy-skia",
stage:
compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
async () => {
const src = require.resolve(
"canvaskit-wasm/bin/full/canvaskit.wasm"
);
if (compilation.getAsset(src)) {
// Skip emitting the asset again because it's immutable
return;
}

compilation.emitAsset(
"/canvaskit.wasm",
new webpack.sources.RawSource(await fs.promises.readFile(src))
);
}
);
});
}
})(),
],
};
Loading

0 comments on commit 0e162e5

Please sign in to comment.