question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

focalX and focalY are wrong on android only

See original GitHub issue

Description

I am trying to implement pinch to zoom and I pretty sure my math is correct. But the focalX and focalY are wrong on android only

Android android

iOS ios

On android, the first pinch-to-zoom behave “correct”. The last pinch-to-zoom must be wrong. I tap two points and the coordinates are x = 194.86 y = 351.61 and x = 194.32 y = 396.36 . Then I pinch and the focal point is x = 195.93 y = 464.81. I expect the y-coord should be between two taps point. Now it is outside. 351.61 <= 464.81 <= 396.36 ❌ ❌ ❌ ❌ . x-coord has the same issue.

When you take a look iOS, it behaves correctly 313 <= 351 <= 374 ✅✅✅✅

I think the bug is on either react-native or react-native-gesture-handler. I do not think it is react or react-native-reanimated bug

Platforms

  • iOS
  • Android
  • Web

Screenshots

Steps To Reproduce

  1. git clone https://github.com/wood1986/pinch-bug.git
  2. yarn android

Expected behavior

tap[0].x <= focalX <= tap[1].x tap[0].y <= focalY <= tap[1].y

Actual behavior

focalX <= tap[0].x or tap[1].x <= focalX focalY <= tap[0].y or tap[1].y <= focalY

Snack or minimal code example

Package versions

  • React: 18.0.0
  • React Native: 0.69.2
  • React Native Gesture Handler: 2.5.0
  • React Native Reanimated: 2.9.1

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:1
  • Comments:25 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
wood1986commented, Sep 2, 2022

Your code is perfect!!! Thank you so much

1reaction
j-piaseckicommented, Aug 30, 2022

This should do the trick:

import React from 'react';
import { StyleSheet, SafeAreaView, View, Button } from 'react-native';
import {
  Gesture,
  GestureDetector,
  GestureHandlerRootView,
} from 'react-native-gesture-handler';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  useAnimatedRef,
  measure,
} from 'react-native-reanimated';
import { identity3, Matrix3, multiply3 } from 'react-native-redash';

function translateMatrix(matrix: Matrix3, x: number, y: number) {
  'worklet';
  return multiply3(matrix, [1, 0, x, 0, 1, y, 0, 0, 1]);
}

function scaleMatrix(matrox: Matrix3, value: number) {
  'worklet';
  return multiply3(matrox, [value, 0, 0, 0, value, 0, 0, 0, 1]);
}

const ImageViewer = () => {
  const ref = useAnimatedRef();
  const origin = useSharedValue({ x: 0, y: 0 });
  const transform = useSharedValue(identity3);
  const scale = useSharedValue(1);
  const translation = useSharedValue({ x: 0, y: 0 });

  const pinch = Gesture.Pinch()
    .onStart((event) => {
      const measured = measure(ref);
      origin.value = {
        x: event.focalX - measured.width / 2,
        y: event.focalY - measured.height / 2,
      };
    })
    .onChange((event) => {
      scale.value = event.scale;
    })
    .onEnd(() => {
      let matrix = identity3;
      matrix = translateMatrix(matrix, origin.value.x, origin.value.y);
      matrix = scaleMatrix(matrix, scale.value);
      matrix = translateMatrix(matrix, -origin.value.x, -origin.value.y);
      transform.value = multiply3(matrix, transform.value);
      scale.value = 1;
    });

  const pan = Gesture.Pan()
    .averageTouches(true)
    .onChange((event) => {
      translation.value = {
        x: event.translationX,
        y: event.translationY,
      };
    })
    .onEnd(() => {
      let matrix = identity3;
      matrix = translateMatrix(
        matrix,
        translation.value.x,
        translation.value.y
      );
      transform.value = multiply3(matrix, transform.value);
      translation.value = { x: 0, y: 0 };
    });

  const animatedStyle = useAnimatedStyle(() => {
    let matrix = identity3;

    if (translation.value.x !== 0 || translation.value.y !== 0) {
      matrix = translateMatrix(
        matrix,
        translation.value.x,
        translation.value.y
      );
    }

    if (scale.value !== 1) {
      matrix = translateMatrix(matrix, origin.value.x, origin.value.y);
      matrix = scaleMatrix(matrix, scale.value);
      matrix = translateMatrix(matrix, -origin.value.x, -origin.value.y);
    }

    matrix = multiply3(matrix, transform.value);

    return {
      transform: [
        { translateX: matrix[2] },
        { translateY: matrix[5] },
        { scaleX: matrix[0] },
        { scaleY: matrix[4] },
      ],
    };
  });

  return (
    <>
      <GestureDetector gesture={Gesture.Simultaneous(pinch, pan)}>
        <Animated.View
          ref={ref}
          collapsable={false}
          style={[styles.fullscreen]}>
          <Animated.Image
            source={require('./1.png')}
            resizeMode={'contain'}
            style={[styles.fullscreen, animatedStyle]}
          />
        </Animated.View>
      </GestureDetector>

      <View style={{ position: 'absolute', end: 0, backgroundColor: 'black' }}>
        <Button
          title="RESET"
          onPress={() => {
            transform.value = identity3;
          }}
        />
      </View>
    </>
  );
};

const styles = StyleSheet.create({
  fullscreen: {
    ...StyleSheet.absoluteFillObject,
    flex: 1,
    width: '100%',
    height: '100%',
    resizeMode: 'contain',
  },

  pointer: {
    width: 60,
    height: 60,
    borderRadius: 30,
    backgroundColor: 'red',
    position: 'absolute',
    marginStart: -30,
    marginTop: -30,
  },
});

const App = () => {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <SafeAreaView style={{ flex: 1, backgroundColor: 'black' }}>
        <ImageViewer />
      </SafeAreaView>
    </GestureHandlerRootView>
  );
};

export default App;
Read more comments on GitHub >

github_iconTop Results From Across the Web

focalX and focalY are wrong on android only · software-mansion ...
Declarative API exposing platform native touch and gesture system to React Native. - focalX and focalY are wrong on android only ...
Read more >
How to fix pinch zoom focal point in a custom view?
So the custom view in my test app only supports scrolling: ... And while the zoom changes correctly, the focal point is wrong...
Read more >
Can you no longer manually edit the focus on already-taken ...
Edit Photos and Videos,Android ... I have just figured out that you can only adjust the focal point of portrait mode pictures that...
Read more >
CameraCharacteristics - Android Developers
List of focal lengths for android.lens. ... If no hotpixel map output is available for this camera device, this will contain only false...
Read more >
Focal theme changelog - Shopify Themes - Maestrooo
Focal theme changelog. This page keeps track of all changes that happened in the theme so far. Please note that, per our update...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found