in react-native-template-pytorch-live/template/src/toolbox/canvas/CanvasTransform.tsx [16:137]
export default function CanvasStarter() {
const isFocused = useIsFocused();
// `layout` contains canvas properties like width and height
const [layout, setLayout] = useState<LayoutRectangle | null>(null);
// `ctx` is drawing context to draw shapes
const [ctx, setCtx] = useState<CanvasRenderingContext2D>();
// handler to get drawing context when canvas is ready. See <Canvas onContext2D={...}> below
const handleContext2D = useCallback(
async (ctx: CanvasRenderingContext2D) => {
setCtx(ctx);
},
[setCtx],
);
useLayoutEffect(() => {
if (ctx != null) {
// Here we use `layout` to calculate center position
const size = [layout?.width || 0, layout?.height || 0];
const half = size.map(s => s / 2);
const quarter = size.map(s => s / 4);
// fill background by drawing a rect
ctx.fillStyle = colors.accent4;
ctx.fillRect(0, 0, size[0], size[1]);
// Here we draw the 4 quadrants by first translating to center
// And then do additional translations inside save/restore
ctx.save();
ctx.translate(half[0], half[1]);
ctx.save();
ctx.translate(-quarter[0], -quarter[1]); // top-left
ctx.fillStyle = colors.black;
ctx.fillRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
ctx.save();
ctx.translate(0, -quarter[1]); // top-right
ctx.fillStyle = colors.accent1;
ctx.fillRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
ctx.save();
ctx.translate(-quarter[0], 0); // bottom-left
ctx.fillStyle = colors.accent1;
ctx.fillRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
ctx.save(); // bottom-right (no addition translation)
ctx.fillStyle = colors.white;
ctx.fillRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
// Here we scale/rotate/skew each quadrant in strokes
// To transform around a origin, translate to it, transform, and translate back
// scale from center of top-left quadrant
ctx.save();
ctx.lineWidth = 10;
ctx.translate(-quarter[0], -quarter[1]);
ctx.translate(quarter[0] / 2, quarter[1] / 2); // translate to center of quadrant
ctx.scale(0.5, 0.5);
ctx.translate(-quarter[0] / 2, -quarter[1] / 2); // and back
ctx.strokeStyle = colors.white;
ctx.strokeRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
// scale from bottom-left corner of top-right quadrant
ctx.save();
ctx.scale(1.25, 1.25);
ctx.translate(0, -quarter[1]);
ctx.lineWidth = 10;
ctx.strokeStyle = `${colors.accent3}cc`;
ctx.strokeRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
// rotate from center of bottom-left quadrant
ctx.save();
ctx.translate(-quarter[0], 0);
ctx.translate(quarter[0] / 2, quarter[1] / 2);
ctx.rotate(Math.PI / 6);
ctx.translate(-quarter[0] / 2, -quarter[1] / 2);
ctx.lineWidth = 10;
ctx.strokeStyle = `${colors.accent3}cc`;
ctx.strokeRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
// skew and scale from top-left corner of bottom-right quadrant, using 2D matrix transform
ctx.save();
ctx.setTransform(0.75, 0.2, 0.4, 0.75, half[0], half[1]);
ctx.lineWidth = 10;
ctx.strokeStyle = `${colors.accent3}cc`;
ctx.strokeRect(0, 0, quarter[0], quarter[1]);
ctx.restore();
// restore the first translation
ctx.restore();
// Need to include this at the end, for now.
ctx.invalidate();
}
}, [ctx, layout]); // update only when layout or context changes
if (!isFocused) {
return null;
}
return (
<Canvas
style={StyleSheet.absoluteFill}
onContext2D={handleContext2D}
onLayout={event => {
const {layout} = event.nativeEvent;
setLayout(layout);
}}
/>
);
}