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

[Bug]: CropZoom not cropping as expected #67

Open
Uros787 opened this issue Oct 9, 2024 · 8 comments
Open

[Bug]: CropZoom not cropping as expected #67

Uros787 opened this issue Oct 9, 2024 · 8 comments
Labels
bug Something isn't working

Comments

@Uros787
Copy link

Uros787 commented Oct 9, 2024

Summary

Im creating picture crop with skia and react-native-zoom-toolkit.
I've managed to get fixed 9:16 resolution with overlay and configuration.

The issue comes after user tries to crop the image if image is not fully within 9:16 resolution parameter. When its in the resolution frame it works properly and everything is fine. As seen in the video

Simulator.Screen.Recording.-.iPhone.15.-.2024-10-09.at.14.mp4

Crop function:

const crop = async (ref: React.RefObject < CropZoomType > ) => {
    const result = ref.current?.crop()
    if (result === undefined) {
        return
    }

    const actions = []
    actions.push({
        crop: result.crop
    })

    const cropResult = await manipulateAsync(localUri, actions)
    const newAssets = selectedAssets.map((asset) => {
        if (asset.id === selectedAsset.id) {
            return {
                ...asset,
                ...cropResult
            }
        }
        return asset
    })
    setSelectedAssets(newAssets)
}

Im trying to understand if this is something package related, or if this is the issue with the code i've written.
If we take the provided crop example from package, and try to zoom out with circle overlay so that picture is not fully in the circle - would it work or would it also throw this error?

Expected behaviour would be for the picture to be cut out from the sides that are out of the resolution frame

Environment

"react-native-zoom-toolkit": "^3.1.0",
"react-native": "0.74.5",
"expo": "^51.0.0",
"react-native-reanimated": "~3.10.1",
"react-native-gesture-handler": "~2.16.1"

Steps to Reproduce

No response

@Uros787 Uros787 added the bug Something isn't working label Oct 9, 2024
@Glazzes
Copy link
Owner

Glazzes commented Oct 9, 2024

Hello @Uros787, I've read your issue severeal times already and I have trouble trying yo understand what's that you want.

If you mean the image should render it such a way it fits your SVG hole, then you need to set CropZoom's cropSize property to be equals to the dimensions of your SVG hole.

If you mean like it should crop regardless or whether the images fits the frame or not, it will not happen, the way the image renders as well as how crops are achieved is dictated by cropSize property, so dynamic crops are not achievable, it's certainly easy to achieve but not in the context of this component.

@Uros787
Copy link
Author

Uros787 commented Oct 9, 2024

Hi @Glazzes
Thanks for the response.
The second paragraph is what i meant more likely.

Understandably if image is not in the highlighted overlay it should not crop it at all.
But if horizontal image is in the highlighted overlay and lets say 20 pixels of the image on both sides are out of the highlighted overlay horizontally(like on the video i provided where i zoom in a little bit), i would expect crop to still work but only cut out those 20 pixels on the sides.

If package doesnt support that as you mention, how would you recommend i approach this issue?

@Glazzes
Copy link
Owner

Glazzes commented Oct 9, 2024

i would expect crop to still work but only cut out those 20 pixels on the sides

It does work not that way, the overlay component is nothing but a visual representation of the cropping area provided by the developer, it does not play any role in the cropping calculations as these ones are dictated by cropSize, which in your case clearly differs from the dimensions used to render the overlay.

@Uros787 Althought it looks like you want a static cropping area, it is in fact a dynamic cropping area relative to zoom scale, this thing is not achievable because of CropZoom's static nature.

Recommending a proper course of action is difficult to tell... Your use case is 100% achievable but it requires some modifications to this component because of the dynamic nature of what you need, it's pretty much the same until that intermediate step in which the image is not fully covered.

I don't really know to provide guidance to deal with that, sorry.

@Uros787
Copy link
Author

Uros787 commented Oct 9, 2024

I see. Thanks a lot.

Is there a way to check if image is currently fully in the highlighted area(read allegeable for crop)?

@Glazzes
Copy link
Owner

Glazzes commented Oct 9, 2024

No, as I said before, because the overlay dimensions is not used for any type of calculations, with the current tools offered by this library you can not achieve your goal, best you can do is something like this:

recording_20241009_103046.mp4

@Glazzes
Copy link
Owner

Glazzes commented Oct 30, 2024

@Uros787 Someone recently approached me with a very similar use case to yours, I managed to provide a solution, so if you still need exactly what you wanted let me know.

@Uros787
Copy link
Author

Uros787 commented Oct 31, 2024

@Glazzes That would be great, thanks

@Glazzes
Copy link
Owner

Glazzes commented Dec 3, 2024

@Uros787 Sorry for the super late response, but Github never notifies a thing and I have to keep track of all issues by manually looking for updates from time to time, the new version of the library comes with a method that solves your problem, basically the thing goes as follows:

  • Do not use CropZoom, use ResumableZoom fo this task.
  • Make sure ResumableZoom is the same size as your desired crop area.
  • Call getVisibleRect from ResumableZoom's ref, this will grant you the visible rectangle within your desired frame.
  • Multiply the rect by the relative scale of the image resolution and the size of the image displayed on the screen.

There's a gotcha with this thing, RN pixel rounding can mess up your crop by X ammount of pixels if you're rendering an element whose width or height values containing floating point numbers, the bigger the image the more pixels lost, in a 1920x1080 resolution image it's about 5px, from my perspective is not really a noticable thing, putting all this into practice the end result will look like this:

import React, { useRef } from 'react';
import { Image, StyleSheet, View, useWindowDimensions } from 'react-native';
import {
  ResumableZoom,
  fitContainer,
  useImageResolution,
  type ResumableZoomType,
  type SizeVector,
} from 'react-native-zoom-toolkit';

const cropSize: SizeVector<number> = { width: 200, height: 400 };
const IMAGE =
  'https://media.formula1.com/image/upload/v1705423544/fom-website/2023/McLaren/Formula%201%20header%20template%20%2835%29.png';

const App = () => {
  const ref = useRef<ResumableZoomType>(null);
  const { width, height } = useWindowDimensions();
  const { isFetching, resolution } = useImageResolution({ uri: IMAGE });
  if (isFetching || resolution === undefined) {
    return null;
  }

  const imageSize = fitContainer(resolution.width / resolution.height, {
    width: cropSize.width,
    height: cropSize.height,
  });

  const crop = () => {
    const rect = ref.current?.getVisibleRect();
    if (rect === undefined) return;

    const relativeScale = resolution.width / imageSize.width;
    const originX = rect.x * relativeScale;
    const originY = rect.y * relativeScale;
    const w = Math.floor(rect.width * relativeScale);
    const h = Math.floor(rect.height * relativeScale);

    console.log(resolution, imageSize);
    console.log(originX, originY, w, h);
  };

  return (
    <View style={styles.root}>
      <ResumableZoom
        ref={ref}
        style={{ ...cropSize }}
        extendGestures={true}
        pinchCenteringMode="sync"
        onTap={crop}
      >
        <Image source={{ uri: IMAGE }} style={{ ...imageSize }} />
      </ResumableZoom>

      <View style={[{ width, height }, styles.overlay]} pointerEvents="none">
        <View style={[cropSize, styles.cropArea]} />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  root: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  overlay: {
    position: 'absolute',
    backgroundColor: '#000',
    opacity: 0.35,
    justifyContent: 'center',
    alignItems: 'center',
  },
  cropArea: {
    backgroundColor: '#fff',
    opacity: 0.5,
  },
});

export default App;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants