|
|
import { type Point } from "points-on-curve";
|
|
|
import {
|
|
|
type Radians,
|
|
|
pointFrom,
|
|
|
pointCenter,
|
|
|
pointRotateRads,
|
|
|
vectorFromPoint,
|
|
|
vectorNormalize,
|
|
|
vectorSubtract,
|
|
|
vectorAdd,
|
|
|
vectorScale,
|
|
|
pointFromVector,
|
|
|
clamp,
|
|
|
isCloseTo,
|
|
|
} from "../../math";
|
|
|
import type { TransformHandleType } from "./transformHandles";
|
|
|
import type {
|
|
|
ElementsMap,
|
|
|
ExcalidrawElement,
|
|
|
ExcalidrawImageElement,
|
|
|
ImageCrop,
|
|
|
NonDeleted,
|
|
|
} from "./types";
|
|
|
import {
|
|
|
getElementAbsoluteCoords,
|
|
|
getResizedElementAbsoluteCoords,
|
|
|
} from "./bounds";
|
|
|
|
|
|
export const MINIMAL_CROP_SIZE = 10;
|
|
|
|
|
|
export const cropElement = (
|
|
|
element: ExcalidrawImageElement,
|
|
|
transformHandle: TransformHandleType,
|
|
|
naturalWidth: number,
|
|
|
naturalHeight: number,
|
|
|
pointerX: number,
|
|
|
pointerY: number,
|
|
|
widthAspectRatio?: number,
|
|
|
) => {
|
|
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
|
|
getUncroppedWidthAndHeight(element);
|
|
|
|
|
|
const naturalWidthToUncropped = naturalWidth / uncroppedWidth;
|
|
|
const naturalHeightToUncropped = naturalHeight / uncroppedHeight;
|
|
|
|
|
|
const croppedLeft = (element.crop?.x ?? 0) / naturalWidthToUncropped;
|
|
|
const croppedTop = (element.crop?.y ?? 0) / naturalHeightToUncropped;
|
|
|
|
|
|
/**
|
|
|
* uncropped width
|
|
|
* *––––––––––––––––––––––––*
|
|
|
* | (x,y) (natural) |
|
|
|
* | *–––––––* |
|
|
|
* | |///////| height | uncropped height
|
|
|
* | *–––––––* |
|
|
|
* | width (natural) |
|
|
|
* *––––––––––––––––––––––––*
|
|
|
*/
|
|
|
|
|
|
const rotatedPointer = pointRotateRads(
|
|
|
pointFrom(pointerX, pointerY),
|
|
|
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
|
|
-element.angle as Radians,
|
|
|
);
|
|
|
|
|
|
pointerX = rotatedPointer[0];
|
|
|
pointerY = rotatedPointer[1];
|
|
|
|
|
|
let nextWidth = element.width;
|
|
|
let nextHeight = element.height;
|
|
|
|
|
|
let crop: ImageCrop | null = element.crop ?? {
|
|
|
x: 0,
|
|
|
y: 0,
|
|
|
width: naturalWidth,
|
|
|
height: naturalHeight,
|
|
|
naturalWidth,
|
|
|
naturalHeight,
|
|
|
};
|
|
|
|
|
|
const previousCropHeight = crop.height;
|
|
|
const previousCropWidth = crop.width;
|
|
|
|
|
|
const isFlippedByX = element.scale[0] === -1;
|
|
|
const isFlippedByY = element.scale[1] === -1;
|
|
|
|
|
|
let changeInHeight = pointerY - element.y;
|
|
|
let changeInWidth = pointerX - element.x;
|
|
|
|
|
|
if (transformHandle.includes("n")) {
|
|
|
nextHeight = clamp(
|
|
|
element.height - changeInHeight,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
isFlippedByY ? uncroppedHeight - croppedTop : element.height + croppedTop,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (transformHandle.includes("s")) {
|
|
|
changeInHeight = pointerY - element.y - element.height;
|
|
|
nextHeight = clamp(
|
|
|
element.height + changeInHeight,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
isFlippedByY ? element.height + croppedTop : uncroppedHeight - croppedTop,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (transformHandle.includes("e")) {
|
|
|
changeInWidth = pointerX - element.x - element.width;
|
|
|
|
|
|
nextWidth = clamp(
|
|
|
element.width + changeInWidth,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
isFlippedByX ? element.width + croppedLeft : uncroppedWidth - croppedLeft,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (transformHandle.includes("w")) {
|
|
|
nextWidth = clamp(
|
|
|
element.width - changeInWidth,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
isFlippedByX ? uncroppedWidth - croppedLeft : element.width + croppedLeft,
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const updateCropWidthAndHeight = (crop: ImageCrop) => {
|
|
|
crop.height = nextHeight * naturalHeightToUncropped;
|
|
|
crop.width = nextWidth * naturalWidthToUncropped;
|
|
|
};
|
|
|
|
|
|
updateCropWidthAndHeight(crop);
|
|
|
|
|
|
const adjustFlipForHandle = (
|
|
|
handle: TransformHandleType,
|
|
|
crop: ImageCrop,
|
|
|
) => {
|
|
|
updateCropWidthAndHeight(crop);
|
|
|
if (handle.includes("n")) {
|
|
|
if (!isFlippedByY) {
|
|
|
crop.y += previousCropHeight - crop.height;
|
|
|
}
|
|
|
}
|
|
|
if (handle.includes("s")) {
|
|
|
if (isFlippedByY) {
|
|
|
crop.y += previousCropHeight - crop.height;
|
|
|
}
|
|
|
}
|
|
|
if (handle.includes("e")) {
|
|
|
if (isFlippedByX) {
|
|
|
crop.x += previousCropWidth - crop.width;
|
|
|
}
|
|
|
}
|
|
|
if (handle.includes("w")) {
|
|
|
if (!isFlippedByX) {
|
|
|
crop.x += previousCropWidth - crop.width;
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
switch (transformHandle) {
|
|
|
case "n": {
|
|
|
if (widthAspectRatio) {
|
|
|
const distanceToLeft = croppedLeft + element.width / 2;
|
|
|
const distanceToRight =
|
|
|
uncroppedWidth - croppedLeft - element.width / 2;
|
|
|
|
|
|
const MAX_WIDTH = Math.min(distanceToLeft, distanceToRight) * 2;
|
|
|
|
|
|
nextWidth = clamp(
|
|
|
nextHeight * widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_WIDTH,
|
|
|
);
|
|
|
nextHeight = nextWidth / widthAspectRatio;
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
|
|
|
if (widthAspectRatio) {
|
|
|
crop.x += (previousCropWidth - crop.width) / 2;
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
case "s": {
|
|
|
if (widthAspectRatio) {
|
|
|
const distanceToLeft = croppedLeft + element.width / 2;
|
|
|
const distanceToRight =
|
|
|
uncroppedWidth - croppedLeft - element.width / 2;
|
|
|
|
|
|
const MAX_WIDTH = Math.min(distanceToLeft, distanceToRight) * 2;
|
|
|
|
|
|
nextWidth = clamp(
|
|
|
nextHeight * widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_WIDTH,
|
|
|
);
|
|
|
nextHeight = nextWidth / widthAspectRatio;
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
|
|
|
if (widthAspectRatio) {
|
|
|
crop.x += (previousCropWidth - crop.width) / 2;
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
case "w": {
|
|
|
if (widthAspectRatio) {
|
|
|
const distanceToTop = croppedTop + element.height / 2;
|
|
|
const distanceToBottom =
|
|
|
uncroppedHeight - croppedTop - element.height / 2;
|
|
|
|
|
|
const MAX_HEIGHT = Math.min(distanceToTop, distanceToBottom) * 2;
|
|
|
|
|
|
nextHeight = clamp(
|
|
|
nextWidth / widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_HEIGHT,
|
|
|
);
|
|
|
nextWidth = nextHeight * widthAspectRatio;
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
|
|
|
if (widthAspectRatio) {
|
|
|
crop.y += (previousCropHeight - crop.height) / 2;
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
case "e": {
|
|
|
if (widthAspectRatio) {
|
|
|
const distanceToTop = croppedTop + element.height / 2;
|
|
|
const distanceToBottom =
|
|
|
uncroppedHeight - croppedTop - element.height / 2;
|
|
|
|
|
|
const MAX_HEIGHT = Math.min(distanceToTop, distanceToBottom) * 2;
|
|
|
|
|
|
nextHeight = clamp(
|
|
|
nextWidth / widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_HEIGHT,
|
|
|
);
|
|
|
nextWidth = nextHeight * widthAspectRatio;
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
|
|
|
if (widthAspectRatio) {
|
|
|
crop.y += (previousCropHeight - crop.height) / 2;
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
case "ne": {
|
|
|
if (widthAspectRatio) {
|
|
|
if (changeInWidth > -changeInHeight) {
|
|
|
const MAX_HEIGHT = isFlippedByY
|
|
|
? uncroppedHeight - croppedTop
|
|
|
: croppedTop + element.height;
|
|
|
|
|
|
nextHeight = clamp(
|
|
|
nextWidth / widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_HEIGHT,
|
|
|
);
|
|
|
nextWidth = nextHeight * widthAspectRatio;
|
|
|
} else {
|
|
|
const MAX_WIDTH = isFlippedByX
|
|
|
? croppedLeft + element.width
|
|
|
: uncroppedWidth - croppedLeft;
|
|
|
|
|
|
nextWidth = clamp(
|
|
|
nextHeight * widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_WIDTH,
|
|
|
);
|
|
|
nextHeight = nextWidth / widthAspectRatio;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
break;
|
|
|
}
|
|
|
case "nw": {
|
|
|
if (widthAspectRatio) {
|
|
|
if (changeInWidth < changeInHeight) {
|
|
|
const MAX_HEIGHT = isFlippedByY
|
|
|
? uncroppedHeight - croppedTop
|
|
|
: croppedTop + element.height;
|
|
|
nextHeight = clamp(
|
|
|
nextWidth / widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_HEIGHT,
|
|
|
);
|
|
|
nextWidth = nextHeight * widthAspectRatio;
|
|
|
} else {
|
|
|
const MAX_WIDTH = isFlippedByX
|
|
|
? uncroppedWidth - croppedLeft
|
|
|
: croppedLeft + element.width;
|
|
|
|
|
|
nextWidth = clamp(
|
|
|
nextHeight * widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_WIDTH,
|
|
|
);
|
|
|
nextHeight = nextWidth / widthAspectRatio;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
break;
|
|
|
}
|
|
|
case "se": {
|
|
|
if (widthAspectRatio) {
|
|
|
if (changeInWidth > changeInHeight) {
|
|
|
const MAX_HEIGHT = isFlippedByY
|
|
|
? croppedTop + element.height
|
|
|
: uncroppedHeight - croppedTop;
|
|
|
|
|
|
nextHeight = clamp(
|
|
|
nextWidth / widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_HEIGHT,
|
|
|
);
|
|
|
nextWidth = nextHeight * widthAspectRatio;
|
|
|
} else {
|
|
|
const MAX_WIDTH = isFlippedByX
|
|
|
? croppedLeft + element.width
|
|
|
: uncroppedWidth - croppedLeft;
|
|
|
|
|
|
nextWidth = clamp(
|
|
|
nextHeight * widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_WIDTH,
|
|
|
);
|
|
|
nextHeight = nextWidth / widthAspectRatio;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
break;
|
|
|
}
|
|
|
case "sw": {
|
|
|
if (widthAspectRatio) {
|
|
|
if (-changeInWidth > changeInHeight) {
|
|
|
const MAX_HEIGHT = isFlippedByY
|
|
|
? croppedTop + element.height
|
|
|
: uncroppedHeight - croppedTop;
|
|
|
|
|
|
nextHeight = clamp(
|
|
|
nextWidth / widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_HEIGHT,
|
|
|
);
|
|
|
nextWidth = nextHeight * widthAspectRatio;
|
|
|
} else {
|
|
|
const MAX_WIDTH = isFlippedByX
|
|
|
? uncroppedWidth - croppedLeft
|
|
|
: croppedLeft + element.width;
|
|
|
|
|
|
nextWidth = clamp(
|
|
|
nextHeight * widthAspectRatio,
|
|
|
MINIMAL_CROP_SIZE,
|
|
|
MAX_WIDTH,
|
|
|
);
|
|
|
nextHeight = nextWidth / widthAspectRatio;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
adjustFlipForHandle(transformHandle, crop);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
const newOrigin = recomputeOrigin(
|
|
|
element,
|
|
|
transformHandle,
|
|
|
nextWidth,
|
|
|
nextHeight,
|
|
|
!!widthAspectRatio,
|
|
|
);
|
|
|
|
|
|
// reset crop to null if we're back to orig size
|
|
|
if (
|
|
|
isCloseTo(crop.width, crop.naturalWidth) &&
|
|
|
isCloseTo(crop.height, crop.naturalHeight)
|
|
|
) {
|
|
|
crop = null;
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
x: newOrigin[0],
|
|
|
y: newOrigin[1],
|
|
|
width: nextWidth,
|
|
|
height: nextHeight,
|
|
|
crop,
|
|
|
};
|
|
|
};
|
|
|
|
|
|
const recomputeOrigin = (
|
|
|
stateAtCropStart: NonDeleted<ExcalidrawElement>,
|
|
|
transformHandle: TransformHandleType,
|
|
|
width: number,
|
|
|
height: number,
|
|
|
shouldMaintainAspectRatio?: boolean,
|
|
|
) => {
|
|
|
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
|
|
|
stateAtCropStart,
|
|
|
stateAtCropStart.width,
|
|
|
stateAtCropStart.height,
|
|
|
true,
|
|
|
);
|
|
|
const startTopLeft = pointFrom(x1, y1);
|
|
|
const startBottomRight = pointFrom(x2, y2);
|
|
|
const startCenter: any = pointCenter(startTopLeft, startBottomRight);
|
|
|
|
|
|
const [newBoundsX1, newBoundsY1, newBoundsX2, newBoundsY2] =
|
|
|
getResizedElementAbsoluteCoords(stateAtCropStart, width, height, true);
|
|
|
const newBoundsWidth = newBoundsX2 - newBoundsX1;
|
|
|
const newBoundsHeight = newBoundsY2 - newBoundsY1;
|
|
|
|
|
|
// Calculate new topLeft based on fixed corner during resize
|
|
|
let newTopLeft = [...startTopLeft] as [number, number];
|
|
|
|
|
|
if (["n", "w", "nw"].includes(transformHandle)) {
|
|
|
newTopLeft = [
|
|
|
startBottomRight[0] - Math.abs(newBoundsWidth),
|
|
|
startBottomRight[1] - Math.abs(newBoundsHeight),
|
|
|
];
|
|
|
}
|
|
|
if (transformHandle === "ne") {
|
|
|
const bottomLeft = [startTopLeft[0], startBottomRight[1]];
|
|
|
newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newBoundsHeight)];
|
|
|
}
|
|
|
if (transformHandle === "sw") {
|
|
|
const topRight = [startBottomRight[0], startTopLeft[1]];
|
|
|
newTopLeft = [topRight[0] - Math.abs(newBoundsWidth), topRight[1]];
|
|
|
}
|
|
|
|
|
|
if (shouldMaintainAspectRatio) {
|
|
|
if (["s", "n"].includes(transformHandle)) {
|
|
|
newTopLeft[0] = startCenter[0] - newBoundsWidth / 2;
|
|
|
}
|
|
|
if (["e", "w"].includes(transformHandle)) {
|
|
|
newTopLeft[1] = startCenter[1] - newBoundsHeight / 2;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// adjust topLeft to new rotation point
|
|
|
const angle = stateAtCropStart.angle;
|
|
|
const rotatedTopLeft = pointRotateRads(newTopLeft, startCenter, angle);
|
|
|
const newCenter: Point = [
|
|
|
newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
|
|
|
newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
|
|
|
];
|
|
|
const rotatedNewCenter = pointRotateRads(newCenter, startCenter, angle);
|
|
|
newTopLeft = pointRotateRads(
|
|
|
rotatedTopLeft,
|
|
|
rotatedNewCenter,
|
|
|
-angle as Radians,
|
|
|
);
|
|
|
|
|
|
const newOrigin = [...newTopLeft];
|
|
|
newOrigin[0] += stateAtCropStart.x - newBoundsX1;
|
|
|
newOrigin[1] += stateAtCropStart.y - newBoundsY1;
|
|
|
|
|
|
return newOrigin;
|
|
|
};
|
|
|
|
|
|
// refer to https://link.excalidraw.com/l/6rfy1007QOo/6stx5PmRn0k
|
|
|
export const getUncroppedImageElement = (
|
|
|
element: ExcalidrawImageElement,
|
|
|
elementsMap: ElementsMap,
|
|
|
) => {
|
|
|
if (element.crop) {
|
|
|
const { width, height } = getUncroppedWidthAndHeight(element);
|
|
|
|
|
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
|
|
element,
|
|
|
elementsMap,
|
|
|
);
|
|
|
|
|
|
const topLeftVector = vectorFromPoint(
|
|
|
pointRotateRads(pointFrom(x1, y1), pointFrom(cx, cy), element.angle),
|
|
|
);
|
|
|
const topRightVector = vectorFromPoint(
|
|
|
pointRotateRads(pointFrom(x2, y1), pointFrom(cx, cy), element.angle),
|
|
|
);
|
|
|
const topEdgeNormalized = vectorNormalize(
|
|
|
vectorSubtract(topRightVector, topLeftVector),
|
|
|
);
|
|
|
const bottomLeftVector = vectorFromPoint(
|
|
|
pointRotateRads(pointFrom(x1, y2), pointFrom(cx, cy), element.angle),
|
|
|
);
|
|
|
const leftEdgeVector = vectorSubtract(bottomLeftVector, topLeftVector);
|
|
|
const leftEdgeNormalized = vectorNormalize(leftEdgeVector);
|
|
|
|
|
|
const { cropX, cropY } = adjustCropPosition(element.crop, element.scale);
|
|
|
|
|
|
const rotatedTopLeft = vectorAdd(
|
|
|
vectorAdd(
|
|
|
topLeftVector,
|
|
|
vectorScale(
|
|
|
topEdgeNormalized,
|
|
|
(-cropX * width) / element.crop.naturalWidth,
|
|
|
),
|
|
|
),
|
|
|
vectorScale(
|
|
|
leftEdgeNormalized,
|
|
|
(-cropY * height) / element.crop.naturalHeight,
|
|
|
),
|
|
|
);
|
|
|
|
|
|
const center = pointFromVector(
|
|
|
vectorAdd(
|
|
|
vectorAdd(rotatedTopLeft, vectorScale(topEdgeNormalized, width / 2)),
|
|
|
vectorScale(leftEdgeNormalized, height / 2),
|
|
|
),
|
|
|
);
|
|
|
|
|
|
const unrotatedTopLeft = pointRotateRads(
|
|
|
pointFromVector(rotatedTopLeft),
|
|
|
center,
|
|
|
-element.angle as Radians,
|
|
|
);
|
|
|
|
|
|
const uncroppedElement: ExcalidrawImageElement = {
|
|
|
...element,
|
|
|
x: unrotatedTopLeft[0],
|
|
|
y: unrotatedTopLeft[1],
|
|
|
width,
|
|
|
height,
|
|
|
crop: null,
|
|
|
};
|
|
|
|
|
|
return uncroppedElement;
|
|
|
}
|
|
|
|
|
|
return element;
|
|
|
};
|
|
|
|
|
|
export const getUncroppedWidthAndHeight = (element: ExcalidrawImageElement) => {
|
|
|
if (element.crop) {
|
|
|
const width =
|
|
|
element.width / (element.crop.width / element.crop.naturalWidth);
|
|
|
const height =
|
|
|
element.height / (element.crop.height / element.crop.naturalHeight);
|
|
|
|
|
|
return {
|
|
|
width,
|
|
|
height,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
width: element.width,
|
|
|
height: element.height,
|
|
|
};
|
|
|
};
|
|
|
|
|
|
const adjustCropPosition = (
|
|
|
crop: ImageCrop,
|
|
|
scale: ExcalidrawImageElement["scale"],
|
|
|
) => {
|
|
|
let cropX = crop.x;
|
|
|
let cropY = crop.y;
|
|
|
|
|
|
const flipX = scale[0] === -1;
|
|
|
const flipY = scale[1] === -1;
|
|
|
|
|
|
if (flipX) {
|
|
|
cropX = crop.naturalWidth - Math.abs(cropX) - crop.width;
|
|
|
}
|
|
|
|
|
|
if (flipY) {
|
|
|
cropY = crop.naturalHeight - Math.abs(cropY) - crop.height;
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
cropX,
|
|
|
cropY,
|
|
|
};
|
|
|
};
|
|
|
|
|
|
export const getFlipAdjustedCropPosition = (
|
|
|
element: ExcalidrawImageElement,
|
|
|
natural = false,
|
|
|
) => {
|
|
|
const crop = element.crop;
|
|
|
if (!crop) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
const isFlippedByX = element.scale[0] === -1;
|
|
|
const isFlippedByY = element.scale[1] === -1;
|
|
|
|
|
|
let cropX = crop.x;
|
|
|
let cropY = crop.y;
|
|
|
|
|
|
if (isFlippedByX) {
|
|
|
cropX = crop.naturalWidth - crop.width - crop.x;
|
|
|
}
|
|
|
|
|
|
if (isFlippedByY) {
|
|
|
cropY = crop.naturalHeight - crop.height - crop.y;
|
|
|
}
|
|
|
|
|
|
if (natural) {
|
|
|
return {
|
|
|
x: cropX,
|
|
|
y: cropY,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
const { width, height } = getUncroppedWidthAndHeight(element);
|
|
|
|
|
|
return {
|
|
|
x: cropX / (crop.naturalWidth / width),
|
|
|
y: cropY / (crop.naturalHeight / height),
|
|
|
};
|
|
|
};
|