shift to crop with initial aspect ratio

pull/8613/head
Ryan Di 4 months ago
parent a7b55da49e
commit 467a4a2a6a

@ -10161,11 +10161,20 @@ class App extends React.Component<AppProps, AppState> {
croppingElement && croppingElement &&
isImageElement(croppingElement) isImageElement(croppingElement)
) { ) {
const croppingAtStateStart = pointerDownState.originalElements.get(
croppingElement.id,
);
const image = const image =
isInitializedImageElement(croppingElement) && isInitializedImageElement(croppingElement) &&
this.imageCache.get(croppingElement.fileId)?.image; this.imageCache.get(croppingElement.fileId)?.image;
if (image && !(image instanceof Promise)) { if (
croppingAtStateStart &&
isImageElement(croppingAtStateStart) &&
image &&
!(image instanceof Promise)
) {
mutateElement( mutateElement(
croppingElement, croppingElement,
cropElement( cropElement(
@ -10175,6 +10184,9 @@ class App extends React.Component<AppProps, AppState> {
image.naturalHeight, image.naturalHeight,
x, x,
y, y,
event.shiftKey
? croppingAtStateStart.width / croppingAtStateStart.height
: undefined,
), ),
); );

@ -12,6 +12,7 @@ import {
pointFromVector, pointFromVector,
clamp, clamp,
isCloseTo, isCloseTo,
round,
} from "../../math"; } from "../../math";
import type { TransformHandleType } from "./transformHandles"; import type { TransformHandleType } from "./transformHandles";
import type { import type {
@ -35,6 +36,7 @@ export const cropElement = (
naturalHeight: number, naturalHeight: number,
pointerX: number, pointerX: number,
pointerY: number, pointerY: number,
widthAspectRatio?: number,
) => { ) => {
const { width: uncroppedWidth, height: uncroppedHeight } = const { width: uncroppedWidth, height: uncroppedHeight } =
getUncroppedWidthAndHeight(element); getUncroppedWidthAndHeight(element);
@ -83,57 +85,296 @@ export const cropElement = (
const isFlippedByX = element.scale[0] === -1; const isFlippedByX = element.scale[0] === -1;
const isFlippedByY = element.scale[1] === -1; const isFlippedByY = element.scale[1] === -1;
let changeInHeight = pointerY - element.y;
let changeInWidth = pointerX - element.x;
if (transformHandle.includes("n")) { if (transformHandle.includes("n")) {
const pointerDeltaY = pointerY - element.y;
nextHeight = clamp( nextHeight = clamp(
element.height - pointerDeltaY, element.height - changeInHeight,
MINIMAL_CROP_SIZE, MINIMAL_CROP_SIZE,
isFlippedByY ? uncroppedHeight - croppedTop : element.height + croppedTop, isFlippedByY ? uncroppedHeight - croppedTop : element.height + croppedTop,
); );
crop.height = (nextHeight / uncroppedHeight) * naturalHeight;
if (!isFlippedByY) {
crop.y = crop.y + (previousCropHeight - crop.height);
} }
} else if (transformHandle.includes("s")) {
if (transformHandle.includes("s")) {
changeInHeight = pointerY - element.y - element.height;
nextHeight = clamp( nextHeight = clamp(
pointerY - element.y, element.height + changeInHeight,
MINIMAL_CROP_SIZE, MINIMAL_CROP_SIZE,
isFlippedByY ? element.height + croppedTop : uncroppedHeight - croppedTop, isFlippedByY ? element.height + croppedTop : uncroppedHeight - croppedTop,
); );
crop.height = (nextHeight / uncroppedHeight) * naturalHeight;
if (isFlippedByY) {
const changeInCropHeight = previousCropHeight - crop.height;
crop.y += changeInCropHeight;
} }
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")) { if (transformHandle.includes("w")) {
const pointerDeltaX = pointerX - element.x;
nextWidth = clamp( nextWidth = clamp(
element.width - pointerDeltaX, element.width - changeInWidth,
MINIMAL_CROP_SIZE, MINIMAL_CROP_SIZE,
isFlippedByX ? uncroppedWidth - croppedLeft : element.width + croppedLeft, isFlippedByX ? uncroppedWidth - croppedLeft : element.width + croppedLeft,
); );
}
const updateCropWidthAndHeight = (crop: ImageCrop) => {
crop.height = nextHeight * naturalHeightToUncropped;
crop.width = nextWidth * naturalWidthToUncropped;
};
crop.width = (nextWidth / uncroppedWidth) * naturalWidth; 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) { if (!isFlippedByX) {
crop.x += previousCropWidth - crop.width; crop.x += previousCropWidth - crop.width;
} }
} else if (transformHandle.includes("e")) { }
};
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( nextWidth = clamp(
pointerX - element.x, nextHeight * widthAspectRatio,
MINIMAL_CROP_SIZE, MINIMAL_CROP_SIZE,
isFlippedByX ? element.width + croppedLeft : uncroppedWidth - croppedLeft, MAX_WIDTH,
); );
crop.width = nextWidth * naturalWidthToUncropped; nextHeight = nextWidth / widthAspectRatio;
if (isFlippedByX) {
const changeInCropWidth = previousCropWidth - crop.width;
crop.x += changeInCropWidth;
} }
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( const newOrigin = recomputeOrigin(
@ -141,8 +382,14 @@ export const cropElement = (
transformHandle, transformHandle,
nextWidth, nextWidth,
nextHeight, nextHeight,
!!widthAspectRatio,
); );
crop.x = round(crop.x, 6);
crop.y = round(crop.y, 6);
crop.width = round(crop.width, 6);
crop.height = round(crop.height, 6);
// reset crop to null if we're back to orig size // reset crop to null if we're back to orig size
if ( if (
isCloseTo(crop.width, crop.naturalWidth) && isCloseTo(crop.width, crop.naturalWidth) &&
@ -165,6 +412,7 @@ const recomputeOrigin = (
transformHandle: TransformHandleType, transformHandle: TransformHandleType,
width: number, width: number,
height: number, height: number,
shouldMaintainAspectRatio?: boolean,
) => { ) => {
const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords( const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
stateAtCropStart, stateAtCropStart,
@ -199,6 +447,15 @@ const recomputeOrigin = (
newTopLeft = [topRight[0] - Math.abs(newBoundsWidth), topRight[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 // adjust topLeft to new rotation point
const angle = stateAtCropStart.angle; const angle = stateAtCropStart.angle;
const rotatedTopLeft = pointRotateRads(newTopLeft, startCenter, angle); const rotatedTopLeft = pointRotateRads(newTopLeft, startCenter, angle);

@ -781,7 +781,7 @@ const _renderInteractiveScene = ({
} }
// Paint selection element // Paint selection element
if (appState.selectionElement) { if (appState.selectionElement && !appState.isCropping) {
try { try {
renderSelectionElement( renderSelectionElement(
appState.selectionElement, appState.selectionElement,

Loading…
Cancel
Save