Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a magnifying glass example #2419

Merged
merged 7 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Aurora,
Breathe,
Filters,
MagnifyingGlass,
Gooey,
GraphsScreen,
Hue,
Expand Down Expand Up @@ -44,6 +45,7 @@ const linking: LinkingOptions<StackParamList> = {
API: "api",
Breathe: "breathe",
Filters: "filters",
MagnifyingGlass: "magnifying-glass",
Gooey: "gooey",
Hue: "hue",
Matrix: "matrix",
Expand Down Expand Up @@ -127,6 +129,7 @@ const App = () => {
<Stack.Screen name="API" component={API} />
<Stack.Screen name="Breathe" component={Breathe} />
<Stack.Screen name="Filters" component={Filters} />
<Stack.Screen name="MagnifyingGlass" component={MagnifyingGlass} />
<Stack.Screen name="Gooey" component={Gooey} />
<Stack.Screen name="Hue" component={Hue} />
<Stack.Screen
Expand Down
5 changes: 5 additions & 0 deletions example/src/Examples/Examples.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Matrix } from "./Matrix";
import { Hue } from "./Hue";
import { Glassmorphism } from "./Glassmorphism";
import { Filters } from "./Filters";
import { MagnifyingGlass } from "./MagnifyingGlass";

it("should render the Breathe example correctly", () => {
render(<Breathe />);
Expand Down Expand Up @@ -69,4 +70,8 @@ it("should render the Filter example correctly", () => {
render(<Filters />);
});

it("should render the MagnifyingGlass example correctly", () => {
render(<MagnifyingGlass />);
});

afterEach(cleanup);
193 changes: 193 additions & 0 deletions example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Loading image...</Text>
</View>
);
}

const handleCanvasLayoutChange = (event: LayoutChangeEvent) => {
canvasWidth.value = event.nativeEvent.layout.width;
canvasHeight.value = event.nativeEvent.layout.height;
};

return (
<View style={{ flex: 1, flexDirection: "column-reverse" }}>
<Canvas
style={StyleSheet.absoluteFill}
mode="continuous"
onTouch={onTouch}
onLayout={handleCanvasLayoutChange}
>
<Group transform={[{ scale: 1 / pd }]}>
<Group
layer={
<Paint>
<RuntimeShader source={source} uniforms={uniforms} />
</Paint>
}
transform={[{ scale: pd }]}
>
<Image
image={image}
fit="cover"
x={0}
y={0}
width={canvasWidth}
height={canvasHeight}
/>
</Group>
</Group>
</Canvas>
<View
style={{
height: 60,
flexDirection: "row",
justifyContent: "space-evenly",
alignItems: "center",
backgroundColor: "black",
}}
>
<Slider
initialValue={0.5}
minValue={1}
maxValue={0}
onValueChange={(value) => (zoomLevel.value = value)}
/>
<Button
title={isFixed ? "Fixed" : "Following"}
onPress={() => {
setIsFixed((prev) => {
isFixedSharedValue.value = !prev ? 1 : 0;
return !prev;
});
}}
/>
</View>
</View>
);
};
1 change: 1 addition & 0 deletions example/src/Examples/MagnifyingGlass/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MagnifyingGlass } from "./MagnifyingGlass";
10 changes: 9 additions & 1 deletion example/src/Examples/SpeedTest/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Props {
onValueChange: (value: number) => void;
minValue: number;
maxValue: number;
initialValue?: number;
}

const size = 32;
Expand All @@ -27,14 +28,21 @@ export const Slider: React.FC<Props> = ({
onValueChange,
minValue,
maxValue,
initialValue = minValue,
}) => {
const { width } = useWindowDimensions();

const sliderWidth = width / 2;
const pickerR = size / 2;
const progressBarHeight = 3;

const translateX = useSharedValue(-pickerR);
const initialTranslateX = interpolate(
initialValue,
[minValue, maxValue],
[-pickerR, sliderWidth - pickerR]
);

const translateX = useSharedValue(initialTranslateX);
const contextX = useSharedValue(0);
const scale = useSharedValue(1);

Expand Down
1 change: 1 addition & 0 deletions example/src/Examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from "./Reanimated";
export * from "./API";
export * from "./Breathe";
export * from "./Filters";
export * from "./MagnifyingGlass";
export * from "./Gooey";
export * from "./Matrix";
export * from "./Graphs";
Expand Down
5 changes: 5 additions & 0 deletions example/src/Home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export const HomeScreen = () => {
description="Simple Image Filters"
route="Filters"
/>
<HomeScreenButton
title="🔍 Magnifying Glass"
description="Magnifying glass filter"
route="MagnifyingGlass"
/>
<HomeScreenButton
title="🟣 Gooey Effect"
description="Simple Gooey effect"
Expand Down
1 change: 1 addition & 0 deletions example/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type StackParamList = {
API: undefined;
Breathe: undefined;
Filters: undefined;
MagnifyingGlass: undefined;
Gooey: undefined;
Hue: undefined;
Matrix: undefined;
Expand Down
Loading