Skip to content

Commit 9912c25

Browse files
Fix bug in Coons Patches + API alignement + Example (#164)
1 parent 202efcb commit 9912c25

File tree

26 files changed

+572
-63
lines changed

26 files changed

+572
-63
lines changed

docs/docs/shapes/patch.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ import {Canvas, Patch, vec} from "@shopify/react-native-skia";
2222
const PatchDemo = () => {
2323
const colors = ["#61dafb", "#fb61da", "#61fbcf", "#dafb61"];
2424
const C = 64;
25-
const topLeft = { src: vec(0, 0), c1: vec(C, 0), c2: vec(0, C) };
26-
const topRight = { src: vec(256, 0), c1: vec(256 + C, 0), c2: vec(256, C) };
25+
const topLeft = { pos: vec(0, 0), c1: vec(C, 0), c2: vec(0, C) };
26+
const topRight = { pos: vec(256, 0), c1: vec(256 + C, 0), c2: vec(256, C) };
2727
const bottomRight = {
28-
src: vec(256, 256),
28+
pos: vec(256, 256),
2929
c1: vec(256 - 2 * C, 256),
3030
c2: vec(256, 256 - 2 * C),
3131
};
3232
const bottomLeft = {
33-
src: vec(0, 256),
33+
pos: vec(0, 256),
3434
c1: vec(-2 * C, 256),
3535
c2: vec(0, 256 - 2 * C),
3636
};

example/src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Filters } from "./Examples/Filters";
1010
import { Gooey } from "./Examples/Gooey";
1111
import { Hue } from "./Examples/Hue";
1212
import { Matrix } from "./Examples/Matrix";
13+
import { Aurora } from "./Examples/Aurora";
1314
import { HomeScreen } from "./Home";
1415

1516
const App = () => {
@@ -39,6 +40,13 @@ const App = () => {
3940
header: () => null,
4041
}}
4142
/>
43+
<Stack.Screen
44+
name="Aurora"
45+
component={Aurora}
46+
options={{
47+
header: () => null,
48+
}}
49+
/>
4250
<Stack.Screen name="Drawing" component={DrawingExample} />
4351
<Stack.Screen name="Graphs" component={GraphsScreen} />
4452
<Stack.Screen name="Animation" component={AnimationExample} />

example/src/Examples/API/Shapes2.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import {
1010
Oval,
1111
Line,
1212
Points,
13-
Patch,
1413
vec,
1514
rect,
1615
rrect,
1716
Paint,
1817
DashPathEffect,
1918
RoundedRect,
19+
Patch,
2020
} from "@shopify/react-native-skia";
2121

2222
import { Title } from "./components/Title";
@@ -59,10 +59,10 @@ const inner = rrect(
5959
0
6060
);
6161

62-
const topLeft = { src: vec(0, 0), c1: vec(0, 15), c2: vec(15, 0) };
63-
const topRight = { src: vec(100, 0), c1: vec(100, 15), c2: vec(85, 0) };
64-
const bottomRight = { src: vec(100, 100), c1: vec(100, 85), c2: vec(85, 100) };
65-
const bottomLeft = { src: vec(0, 100), c1: vec(0, 85), c2: vec(15, 100) };
62+
const topLeft = { pos: vec(16, 0), c1: vec(0, 15), c2: vec(15, 0) };
63+
const topRight = { pos: vec(100, 0), c1: vec(80, 15), c2: vec(85, 0) };
64+
const bottomRight = { pos: vec(100, 100), c1: vec(100, 85), c2: vec(85, 100) };
65+
const bottomLeft = { pos: vec(16, 100), c1: vec(0, 85), c2: vec(15, 100) };
6666

6767
export const Shapes = () => {
6868
return (
@@ -111,7 +111,7 @@ export const Shapes = () => {
111111
<Canvas style={styles.container}>
112112
<Patch
113113
colors={["#61DAFB", "#fb61da", "#61fbcf", "#dafb61"]}
114-
cubics={[topLeft, topRight, bottomRight, bottomLeft]}
114+
patch={[topLeft, topRight, bottomRight, bottomLeft]}
115115
/>
116116
</Canvas>
117117
</ScrollView>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React from "react";
2+
3+
import { CoonsPatchMeshGradient } from "./components/CoonsPatchMeshGradient";
4+
5+
export const Aurora = () => {
6+
return <CoonsPatchMeshGradient rows={3} cols={3} colors={palette.skia} />;
7+
};
8+
9+
const palette = {
10+
otto: [
11+
"#FEF8C4",
12+
"#E1F1D5",
13+
"#C4EBE5",
14+
"#ECA171",
15+
"#FFFCF3",
16+
"#D4B3B7",
17+
"#B5A8D2",
18+
"#F068A1",
19+
"#EDD9A2",
20+
"#FEEFAB",
21+
"#A666C0",
22+
"#8556E5",
23+
"#DC4C4C",
24+
"#EC795A",
25+
"#E599F0",
26+
"#96EDF2",
27+
],
28+
will: [
29+
"#2D4CD2",
30+
"#36B6D9",
31+
"#3CF2B5",
32+
"#37FF5E",
33+
"#59FB2D",
34+
"#AFF12D",
35+
"#DABC2D",
36+
"#D35127",
37+
"#D01252",
38+
"#CF0CAA",
39+
"#A80DD8",
40+
"#5819D7",
41+
],
42+
skia: [
43+
"#61DAFB",
44+
"#dafb61",
45+
"#61fbcf",
46+
"#61DAFB",
47+
"#fb61da",
48+
"#61fbcf",
49+
"#dafb61",
50+
"#fb61da",
51+
"#61DAFB",
52+
"#fb61da",
53+
"#dafb61",
54+
"#61fbcf",
55+
"#fb61da",
56+
"#61DAFB",
57+
"#dafb61",
58+
"#61fbcf",
59+
],
60+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import type { ColorProp, Vector } from "@shopify/react-native-skia";
3+
import {
4+
processColorAsUnitArray,
5+
Shader,
6+
Skia,
7+
} from "@shopify/react-native-skia";
8+
9+
const source = Skia.RuntimeEffect.Make(`
10+
uniform vec2 size;
11+
uniform vec4 color0;
12+
uniform vec4 color1;
13+
uniform vec4 color2;
14+
uniform vec4 color3;
15+
16+
vec4 main(vec2 pos) {
17+
vec2 uv = pos/size;
18+
vec4 colorA = mix(color0, color1, uv.x);
19+
vec4 colorB = mix(color2, color3, uv.x);
20+
return mix(colorA, colorB, uv.y);
21+
}`)!;
22+
23+
interface BilinearGradientProps {
24+
size: Vector;
25+
colors: ColorProp[];
26+
}
27+
28+
export const BilinearGradient = ({ size, colors }: BilinearGradientProps) => {
29+
const [color0, color1, color2, color3] = colors.map((cl) =>
30+
processColorAsUnitArray(cl, 1)
31+
);
32+
return (
33+
<Shader
34+
source={source}
35+
uniforms={{ size, color0, color1, color2, color3 }}
36+
/>
37+
);
38+
};
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import React from "react";
2+
import type {
3+
AnimationValue,
4+
CubicBezierHandle,
5+
} from "@shopify/react-native-skia";
6+
import {
7+
add,
8+
useValue,
9+
Canvas,
10+
ImageShader,
11+
Patch,
12+
vec,
13+
Paint,
14+
processColor,
15+
} from "@shopify/react-native-skia";
16+
import { Dimensions } from "react-native";
17+
18+
import { bilinearInterpolate, symmetric } from "./Math";
19+
import { Cubic } from "./Cubic";
20+
import { Curves } from "./Curves";
21+
import { useHandles } from "./useHandles";
22+
23+
const { width, height } = Dimensions.get("window");
24+
const size = vec(width, height);
25+
26+
const rectToTexture = (
27+
vertices: CubicBezierHandle[],
28+
[tl, tr, br, bl]: readonly [number, number, number, number]
29+
) =>
30+
[
31+
vertices[tl].pos,
32+
vertices[tr].pos,
33+
vertices[br].pos,
34+
vertices[bl].pos,
35+
] as const;
36+
37+
const rectToColors = (
38+
colors: number[],
39+
[tl, tr, br, bl]: readonly [number, number, number, number]
40+
) => [colors[tl], colors[tr], colors[br], colors[bl]] as const;
41+
42+
const rectToPatch =
43+
(mesh: AnimationValue<CubicBezierHandle[]>, indices: readonly number[]) =>
44+
() => {
45+
const tl = mesh.value[indices[0]];
46+
const tr = mesh.value[indices[1]];
47+
const br = mesh.value[indices[2]];
48+
const bl = mesh.value[indices[3]];
49+
return [
50+
{
51+
pos: tl.pos,
52+
c1: tl.c2,
53+
c2: tl.c1,
54+
},
55+
{
56+
pos: tr.pos,
57+
c1: symmetric(tr.c1, tr.pos),
58+
c2: tr.c2,
59+
},
60+
{
61+
pos: br.pos,
62+
c1: symmetric(br.c2, br.pos),
63+
c2: symmetric(br.c1, br.pos),
64+
},
65+
{
66+
pos: bl.pos,
67+
c1: bl.c1,
68+
c2: symmetric(bl.c2, bl.pos),
69+
},
70+
] as const;
71+
};
72+
73+
interface CoonsPatchMeshGradientProps {
74+
rows: number;
75+
cols: number;
76+
colors: string[];
77+
debug?: boolean;
78+
lines?: boolean;
79+
}
80+
81+
export const CoonsPatchMeshGradient = ({
82+
rows,
83+
cols,
84+
colors: rawColors,
85+
debug,
86+
lines,
87+
}: CoonsPatchMeshGradientProps) => {
88+
const colors = rawColors.map((color) => processColor(color, 1));
89+
const dx = width / cols;
90+
const dy = height / rows;
91+
const C = dx / 3;
92+
93+
const defaultMesh = new Array(cols + 1)
94+
.fill(0)
95+
.map((_c, col) =>
96+
new Array(rows + 1).fill(0).map((_r, row) => {
97+
const pos = vec(row * dx, col * dy);
98+
return {
99+
pos,
100+
c1: add(pos, vec(C, 0)),
101+
c2: add(pos, vec(0, C)),
102+
};
103+
})
104+
)
105+
.flat(2);
106+
107+
const mesh = useValue(defaultMesh);
108+
const rects = new Array(rows)
109+
.fill(0)
110+
.map((_r, row) =>
111+
new Array(cols).fill(0).map((_c, col) => {
112+
const l = cols + 1;
113+
const tl = row * l + col;
114+
const tr = tl + 1;
115+
const bl = (row + 1) * l + col;
116+
const br = bl + 1;
117+
return [tl, tr, br, bl] as const;
118+
})
119+
)
120+
.flat();
121+
122+
const onTouch = useHandles(mesh, defaultMesh, width, height);
123+
return (
124+
<Canvas style={{ width, height }} onTouch={onTouch}>
125+
<Paint>
126+
<ImageShader
127+
source={require("../../../assets/debug.png")}
128+
tx="repeat"
129+
ty="repeat"
130+
/>
131+
</Paint>
132+
{rects.map((r, i) => {
133+
const patch = rectToPatch(mesh, r);
134+
return (
135+
<React.Fragment key={i}>
136+
<Patch
137+
patch={patch}
138+
colors={debug ? undefined : rectToColors(colors, r)}
139+
texture={rectToTexture(defaultMesh, r)}
140+
/>
141+
{lines && <Curves patch={patch} />}
142+
</React.Fragment>
143+
);
144+
})}
145+
{defaultMesh.map(({ pos }, index) => {
146+
const edge =
147+
pos.x === 0 || pos.y === 0 || pos.x === width || pos.y === height;
148+
if (edge) {
149+
return null;
150+
}
151+
return (
152+
<Cubic key={index} mesh={mesh} index={index} color={colors[index]} />
153+
);
154+
})}
155+
</Canvas>
156+
);
157+
};
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from "react";
2+
import type {
3+
AnimationValue,
4+
CubicBezierHandle,
5+
Vector,
6+
} from "@shopify/react-native-skia";
7+
import { Line, Paint, Circle } from "@shopify/react-native-skia";
8+
9+
import { symmetric } from "./Math";
10+
11+
interface CubicProps {
12+
mesh: AnimationValue<CubicBezierHandle[]>;
13+
index: number;
14+
color: number;
15+
}
16+
17+
export const Cubic = ({ mesh, index, color }: CubicProps) => {
18+
return (
19+
<>
20+
<Line
21+
strokeWidth={2}
22+
color="white"
23+
p1={() => mesh.value[index].c1}
24+
p2={() => symmetric(mesh.value[index].c1, mesh.value[index].pos)}
25+
/>
26+
<Line
27+
strokeWidth={2}
28+
color="white"
29+
p1={() => mesh.value[index].c2}
30+
p2={() => symmetric(mesh.value[index].c2, mesh.value[index].pos)}
31+
/>
32+
<Circle c={() => mesh.value[index].pos} r={16} color={() => color}>
33+
<Paint style="stroke" strokeWidth={4} color="white" />
34+
</Circle>
35+
<Circle c={() => mesh.value[index].c1} r={10} color="white" />
36+
<Circle c={() => mesh.value[index].c2} r={10} color="white" />
37+
<Circle
38+
c={() => symmetric(mesh.value[index].c1, mesh.value[index].pos)}
39+
r={10}
40+
color="white"
41+
/>
42+
<Circle
43+
c={() => symmetric(mesh.value[index].c2, mesh.value[index].pos)}
44+
r={10}
45+
color="white"
46+
/>
47+
</>
48+
);
49+
};

0 commit comments

Comments
 (0)