diff --git a/example/src/App.tsx b/example/src/App.tsx index 4fd9c447a9..32c7319c2c 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -14,6 +14,7 @@ import { Aurora, Breathe, Filters, + MagnifyingGlass, Gooey, GraphsScreen, Hue, @@ -44,6 +45,7 @@ const linking: LinkingOptions = { API: "api", Breathe: "breathe", Filters: "filters", + MagnifyingGlass: "magnifying-glass", Gooey: "gooey", Hue: "hue", Matrix: "matrix", @@ -127,6 +129,7 @@ const App = () => { + { render(); @@ -69,4 +70,8 @@ it("should render the Filter example correctly", () => { render(); }); +it("should render the MagnifyingGlass example correctly", () => { + render(); +}); + afterEach(cleanup); diff --git a/example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx b/example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx new file mode 100644 index 0000000000..63538e5d77 --- /dev/null +++ b/example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx @@ -0,0 +1,193 @@ +import React, { useState } from "react"; +import type { LayoutChangeEvent } from "react-native"; +import { Button, PixelRatio, StyleSheet, Text, View } from "react-native"; +import { + Canvas, + Group, + Image, + Paint, + Skia, + RuntimeShader, + useImage, + useTouchHandler, + vec, +} from "@shopify/react-native-skia"; +import { useDerivedValue, useSharedValue } from "react-native-reanimated"; + +import { Slider } from "../SpeedTest/Slider"; + +const pd = PixelRatio.get(); + +const source = Skia.RuntimeEffect.Make(` +uniform shader image; +uniform vec2 screen; +uniform vec2 touchPos; +uniform float drawing; +uniform float zoomLevel; +uniform float isFixed; + +const vec2 magnifier_center = vec2(80); + +half4 main(vec2 pos) { + if (drawing == 0) + return image.eval(pos); + + // Convert to UV coordinates, accounting for aspect ratio + vec2 uv = pos / screen.y / ${pd}; + + vec2 touch = touchPos.xy; + if (touch == vec2(0)) + touch = screen.xy / 2 / ${pd}; + + // UV coordinates of touch + vec2 touch_uv = touch / screen.y; + + // Distance to touch + float touch_dist = distance(uv, touch_uv); + + // UV coordinates of magnifier center + vec2 magnifier_uv = magnifier_center / screen.y; + + // Distance from magnifier to touch + float magnifier_touch_dist = distance(magnifier_uv, touch_uv); + + if (magnifier_touch_dist < 0.1) + magnifier_uv.x = (screen.x / screen.y) - magnifier_uv.x; + + // Distance to magnifier center + float magnifier_dist = distance(uv, magnifier_uv); + + // Draw the texture + half4 fragColor = image.eval(uv * screen.y * ${pd}); + + if (isFixed == 1) { + // Draw the outline of the glass + if (magnifier_dist < 0.102) + fragColor = half4(0.01, 0.01, 0.01, 1); + + // Draw a zoomed-in version of the texture + if (magnifier_dist < 0.1) + fragColor = image.eval((touch_uv - ((magnifier_uv - uv) * zoomLevel)) * screen.y * ${pd}); + } else { + // Draw the outline of the glass + if (touch_dist < 0.102) + fragColor = half4(0.01, 0.01, 0.01, 1); + + // Draw a zoomed-in version of the texture + if (touch_dist < 0.1) + fragColor = image.eval((uv + (touch_uv - uv) * (1 - zoomLevel)) * screen.y * ${pd}); + } + + return fragColor; +}`)!; + +export const MagnifyingGlass = () => { + const canvasWidth = useSharedValue(0); + const canvasHeight = useSharedValue(0); + + const drawing = useSharedValue(0); + const touchPosX = useSharedValue(0); + const touchPosY = useSharedValue(0); + + // 1 means no zoom and 0 max + const zoomLevel = useSharedValue(0.4); + + const [isFixed, setIsFixed] = useState(true); + const isFixedSharedValue = useSharedValue(1); + + const image = useImage(require("../../assets/oslo2.jpg")); + + const onTouch = useTouchHandler({ + onStart: ({ x, y }) => { + touchPosX.value = x; + touchPosY.value = y; + drawing.value = 1; + }, + onActive: ({ x, y }) => { + touchPosX.value = x; + touchPosY.value = y; + }, + onEnd: () => { + drawing.value = 0; + }, + }); + + const uniforms = useDerivedValue(() => { + return { + screen: vec(canvasWidth.value, canvasHeight.value), + touchPos: vec(touchPosX.value, touchPosY.value), + drawing: drawing.value, + zoomLevel: zoomLevel.value, + isFixed: isFixedSharedValue.value, + }; + }, [drawing, canvasWidth, canvasHeight, zoomLevel, isFixedSharedValue]); + + if (!image) { + return ( + + Loading image... + + ); + } + + const handleCanvasLayoutChange = (event: LayoutChangeEvent) => { + canvasWidth.value = event.nativeEvent.layout.width; + canvasHeight.value = event.nativeEvent.layout.height; + }; + + return ( + + + + + + + } + transform={[{ scale: pd }]} + > + + + + + + (zoomLevel.value = value)} + /> +