Skip to content

Commit

Permalink
Merge pull request #2001 from Shopify/snapshots
Browse files Browse the repository at this point in the history
Substancial bug fixes in makeImageFromView
  • Loading branch information
wcandillon authored Nov 24, 2023
2 parents 32c892a + 2209425 commit 80911b0
Show file tree
Hide file tree
Showing 36 changed files with 798 additions and 159 deletions.
65 changes: 48 additions & 17 deletions docs/docs/snapshot-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,56 @@ slug: /snapshotviews

The function `makeImageFromView` lets you take a snapshot of another React Native View as a Skia SkImage. The function accepts a ref to a native view and returns a promise that resolves to an `SkImage` instance upon success.

::::info

`makeImageFromView` is not support on Fabric yet ([see #2002](https://github.com/Shopify/react-native-skia/issues/2002)).

::::info


On Android, it is safer to use `collapsable=false` on the root view of the snapshot to prevent the root view from being removed by React Native.
If the view is optimized away, `makeImageFromView` will crash.

```tsx twoslash
import { useState, useRef } from "react";
import { View } from "react-native";
import { View, Text, PixelRatio, StyleSheet, Pressable } 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 snapshot of
const viewRef = useRef<View>(null);

// 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 snapshot of the view
const snapshot = await makeImageFromView(viewRef);
setImage(snapshot);
import { makeImageFromView, Canvas, Image } from "@shopify/react-native-skia";

const pd = PixelRatio.get();

const Demo = () => {
// Create a ref for the view you'd like to take a snapshot of
const ref = useRef<View>(null);
// Create a state variable to store the snapshot
const [image, setImage] = useState<SkImage | null>(null);
// Create a function to take the snapshot
const onPress = async () => {
// Take the snapshot of the view
const snapshot = await makeImageFromView(ref);
setImage(snapshot);
};
return (
<View style={{ flex: 1 }}>
<Pressable onPress={onPress}>
<View ref={ref} collapsable={false} style={{ backgroundColor: "cyan", flex: 1 }}>
<Text>This is a React Native View</Text>
</View>
</Pressable>
{
image && (
<Canvas style={StyleSheet.absoluteFill}>
<Image
image={image}
x={0}
y={0}
width={image.width() / pd}
height={image.height() / pd}
/>
</Canvas>
)
}
</View>
)
};
```
15 changes: 11 additions & 4 deletions docs/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3185,6 +3185,11 @@
"@webassemblyjs/ast" "1.11.6"
"@xtuc/long" "4.2.2"

"@webgpu/[email protected]":
version "0.1.21"
resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.21.tgz#b181202daec30d66ccd67264de23814cfd176d3a"
integrity sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==

"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
Expand Down Expand Up @@ -3906,10 +3911,12 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz#752f21f56f96f1b1a52e97aae98c57c562d5d9da"
integrity sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==

[email protected]:
version "0.38.2"
resolved "https://registry.yarnpkg.com/canvaskit-wasm/-/canvaskit-wasm-0.38.2.tgz#b6c2be236670fd0f18977b9026652b2c0e201fee"
integrity sha512-ieRb6DO4yL91qUfyRgmyhp2Hi1KmQ9lIMfKacxHVlfp/CpKCkzgAxRGUbCsJFzwLKjs9fufGrIyvnzEYRwm1XQ==
[email protected]:
version "0.39.1"
resolved "https://registry.yarnpkg.com/canvaskit-wasm/-/canvaskit-wasm-0.39.1.tgz#c3c8f3962cbabbedf246f7bcf90e859013c7eae9"
integrity sha512-Gy3lCmhUdKq+8bvDrs9t8+qf7RvcjuQn+we7vTVVyqgOVO1UVfHpsnBxkTZw+R4ApEJ3D5fKySl9TU11hmjl/A==
dependencies:
"@webgpu/types" "0.1.21"

ccount@^1.0.0:
version "1.1.0"
Expand Down
7 changes: 6 additions & 1 deletion example/src/Examples/API/Snapshot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,13 @@ const Component = () => {
height: 40,
backgroundColor: "red",
opacity: 0.5,
overflow: "hidden",
}}
/>
>
<View style={{ width: 150, height: 10, backgroundColor: "cyan" }}>
<View style={{ width: 20, height: 20, backgroundColor: "red" }} />
</View>
</View>
</View>
<Button
title={"Press me to increment (" + counter + ")"}
Expand Down
19 changes: 19 additions & 0 deletions example/src/Tests/Screens/Snapshot1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { View } from "react-native";

export const Snapshot1 = () => {
return (
<View style={{ flex: 1, backgroundColor: "cyan", padding: 16 }}>
<View
style={{
width: 100,
height: 100,
backgroundColor: "magenta",
borderRadius: 10,
borderWidth: 4,
borderColor: "yellow",
}}
/>
</View>
);
};
175 changes: 175 additions & 0 deletions example/src/Tests/Screens/Snapshot2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/* eslint-disable max-len */
import React from "react";
import {
View,
StyleSheet,
Button,
Text,
useWindowDimensions,
ScrollView,
} from "react-native";
import { Canvas, RoundedRect, Image, Skia } from "@shopify/react-native-skia";
import { Switch } from "react-native-gesture-handler";

export const Snapshot2 = () => {
return (
<View style={{ flex: 1 }}>
<View style={styles.view}>
<Component />
</View>
</View>
);
};

const Component = () => {
return (
<ScrollView style={styles.scrollview}>
<View
style={{
position: "absolute",
top: 0,
left: 0,
width: 200,
height: 200,
}}
>
<View
style={{
position: "absolute",
transform: [{ translateX: 20 }, { translateY: 100 }],
top: 0,
left: 0,
width: 80,
height: 80,
backgroundColor: "red",
}}
/>
<View
style={{
position: "absolute",
top: 100,
left: 100,
width: 80,
height: 80,
backgroundColor: "blue",
}}
/>
</View>
<Text>Hello World!</Text>
<View style={{ flexDirection: "row" }}>
<View
style={{
width: 80,
height: 80,
backgroundColor: "blue",
opacity: 0.5,
}}
>
<View
style={{
width: 40,
height: 40,
backgroundColor: "green",
opacity: 0.5,
}}
/>
</View>
<View
style={{
width: 40,
height: 40,
backgroundColor: "red",
opacity: 0.5,
overflow: "hidden",
}}
>
<View style={{ width: 150, height: 10, backgroundColor: "cyan" }}>
<View style={{ width: 20, height: 20, backgroundColor: "red" }} />
</View>
</View>
</View>
<Button
title={"Press me to increment (" + 1 + ")"}
onPress={() => true}
/>
<Switch value={true} />
<Canvas style={{ width: 100, height: 100 }}>
<RoundedRect x={0} y={20} width={80} height={80} r={10} color="blue" />
</Canvas>
<Text>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;👆 This is a Skia Canvas!</Text>
<Interleaving />
</ScrollView>
);
};

const styles = StyleSheet.create({
view: {
flex: 1,
backgroundColor: "yellow",
},
scrollview: {
padding: 14,
},
pinkContainer: {
backgroundColor: "#00ff6922",
position: "absolute",
alignSelf: "center",
left: 50,
top: 50,
right: 50,
bottom: 50,
},
integratingContainer: {
position: "absolute",
left: "25%",
top: 0,
bottom: 0,
transform: [{ scale: 0.5 }],
},
salmonContainer: {
backgroundColor: "#ff8c6922",
position: "absolute",
alignSelf: "center",
padding: 20,
marginTop: 80,
},
black: {
color: "black",
textAlign: "center",
},
innerContainer: {
backgroundColor: "#ff8c0044",
transform: [{ translateX: 50 }, { rotate: "45deg" }],
},
});

const image = Skia.Image.MakeImageFromEncoded(
Skia.Data.fromBase64(
"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"
)
);

const Interleaving = () => {
const { height, width } = useWindowDimensions();

return (
<View style={styles.integratingContainer}>
<View style={styles.pinkContainer} />
<Canvas style={{ width, height }}>
<Image
image={image}
height={height}
width={width}
fit={"cover"}
opacity={0.5}
/>
</Canvas>
<View style={styles.salmonContainer}>
<Text style={styles.black}>Let me be a part of your snapshot!</Text>
<View style={styles.innerContainer}>
<Text style={styles.black}>I'm inside the red thingie!</Text>
</View>
</View>
</View>
);
};
17 changes: 17 additions & 0 deletions example/src/Tests/Screens/Snapshot3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
import { View } from "react-native";

export const Snapshot3 = () => {
return (
<View
style={{
margin: 40,
borderRadius: 40,
overflow: "hidden",
}}
>
<View style={{ backgroundColor: "blue", height: 100 }} />
<View style={{ backgroundColor: "green", height: 200 }} />
</View>
);
};
13 changes: 13 additions & 0 deletions example/src/Tests/Screens/Snapshot4.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Canvas, RoundedRect } from "@shopify/react-native-skia";
import React from "react";
import { View } from "react-native";

export const Snapshot4 = () => {
return (
<View style={{ flex: 1, opacity: 0 }}>
<Canvas style={{ width: 100, height: 100 }}>
<RoundedRect x={0} y={20} width={80} height={80} r={10} color="blue" />
</Canvas>
</View>
);
};
13 changes: 13 additions & 0 deletions example/src/Tests/Screens/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { FC } from "react";

import { Snapshot1 } from "./Snapshot1";
import { Snapshot2 } from "./Snapshot2";
import { Snapshot3 } from "./Snapshot3";
import { Snapshot4 } from "./Snapshot4";

export const Screens: Record<string, FC> = {
Snapshot1,
Snapshot2,
Snapshot3,
Snapshot4,
};
Loading

0 comments on commit 80911b0

Please sign in to comment.