|
|
@ -32,19 +32,14 @@ import { isInitializedImageElement } from "./typeChecks";
|
|
|
|
|
|
|
|
|
|
|
|
const _cropElement = (
|
|
|
|
const _cropElement = (
|
|
|
|
element: ExcalidrawImageElement,
|
|
|
|
element: ExcalidrawImageElement,
|
|
|
|
image: HTMLImageElement,
|
|
|
|
|
|
|
|
transformHandle: TransformHandleType,
|
|
|
|
transformHandle: TransformHandleType,
|
|
|
|
naturalWidth: number,
|
|
|
|
naturalWidth: number,
|
|
|
|
naturalHeight: number,
|
|
|
|
naturalHeight: number,
|
|
|
|
pointerX: number,
|
|
|
|
pointerX: number,
|
|
|
|
pointerY: number,
|
|
|
|
pointerY: number,
|
|
|
|
) => {
|
|
|
|
) => {
|
|
|
|
const uncroppedWidth =
|
|
|
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
|
|
|
element.width /
|
|
|
|
getUncroppedWidthAndHeight(element);
|
|
|
|
(element.crop ? element.crop.width / image.naturalWidth : 1);
|
|
|
|
|
|
|
|
const uncroppedHeight =
|
|
|
|
|
|
|
|
element.height /
|
|
|
|
|
|
|
|
(element.crop ? element.crop.height / image.naturalHeight : 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const naturalWidthToUncropped = naturalWidth / uncroppedWidth;
|
|
|
|
const naturalWidthToUncropped = naturalWidth / uncroppedWidth;
|
|
|
|
const naturalHeightToUncropped = naturalHeight / uncroppedHeight;
|
|
|
|
const naturalHeightToUncropped = naturalHeight / uncroppedHeight;
|
|
|
@ -53,7 +48,7 @@ const _cropElement = (
|
|
|
|
const croppedTop = (element.crop?.y ?? 0) / naturalHeightToUncropped;
|
|
|
|
const croppedTop = (element.crop?.y ?? 0) / naturalHeightToUncropped;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* uncropped width
|
|
|
|
* uncropped width
|
|
|
|
* *––––––––––––––––––––––––*
|
|
|
|
* *––––––––––––––––––––––––*
|
|
|
|
* | (x,y) (natural) |
|
|
|
|
* | (x,y) (natural) |
|
|
|
|
* | *–––––––* |
|
|
|
|
* | *–––––––* |
|
|
|
@ -79,6 +74,7 @@ const _cropElement = (
|
|
|
|
y: 0,
|
|
|
|
y: 0,
|
|
|
|
width: naturalWidth,
|
|
|
|
width: naturalWidth,
|
|
|
|
height: naturalHeight,
|
|
|
|
height: naturalHeight,
|
|
|
|
|
|
|
|
naturalDimension: [naturalWidth, naturalHeight],
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const previousCropHeight = crop.height;
|
|
|
|
const previousCropHeight = crop.height;
|
|
|
@ -168,25 +164,24 @@ export const cropElement = (
|
|
|
|
isInitializedImageElement(element) && imageCache.get(element.fileId)?.image;
|
|
|
|
isInitializedImageElement(element) && imageCache.get(element.fileId)?.image;
|
|
|
|
|
|
|
|
|
|
|
|
if (image && !(image instanceof Promise)) {
|
|
|
|
if (image && !(image instanceof Promise)) {
|
|
|
|
const mutation = _cropElement(
|
|
|
|
mutateElement(
|
|
|
|
element,
|
|
|
|
element,
|
|
|
|
image,
|
|
|
|
_cropElement(
|
|
|
|
transformHandle,
|
|
|
|
element,
|
|
|
|
image.naturalWidth,
|
|
|
|
transformHandle,
|
|
|
|
image.naturalHeight,
|
|
|
|
image.naturalWidth,
|
|
|
|
pointerX,
|
|
|
|
image.naturalHeight,
|
|
|
|
pointerY,
|
|
|
|
pointerX,
|
|
|
|
|
|
|
|
pointerY,
|
|
|
|
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
mutateElement(element, mutation);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
updateBoundElements(element, elementsMap, {
|
|
|
|
updateBoundElements(element, elementsMap, {
|
|
|
|
oldSize: { width: element.width, height: element.height },
|
|
|
|
oldSize: { width: element.width, height: element.height },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: replace with the refactored resizeSingleElement
|
|
|
|
|
|
|
|
const recomputeOrigin = (
|
|
|
|
const recomputeOrigin = (
|
|
|
|
stateAtCropStart: NonDeleted<ExcalidrawElement>,
|
|
|
|
stateAtCropStart: NonDeleted<ExcalidrawElement>,
|
|
|
|
transformHandle: TransformHandleType,
|
|
|
|
transformHandle: TransformHandleType,
|
|
|
@ -250,87 +245,96 @@ const recomputeOrigin = (
|
|
|
|
export const getUncroppedImageElement = (
|
|
|
|
export const getUncroppedImageElement = (
|
|
|
|
element: ExcalidrawImageElement,
|
|
|
|
element: ExcalidrawImageElement,
|
|
|
|
elementsMap: ElementsMap,
|
|
|
|
elementsMap: ElementsMap,
|
|
|
|
imageCache: AppClassProperties["imageCache"],
|
|
|
|
|
|
|
|
) => {
|
|
|
|
) => {
|
|
|
|
const image =
|
|
|
|
if (element.crop) {
|
|
|
|
isInitializedImageElement(element) && imageCache.get(element.fileId)?.image;
|
|
|
|
const { width, height } = getUncroppedWidthAndHeight(element);
|
|
|
|
|
|
|
|
|
|
|
|
if (image && !(image instanceof Promise)) {
|
|
|
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
|
|
|
if (element.crop) {
|
|
|
|
element,
|
|
|
|
const width = element.width / (element.crop.width / image.naturalWidth);
|
|
|
|
elementsMap,
|
|
|
|
const height =
|
|
|
|
);
|
|
|
|
element.height / (element.crop.height / image.naturalHeight);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
|
|
|
|
const topLeftVector = vectorFromPoint(
|
|
|
|
element,
|
|
|
|
pointRotateRads(pointFrom(x1, y1), pointFrom(cx, cy), element.angle),
|
|
|
|
elementsMap,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
const topRightVector = vectorFromPoint(
|
|
|
|
|
|
|
|
pointRotateRads(pointFrom(x2, y1), pointFrom(cx, cy), element.angle),
|
|
|
|
const topLeftVector = vectorFromPoint(
|
|
|
|
);
|
|
|
|
pointRotateRads(pointFrom(x1, y1), pointFrom(cx, cy), element.angle),
|
|
|
|
const topEdgeNormalized = vectorNormalize(
|
|
|
|
);
|
|
|
|
vectorSubtract(topRightVector, topLeftVector),
|
|
|
|
const topRightVector = vectorFromPoint(
|
|
|
|
);
|
|
|
|
pointRotateRads(pointFrom(x2, y1), pointFrom(cx, cy), element.angle),
|
|
|
|
const bottomLeftVector = vectorFromPoint(
|
|
|
|
);
|
|
|
|
pointRotateRads(pointFrom(x1, y2), pointFrom(cx, cy), element.angle),
|
|
|
|
const topEdgeNormalized = vectorNormalize(
|
|
|
|
);
|
|
|
|
vectorSubtract(topRightVector, topLeftVector),
|
|
|
|
const leftEdgeVector = vectorSubtract(bottomLeftVector, topLeftVector);
|
|
|
|
);
|
|
|
|
const leftEdgeNormalized = vectorNormalize(leftEdgeVector);
|
|
|
|
const bottomLeftVector = vectorFromPoint(
|
|
|
|
|
|
|
|
pointRotateRads(pointFrom(x1, y2), pointFrom(cx, cy), element.angle),
|
|
|
|
const { cropX, cropY } = adjustCropPosition(element.crop, element.scale);
|
|
|
|
);
|
|
|
|
|
|
|
|
const leftEdgeVector = vectorSubtract(bottomLeftVector, topLeftVector);
|
|
|
|
const rotatedTopLeft = vectorAdd(
|
|
|
|
const leftEdgeNormalized = vectorNormalize(leftEdgeVector);
|
|
|
|
vectorAdd(
|
|
|
|
|
|
|
|
topLeftVector,
|
|
|
|
const { cropX, cropY } = adjustCropPosition(
|
|
|
|
|
|
|
|
element.crop,
|
|
|
|
|
|
|
|
element.scale,
|
|
|
|
|
|
|
|
image,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const rotatedTopLeft = vectorAdd(
|
|
|
|
|
|
|
|
vectorAdd(
|
|
|
|
|
|
|
|
topLeftVector,
|
|
|
|
|
|
|
|
vectorScale(topEdgeNormalized, (-cropX * width) / image.naturalWidth),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
vectorScale(
|
|
|
|
vectorScale(
|
|
|
|
leftEdgeNormalized,
|
|
|
|
topEdgeNormalized,
|
|
|
|
(-cropY * height) / image.naturalHeight,
|
|
|
|
(-cropX * width) / element.crop.naturalDimension[0],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
),
|
|
|
|
|
|
|
|
vectorScale(
|
|
|
|
|
|
|
|
leftEdgeNormalized,
|
|
|
|
|
|
|
|
(-cropY * height) / element.crop.naturalDimension[1],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const center = pointFromVector(
|
|
|
|
const center = pointFromVector(
|
|
|
|
vectorAdd(
|
|
|
|
vectorAdd(
|
|
|
|
vectorAdd(rotatedTopLeft, vectorScale(topEdgeNormalized, width / 2)),
|
|
|
|
vectorAdd(rotatedTopLeft, vectorScale(topEdgeNormalized, width / 2)),
|
|
|
|
vectorScale(leftEdgeNormalized, height / 2),
|
|
|
|
vectorScale(leftEdgeNormalized, height / 2),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const unrotatedTopLeft = pointRotateRads(
|
|
|
|
const unrotatedTopLeft = pointRotateRads(
|
|
|
|
pointFromVector(rotatedTopLeft),
|
|
|
|
pointFromVector(rotatedTopLeft),
|
|
|
|
center,
|
|
|
|
center,
|
|
|
|
-element.angle as Radians,
|
|
|
|
-element.angle as Radians,
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const uncroppedElement: ExcalidrawImageElement = {
|
|
|
|
const uncroppedElement: ExcalidrawImageElement = {
|
|
|
|
...element,
|
|
|
|
...element,
|
|
|
|
x: unrotatedTopLeft[0],
|
|
|
|
x: unrotatedTopLeft[0],
|
|
|
|
y: unrotatedTopLeft[1],
|
|
|
|
y: unrotatedTopLeft[1],
|
|
|
|
width,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
height,
|
|
|
|
crop: null,
|
|
|
|
crop: null,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return uncroppedElement;
|
|
|
|
return uncroppedElement;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return element;
|
|
|
|
return element;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const getUncroppedWidthAndHeight = (element: ExcalidrawImageElement) => {
|
|
|
|
|
|
|
|
if (element.crop) {
|
|
|
|
|
|
|
|
const width =
|
|
|
|
|
|
|
|
element.width / (element.crop.width / element.crop.naturalDimension[0]);
|
|
|
|
|
|
|
|
const height =
|
|
|
|
|
|
|
|
element.height / (element.crop.height / element.crop.naturalDimension[1]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
width,
|
|
|
|
|
|
|
|
height,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
width: element.width,
|
|
|
|
|
|
|
|
height: element.height,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const adjustCropPosition = (
|
|
|
|
const adjustCropPosition = (
|
|
|
|
crop: ImageCrop,
|
|
|
|
crop: ImageCrop,
|
|
|
|
scale: ExcalidrawImageElement["scale"],
|
|
|
|
scale: ExcalidrawImageElement["scale"],
|
|
|
|
image: HTMLImageElement,
|
|
|
|
|
|
|
|
) => {
|
|
|
|
) => {
|
|
|
|
let cropX = crop.x;
|
|
|
|
let cropX = crop.x;
|
|
|
|
let cropY = crop.y;
|
|
|
|
let cropY = crop.y;
|
|
|
@ -339,11 +343,11 @@ const adjustCropPosition = (
|
|
|
|
const flipY = scale[1] === -1;
|
|
|
|
const flipY = scale[1] === -1;
|
|
|
|
|
|
|
|
|
|
|
|
if (flipX) {
|
|
|
|
if (flipX) {
|
|
|
|
cropX = image.naturalWidth - Math.abs(cropX) - crop.width;
|
|
|
|
cropX = crop.naturalDimension[0] - Math.abs(cropX) - crop.width;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (flipY) {
|
|
|
|
if (flipY) {
|
|
|
|
cropY = image.naturalHeight - Math.abs(cropY) - crop.height;
|
|
|
|
cropY = crop.naturalDimension[1] - Math.abs(cropY) - crop.height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|