Skip to content

Commit

Permalink
Add a magnifying glass example (#2419)
Browse files Browse the repository at this point in the history
* Add a magnifying glass example

* lint fix

---------

Co-authored-by: William Candillon <wcandillon@gmail.com>
  • Loading branch information
rayronvictor and wcandillon authored Jun 13, 2024
1 parent 81654ce commit 391fb9a
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 1 deletion.
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

0 comments on commit 391fb9a

Please sign in to comment.