make cropping work with flipping
This commit is contained in:
parent
bccd2bf30d
commit
3a01122093
@ -7842,12 +7842,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
const nextCrop = {
|
const nextCrop = {
|
||||||
...crop,
|
...crop,
|
||||||
x: clamp(
|
x: clamp(
|
||||||
crop.x - instantDragOffset.x,
|
crop.x -
|
||||||
|
instantDragOffset.x * Math.sign(croppingElement.scale[0]),
|
||||||
0,
|
0,
|
||||||
image.naturalWidth - crop.width,
|
image.naturalWidth - crop.width,
|
||||||
),
|
),
|
||||||
y: clamp(
|
y: clamp(
|
||||||
crop.y - instantDragOffset.y,
|
crop.y -
|
||||||
|
instantDragOffset.y * Math.sign(croppingElement.scale[1]),
|
||||||
0,
|
0,
|
||||||
image.naturalHeight - crop.height,
|
image.naturalHeight - crop.height,
|
||||||
),
|
),
|
||||||
|
@ -19,6 +19,7 @@ import type {
|
|||||||
ElementsMap,
|
ElementsMap,
|
||||||
ExcalidrawElement,
|
ExcalidrawElement,
|
||||||
ExcalidrawImageElement,
|
ExcalidrawImageElement,
|
||||||
|
ImageCrop,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
NonDeletedSceneElementsMap,
|
NonDeletedSceneElementsMap,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@ -62,9 +63,6 @@ const _cropElement = (
|
|||||||
* *––––––––––––––––––––––––*
|
* *––––––––––––––––––––––––*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const availableTopCropSpace = croppedTop;
|
|
||||||
const availableLeftCropSpace = croppedLeft;
|
|
||||||
|
|
||||||
const rotatedPointer = pointRotateRads(
|
const rotatedPointer = pointRotateRads(
|
||||||
point(pointerX, pointerY),
|
point(pointerX, pointerY),
|
||||||
point(element.x + element.width / 2, element.y + element.height / 2),
|
point(element.x + element.width / 2, element.y + element.height / 2),
|
||||||
@ -83,50 +81,63 @@ const _cropElement = (
|
|||||||
height: naturalHeight,
|
height: naturalHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const previousCropHeight = crop.height;
|
||||||
|
const previousCropWidth = crop.width;
|
||||||
|
|
||||||
|
const isFlippedByX = element.scale[0] === -1;
|
||||||
|
const isFlippedByY = element.scale[1] === -1;
|
||||||
|
|
||||||
if (transformHandle.includes("n")) {
|
if (transformHandle.includes("n")) {
|
||||||
const northBound = element.y - availableTopCropSpace;
|
|
||||||
const southBound = element.y + element.height;
|
|
||||||
|
|
||||||
pointerY = clamp(pointerY, northBound, southBound);
|
|
||||||
|
|
||||||
const pointerDeltaY = pointerY - element.y;
|
const pointerDeltaY = pointerY - element.y;
|
||||||
nextHeight = Math.max(element.height - pointerDeltaY, 1);
|
nextHeight = clamp(
|
||||||
|
element.height - pointerDeltaY,
|
||||||
crop.y = ((pointerDeltaY + croppedTop) / uncroppedHeight) * naturalHeight;
|
1,
|
||||||
|
isFlippedByY ? uncroppedHeight - croppedTop : element.height + croppedTop,
|
||||||
|
);
|
||||||
crop.height = (nextHeight / uncroppedHeight) * naturalHeight;
|
crop.height = (nextHeight / uncroppedHeight) * naturalHeight;
|
||||||
}
|
|
||||||
|
|
||||||
if (transformHandle.includes("s")) {
|
if (!isFlippedByY) {
|
||||||
const northBound = element.y;
|
crop.y = crop.y + (previousCropHeight - crop.height);
|
||||||
const southBound = element.y + (uncroppedHeight - croppedTop);
|
}
|
||||||
|
} else if (transformHandle.includes("s")) {
|
||||||
pointerY = clamp(pointerY, northBound, southBound);
|
nextHeight = clamp(
|
||||||
|
pointerY - element.y,
|
||||||
nextHeight = Math.max(pointerY - element.y, 1);
|
1,
|
||||||
|
isFlippedByY ? element.height + croppedTop : uncroppedHeight - croppedTop,
|
||||||
|
);
|
||||||
crop.height = (nextHeight / uncroppedHeight) * naturalHeight;
|
crop.height = (nextHeight / uncroppedHeight) * naturalHeight;
|
||||||
|
|
||||||
|
if (isFlippedByY) {
|
||||||
|
const changeInCropHeight = previousCropHeight - crop.height;
|
||||||
|
crop.y += changeInCropHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transformHandle.includes("w")) {
|
if (transformHandle.includes("w")) {
|
||||||
const eastBound = element.x + element.width;
|
|
||||||
const westBound = element.x - availableLeftCropSpace;
|
|
||||||
|
|
||||||
pointerX = clamp(pointerX, westBound, eastBound);
|
|
||||||
|
|
||||||
const pointerDeltaX = pointerX - element.x;
|
const pointerDeltaX = pointerX - element.x;
|
||||||
nextWidth = Math.max(element.width - pointerDeltaX, 1);
|
|
||||||
|
|
||||||
crop.x = ((pointerDeltaX + croppedLeft) / uncroppedWidth) * naturalWidth;
|
nextWidth = clamp(
|
||||||
|
element.width - pointerDeltaX,
|
||||||
|
1,
|
||||||
|
isFlippedByX ? uncroppedWidth - croppedLeft : element.width + croppedLeft,
|
||||||
|
);
|
||||||
|
|
||||||
crop.width = (nextWidth / uncroppedWidth) * naturalWidth;
|
crop.width = (nextWidth / uncroppedWidth) * naturalWidth;
|
||||||
}
|
|
||||||
|
|
||||||
if (transformHandle.includes("e")) {
|
if (!isFlippedByX) {
|
||||||
const eastBound = element.x + (uncroppedWidth - croppedLeft);
|
crop.x += previousCropWidth - crop.width;
|
||||||
const westBound = element.x;
|
}
|
||||||
|
} else if (transformHandle.includes("e")) {
|
||||||
pointerX = clamp(pointerX, westBound, eastBound);
|
nextWidth = clamp(
|
||||||
|
pointerX - element.x,
|
||||||
nextWidth = Math.max(pointerX - element.x, 1);
|
1,
|
||||||
crop.width = (nextWidth / uncroppedWidth) * naturalWidth;
|
isFlippedByX ? element.width + croppedLeft : uncroppedWidth - croppedLeft,
|
||||||
|
);
|
||||||
|
crop.width = nextWidth * naturalWidthToUncropped;
|
||||||
|
if (isFlippedByX) {
|
||||||
|
const changeInCropWidth = previousCropWidth - crop.width;
|
||||||
|
crop.x += changeInCropWidth;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newOrigin = recomputeOrigin(
|
const newOrigin = recomputeOrigin(
|
||||||
@ -270,17 +281,20 @@ export const getUncroppedImageElement = (
|
|||||||
const leftEdgeVector = vectorSubtract(bottomLeftVector, topLeftVector);
|
const leftEdgeVector = vectorSubtract(bottomLeftVector, topLeftVector);
|
||||||
const leftEdgeNormalized = vectorNormalize(leftEdgeVector);
|
const leftEdgeNormalized = vectorNormalize(leftEdgeVector);
|
||||||
|
|
||||||
|
const { cropX, cropY } = adjustCropPosition(
|
||||||
|
element.crop,
|
||||||
|
element.scale,
|
||||||
|
image,
|
||||||
|
);
|
||||||
|
|
||||||
const rotatedTopLeft = vectorAdd(
|
const rotatedTopLeft = vectorAdd(
|
||||||
vectorAdd(
|
vectorAdd(
|
||||||
topLeftVector,
|
topLeftVector,
|
||||||
vectorScale(
|
vectorScale(topEdgeNormalized, (-cropX * width) / image.naturalWidth),
|
||||||
topEdgeNormalized,
|
|
||||||
(-element.crop.x * width) / image.naturalWidth,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
vectorScale(
|
vectorScale(
|
||||||
leftEdgeNormalized,
|
leftEdgeNormalized,
|
||||||
(-element.crop.y * height) / image.naturalHeight,
|
(-cropY * height) / image.naturalHeight,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -312,3 +326,28 @@ export const getUncroppedImageElement = (
|
|||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const adjustCropPosition = (
|
||||||
|
crop: ImageCrop,
|
||||||
|
scale: ExcalidrawImageElement["scale"],
|
||||||
|
image: HTMLImageElement,
|
||||||
|
) => {
|
||||||
|
let cropX = crop.x;
|
||||||
|
let cropY = crop.y;
|
||||||
|
|
||||||
|
const flipX = scale[0] === -1;
|
||||||
|
const flipY = scale[1] === -1;
|
||||||
|
|
||||||
|
if (flipX) {
|
||||||
|
cropX = image.naturalWidth - Math.abs(cropX) - crop.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flipY) {
|
||||||
|
cropY = image.naturalHeight - Math.abs(cropY) - crop.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cropX,
|
||||||
|
cropY,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -132,6 +132,13 @@ export type IframeData =
|
|||||||
| { type: "document"; srcdoc: (theme: Theme) => string }
|
| { type: "document"; srcdoc: (theme: Theme) => string }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type ImageCrop = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
||||||
Readonly<{
|
Readonly<{
|
||||||
type: "image";
|
type: "image";
|
||||||
@ -141,12 +148,7 @@ export type ExcalidrawImageElement = _ExcalidrawElementBase &
|
|||||||
/** X and Y scale factors <-1, 1>, used for image axis flipping */
|
/** X and Y scale factors <-1, 1>, used for image axis flipping */
|
||||||
scale: [number, number];
|
scale: [number, number];
|
||||||
|
|
||||||
crop: {
|
crop: ImageCrop | null;
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
} | null;
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type InitializedExcalidrawImageElement = MarkNonNullable<
|
export type InitializedExcalidrawImageElement = MarkNonNullable<
|
||||||
|
Loading…
x
Reference in New Issue
Block a user