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

Rotating image in v10 #485

Closed
Goues opened this issue May 30, 2022 · 7 comments
Closed

Rotating image in v10 #485

Goues opened this issue May 30, 2022 · 7 comments

Comments

@Goues
Copy link

Goues commented May 30, 2022

Hey,

I am trying to upgrade to v10 and I like the simplification of rendering the image on my own. However, I don't think the rotation can be applied as proposed by the example. I took the example into a codesandbox to demonstrate: https://codesandbox.io/s/quizzical-shape-6sphb4?file=/src/App.js

It is very simple, there is a button to rotate the image by 90 degrees whenever you click on it.

Expected behaviour: When an image is rotated via transformation, it's displayed as "contain", so that the entire rotate image is still visible. A user can only select a crop within the rotated image.

Actual behaviour: The image is rotated around its center and the edges on the longer side of the image become invisible. But most importantly, it doesn't detect any overflow and if you select the visible portion of the image, the crop is calculated as if I was making a crop against the whole area. This makes CSS rotation totally unviable because it also means I can now make the crop outside of the rotated image (you can try rotating once in the sandbox and then selecting a crop "above" the image.

I've also checked the Advanced example here https://codesandbox.io/s/react-image-crop-demo-with-react-hooks-y831o and the same issue is reproducible. As soon as the image is rotated, parts of it become invisible and a crop can be selected outside of the image.

My feeling is that the release notes should instead point out that rotation is no longer possible (even if you have no rotate button, if you start with a rotated image, you get wrong behaviour), or go into more detail about how to do that. The v9 implementation was far from fully working[1], but saying it's as easy as adding CSS transformation in the changelog brings up false hopes.

Thanks


[1] in v9, it wasn't possible to select a crop by mouse after rotation, but if it gets created programatically, it can then be dragged and resized.

@sekoyo
Copy link
Owner

sekoyo commented Jun 1, 2022

Hi do you have any examples of tools that have this behaviour? A little hard to visualize how you would keep the image in bounds while rotating

@Goues
Copy link
Author

Goues commented Jun 1, 2022

No, I don't, this library is the best I found.

I'm only commenting on the fact than in v9, when I used rotate prop, it would not allow to make a crop outside of the image. I did apply rotation to the image using CSS + made the image fit the area, but for some reason, this library would pick up on it and limit the crop to the rotated image.

I can as well apply the rotation + make the image fit the area even in v10, but it then allows to crop outside of the image.

I have a video here of v9 in action:

Screen.Recording.2022-06-01.at.15.54.27.mov

Basically, when I rotate the image and shrink it, the cropping is a bit buggy, but stays in the bounds of the image.

@sekoyo
Copy link
Owner

sekoyo commented Jun 1, 2022

Ah I see, I'm not sure how the behaviour is different as it is also doing a CSS transform - https://github.com/DominicTobias/react-image-crop/blob/9.1.1/src/ReactCrop.tsx#L1090

However there was also a spin prop which had some other behavior, were you using that?

@Goues
Copy link
Author

Goues commented Jun 1, 2022

Oh, yes, I use the spin as well. I've spent many hours trying to find a combination that would be at least usable I think it would have been better to just help submitting PRs 😅

So, my cleaned up code is:

    <ReactCrop
        src={src}
        unit="%"
        crop={crop}
        onChange={(_, crop) => setCrop(crop)}
        style={{ transform: `rotate(${rotation}deg)` }
        imageStyle={{ maxWidth, maxHeight }}
        spin={((rotation + 180) % 360) - 180}
    >

I was using the rotate props at some point in the past as well, but using just spin caused less issues. So the spin is what made it work within the bounds of the rotated image, right?

I know you also recommend using a different tool, if I have such complex use-case, but it would be nice to find a way to support it anyway.

@ma-anwar
Copy link

ma-anwar commented Jun 16, 2023

Hey @Goues any suggestions on alternate libraries that handle this case well?

I'm running into the same problem.

@ma-anwar
Copy link

ma-anwar commented Jun 16, 2023

I actually found a decent workaround to this problem in my case which is to load the image into React Crop after it has already been rotated. This way React Crop will have the correct dimensions to show the image.

The code is as below, the relevant portions are the canvas manipulations.
Rotation is bounded between 0 and 360. In my application, I don't let the user rotate the image while cropping (they rotate it before). If you want to let them rotate while cropping then you may need to do the canvas manipulations ad hoc and then re-render the cropper.

You can just set the src of the image to the URL generated by below code (along with additional logic to handle state where URL is not yet created etc)

    const getImage = async () => {
        const response = await axios.get(uncroppedMedia?.url, {
            responseType: "blob",
            withCredentials: false,
        });
        const blob = response.data;
        const image = await createImageBitmap(blob);
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");

        // Dimensions of canvas need to take rotation into account
        const rotatedWidth =
            Math.abs(Math.cos((rotation * Math.PI) / 180) * image.width) +
            Math.abs(Math.sin((rotation * Math.PI) / 180) * image.height);
        const rotatedHeight =
            Math.abs(Math.sin((rotation * Math.PI) / 180) * image.width) +
            Math.abs(Math.cos((rotation * Math.PI) / 180) * image.height);

        canvas.width = rotatedWidth;
        canvas.height = rotatedHeight;

        ctx.translate(rotatedWidth / 2, rotatedHeight / 2);
        ctx.rotate((rotation * Math.PI) / 180);
        ctx.translate(-image.width / 2, -image.height / 2);

        ctx.drawImage(image, 0, 0);
        return new Promise((resolve) => {
            canvas.toBlob((blob) => {
                resolve(blob);
            }, "image/jpeg");
        });
    };

    useEffect(() => {
        if (!show) return;

        getImage().then((blob) => {
            setBlob(URL.createObjectURL(blob as Blob));
        });
    }, [rotation, show]);

@Goues
Copy link
Author

Goues commented Nov 18, 2023

I did switch into Canvas as well, Konva JS to be more specific, because I use other filters (flipping, rotating, brightness, saturation, hue rotation, even sepia) and it made more sense in the long run because Canvas is more powerful in the long run. I also did the rotation of the crop using a custom function, but since my contsraints are:

  • only 90 deg clockwise rotation (if you want more, you can do it multiple times)
  • only using percent crop (to make it responsive)

the method ended up as simple as

export function rotateCrop(crop?: PercentCrop): PercentCrop | undefined {
  if (!crop) return crop

  const { x, y, width, height, unit } = crop
  return {
    y: x,
    x: 100 - (y + height),
    width: height,
    height: width,
    unit,
  }
}

I managed to upgrade to v10 after doing this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants