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 3 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);
191 changes: 191 additions & 0 deletions example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import React, { useState } from "react";
import { Button, LayoutChangeEvent, PixelRatio, StyleSheet, Text, View } from "react-native";

Check failure on line 2 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Import "LayoutChangeEvent" is only used as types

Check failure on line 2 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Replace `·Button,·LayoutChangeEvent,·PixelRatio,·StyleSheet,·Text,·View·` with `⏎··Button,⏎··LayoutChangeEvent,⏎··PixelRatio,⏎··StyleSheet,⏎··Text,⏎··View,⏎`
import {
Canvas,
Group,
Image,
Paint,
Skia,
RuntimeShader,
useImage,
useTouchHandler,
vec,
} from "@shopify/react-native-skia";
import { useDerivedValue, useSharedValue } from "react-native-reanimated";

Check failure on line 14 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

There should be at least one empty line between import groups
import { Slider } from "../SpeedTest/Slider";

const pd = PixelRatio.get();

const source = Skia.RuntimeEffect.Make(`
uniform shader image;
uniform vec2 screen;
uniform vec2 touch_pos;
uniform float drawing;
uniform float zoom_level;
uniform float is_fixed;

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 = touch_pos.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 (is_fixed == 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) * zoom_level)) * 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 - zoom_level)) * 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(

Check failure on line 98 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Delete `⏎····`
{
onStart: ({x, y}) => {

Check failure on line 100 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Replace `······onStart:·({x,·y` with `····onStart:·({·x,·y·`
touchPosX.value = x;

Check failure on line 101 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Delete `··`
touchPosY.value = y;

Check failure on line 102 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Delete `··`
drawing.value = 1;

Check failure on line 103 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Replace `········` with `······`
},

Check failure on line 104 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Delete `··`
onActive: ({x, y}) => {

Check failure on line 105 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Replace `······onActive:·({x,·y` with `····onActive:·({·x,·y·`
touchPosX.value = x;
touchPosY.value = y;
},
onEnd: () => {
drawing.value = 0;
},
},
);

const uniforms = useDerivedValue(() => {
return {
screen: vec(canvasWidth.value, canvasHeight.value),
touch_pos: vec(touchPosX.value, touchPosY.value),
drawing: drawing.value,
zoom_level: zoomLevel.value,
is_fixed: 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}

Check warning on line 177 in example/src/Examples/MagnifyingGlass/MagnifyingGlass.tsx

View workflow job for this annotation

GitHub Actions / build (example)

Arrow function should not return assignment
/>
<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