|
@@ -0,0 +1,139 @@
|
|
|
+import { useCallback, useState } from 'react';
|
|
|
+
|
|
|
+export const useCrop = (imageSrc) => {
|
|
|
+ const [crop, setCrop] = useState({ x: 0, y: 0 });
|
|
|
+ const [zoom, setZoom] = useState(1);
|
|
|
+ const [croppedArea, setCroppedArea] = useState({
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ width: 0,
|
|
|
+ height: 0,
|
|
|
+ });
|
|
|
+
|
|
|
+ const onCropChange = useCallback((crop) => {
|
|
|
+ setCrop(crop);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const onZoomChange = useCallback((zoom) => {
|
|
|
+ setZoom(zoom);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const onCropCompleted = useCallback((_, croppedAreaPixels) => {
|
|
|
+ setCroppedArea(croppedAreaPixels);
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const getResult = async () => {
|
|
|
+ try {
|
|
|
+ const croppedImage = await getCroppedImg(imageSrc, croppedArea);
|
|
|
+ return croppedImage;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ crop,
|
|
|
+ setCrop,
|
|
|
+ zoom,
|
|
|
+ setZoom,
|
|
|
+ onCropChange,
|
|
|
+ onZoomChange,
|
|
|
+ onCropCompleted,
|
|
|
+ croppedArea,
|
|
|
+ setCroppedArea,
|
|
|
+ getResult,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export const createImage = (url) =>
|
|
|
+ new Promise((resolve, reject) => {
|
|
|
+ const image = new Image();
|
|
|
+ image.addEventListener('load', () => resolve(image));
|
|
|
+ image.addEventListener('error', (error) => reject(error));
|
|
|
+ image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
|
|
|
+ image.src = url;
|
|
|
+ });
|
|
|
+
|
|
|
+export function getRadianAngle(degreeValue) {
|
|
|
+ return (degreeValue * Math.PI) / 180;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Returns the new bounding area of a rotated rectangle.
|
|
|
+ */
|
|
|
+export function rotateSize(width, height, rotation) {
|
|
|
+ const rotRad = getRadianAngle(rotation);
|
|
|
+
|
|
|
+ return {
|
|
|
+ width:
|
|
|
+ Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
|
|
|
+ height:
|
|
|
+ Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
|
|
|
+ */
|
|
|
+export default async function getCroppedImg(
|
|
|
+ imageSrc,
|
|
|
+ pixelCrop,
|
|
|
+ rotation = 0,
|
|
|
+ flip = { horizontal: false, vertical: false }
|
|
|
+) {
|
|
|
+ const image = await createImage(imageSrc);
|
|
|
+ const canvas = document.createElement('canvas');
|
|
|
+ const ctx = canvas.getContext('2d');
|
|
|
+
|
|
|
+ if (!ctx) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ const rotRad = getRadianAngle(rotation);
|
|
|
+
|
|
|
+ // calculate bounding box of the rotated image
|
|
|
+ const { width: bBoxWidth, height: bBoxHeight } = rotateSize(
|
|
|
+ image.width,
|
|
|
+ image.height,
|
|
|
+ rotation
|
|
|
+ );
|
|
|
+
|
|
|
+ // set canvas size to match the bounding box
|
|
|
+ canvas.width = bBoxWidth;
|
|
|
+ canvas.height = bBoxHeight;
|
|
|
+
|
|
|
+ // translate canvas context to a central location to allow rotating and flipping around the center
|
|
|
+ ctx.translate(bBoxWidth / 2, bBoxHeight / 2);
|
|
|
+ ctx.rotate(rotRad);
|
|
|
+ ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1);
|
|
|
+ ctx.translate(-image.width / 2, -image.height / 2);
|
|
|
+
|
|
|
+ // draw rotated image
|
|
|
+ ctx.drawImage(image, 0, 0);
|
|
|
+
|
|
|
+ // croppedAreaPixels values are bounding box relative
|
|
|
+ // extract the cropped image using these values
|
|
|
+ const data = ctx.getImageData(
|
|
|
+ pixelCrop.x,
|
|
|
+ pixelCrop.y,
|
|
|
+ pixelCrop.width,
|
|
|
+ pixelCrop.height
|
|
|
+ );
|
|
|
+
|
|
|
+ // set canvas width to final desired crop size - this will clear existing context
|
|
|
+ canvas.width = pixelCrop.width;
|
|
|
+ canvas.height = pixelCrop.height;
|
|
|
+
|
|
|
+ // paste generated rotate image at the top left corner
|
|
|
+ ctx.putImageData(data, 0, 0);
|
|
|
+
|
|
|
+ // As Base64 string
|
|
|
+ // return canvas.toDataURL('image/jpeg');
|
|
|
+
|
|
|
+ // As a blob
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ canvas.toBlob((file) => {
|
|
|
+ resolve(URL.createObjectURL(file));
|
|
|
+ }, 'image/jpeg');
|
|
|
+ });
|
|
|
+}
|