mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: use stats panel to crop (#8848)
* feat: use stats panel to crop * fix: test flake --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
parent
551bae07a7
commit
d99e4a23ca
6 changed files with 313 additions and 13 deletions
|
@ -5,6 +5,13 @@ import { getStepSizedValue, isPropertyEditable, resizeElement } from "./utils";
|
||||||
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
|
import { MIN_WIDTH_OR_HEIGHT } from "../../constants";
|
||||||
import type Scene from "../../scene/Scene";
|
import type Scene from "../../scene/Scene";
|
||||||
import type { AppState } from "../../types";
|
import type { AppState } from "../../types";
|
||||||
|
import { isImageElement } from "../../element/typeChecks";
|
||||||
|
import {
|
||||||
|
MINIMAL_CROP_SIZE,
|
||||||
|
getUncroppedWidthAndHeight,
|
||||||
|
} from "../../element/cropElement";
|
||||||
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
|
import { clamp, round } from "../../../math";
|
||||||
|
|
||||||
interface DimensionDragInputProps {
|
interface DimensionDragInputProps {
|
||||||
property: "width" | "height";
|
property: "width" | "height";
|
||||||
|
@ -27,6 +34,8 @@ const handleDimensionChange: DragInputCallbackType<
|
||||||
shouldChangeByStepSize,
|
shouldChangeByStepSize,
|
||||||
nextValue,
|
nextValue,
|
||||||
property,
|
property,
|
||||||
|
originalAppState,
|
||||||
|
instantChange,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const elementsMap = scene.getNonDeletedElementsMap();
|
const elementsMap = scene.getNonDeletedElementsMap();
|
||||||
|
@ -37,6 +46,107 @@ const handleDimensionChange: DragInputCallbackType<
|
||||||
shouldKeepAspectRatio || _shouldKeepAspectRatio(origElement);
|
shouldKeepAspectRatio || _shouldKeepAspectRatio(origElement);
|
||||||
const aspectRatio = origElement.width / origElement.height;
|
const aspectRatio = origElement.width / origElement.height;
|
||||||
|
|
||||||
|
if (originalAppState.croppingElementId === origElement.id) {
|
||||||
|
const element = elementsMap.get(origElement.id);
|
||||||
|
|
||||||
|
if (!element || !isImageElement(element) || !element.crop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crop = element.crop;
|
||||||
|
let nextCrop = { ...crop };
|
||||||
|
|
||||||
|
const isFlippedByX = element.scale[0] === -1;
|
||||||
|
const isFlippedByY = element.scale[1] === -1;
|
||||||
|
|
||||||
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
||||||
|
getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
const naturalToUncroppedWidthRatio = crop.naturalWidth / uncroppedWidth;
|
||||||
|
const naturalToUncroppedHeightRatio =
|
||||||
|
crop.naturalHeight / uncroppedHeight;
|
||||||
|
|
||||||
|
const MAX_POSSIBLE_WIDTH = isFlippedByX
|
||||||
|
? crop.width + crop.x
|
||||||
|
: crop.naturalWidth - crop.x;
|
||||||
|
|
||||||
|
const MAX_POSSIBLE_HEIGHT = isFlippedByY
|
||||||
|
? crop.height + crop.y
|
||||||
|
: crop.naturalHeight - crop.y;
|
||||||
|
|
||||||
|
const MIN_WIDTH = MINIMAL_CROP_SIZE * naturalToUncroppedWidthRatio;
|
||||||
|
const MIN_HEIGHT = MINIMAL_CROP_SIZE * naturalToUncroppedHeightRatio;
|
||||||
|
|
||||||
|
if (nextValue !== undefined) {
|
||||||
|
if (property === "width") {
|
||||||
|
const nextValueInNatural = nextValue * naturalToUncroppedWidthRatio;
|
||||||
|
|
||||||
|
const nextCropWidth = clamp(
|
||||||
|
nextValueInNatural,
|
||||||
|
MIN_WIDTH,
|
||||||
|
MAX_POSSIBLE_WIDTH,
|
||||||
|
);
|
||||||
|
|
||||||
|
nextCrop = {
|
||||||
|
...nextCrop,
|
||||||
|
width: nextCropWidth,
|
||||||
|
x: isFlippedByX ? crop.x + crop.width - nextCropWidth : crop.x,
|
||||||
|
};
|
||||||
|
} else if (property === "height") {
|
||||||
|
const nextValueInNatural = nextValue * naturalToUncroppedHeightRatio;
|
||||||
|
const nextCropHeight = clamp(
|
||||||
|
nextValueInNatural,
|
||||||
|
MIN_HEIGHT,
|
||||||
|
MAX_POSSIBLE_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
nextCrop = {
|
||||||
|
...nextCrop,
|
||||||
|
height: nextCropHeight,
|
||||||
|
y: isFlippedByY ? crop.y + crop.height - nextCropHeight : crop.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateElement(element, {
|
||||||
|
crop: nextCrop,
|
||||||
|
width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
|
||||||
|
height: nextCrop.height / (crop.naturalHeight / uncroppedHeight),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeInWidth = property === "width" ? instantChange : 0;
|
||||||
|
const changeInHeight = property === "height" ? instantChange : 0;
|
||||||
|
|
||||||
|
const nextCropWidth = clamp(
|
||||||
|
crop.width + changeInWidth,
|
||||||
|
MIN_WIDTH,
|
||||||
|
MAX_POSSIBLE_WIDTH,
|
||||||
|
);
|
||||||
|
|
||||||
|
const nextCropHeight = clamp(
|
||||||
|
crop.height + changeInHeight,
|
||||||
|
MIN_WIDTH,
|
||||||
|
MAX_POSSIBLE_HEIGHT,
|
||||||
|
);
|
||||||
|
|
||||||
|
nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: isFlippedByX ? crop.x + crop.width - nextCropWidth : crop.x,
|
||||||
|
y: isFlippedByY ? crop.y + crop.height - nextCropHeight : crop.y,
|
||||||
|
width: nextCropWidth,
|
||||||
|
height: nextCropHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
mutateElement(element, {
|
||||||
|
crop: nextCrop,
|
||||||
|
width: nextCrop.width / (crop.naturalWidth / uncroppedWidth),
|
||||||
|
height: nextCrop.height / (crop.naturalHeight / uncroppedHeight),
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (nextValue !== undefined) {
|
if (nextValue !== undefined) {
|
||||||
const nextWidth = Math.max(
|
const nextWidth = Math.max(
|
||||||
property === "width"
|
property === "width"
|
||||||
|
@ -117,9 +227,25 @@ const DimensionDragInput = ({
|
||||||
scene,
|
scene,
|
||||||
appState,
|
appState,
|
||||||
}: DimensionDragInputProps) => {
|
}: DimensionDragInputProps) => {
|
||||||
const value =
|
let value = round(property === "width" ? element.width : element.height, 2);
|
||||||
Math.round((property === "width" ? element.width : element.height) * 100) /
|
|
||||||
100;
|
if (
|
||||||
|
appState.croppingElementId &&
|
||||||
|
appState.croppingElementId === element.id &&
|
||||||
|
isImageElement(element) &&
|
||||||
|
element.crop
|
||||||
|
) {
|
||||||
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
||||||
|
getUncroppedWidthAndHeight(element);
|
||||||
|
if (property === "width") {
|
||||||
|
const ratio = uncroppedWidth / element.crop.naturalWidth;
|
||||||
|
value = round(element.crop.width * ratio, 2);
|
||||||
|
}
|
||||||
|
if (property === "height") {
|
||||||
|
const ratio = uncroppedHeight / element.crop.naturalHeight;
|
||||||
|
value = round(element.crop.height * ratio, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragInput
|
<DragInput
|
||||||
|
|
|
@ -4,7 +4,13 @@ import type { DragInputCallbackType } from "./DragInput";
|
||||||
import { getStepSizedValue, moveElement } from "./utils";
|
import { getStepSizedValue, moveElement } from "./utils";
|
||||||
import type Scene from "../../scene/Scene";
|
import type Scene from "../../scene/Scene";
|
||||||
import type { AppState } from "../../types";
|
import type { AppState } from "../../types";
|
||||||
import { pointFrom, pointRotateRads } from "../../../math";
|
import { clamp, pointFrom, pointRotateRads, round } from "../../../math";
|
||||||
|
import { isImageElement } from "../../element/typeChecks";
|
||||||
|
import {
|
||||||
|
getFlipAdjustedCropPosition,
|
||||||
|
getUncroppedWidthAndHeight,
|
||||||
|
} from "../../element/cropElement";
|
||||||
|
import { mutateElement } from "../../element/mutateElement";
|
||||||
|
|
||||||
interface PositionProps {
|
interface PositionProps {
|
||||||
property: "x" | "y";
|
property: "x" | "y";
|
||||||
|
@ -18,12 +24,14 @@ const STEP_SIZE = 10;
|
||||||
|
|
||||||
const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
|
const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
|
||||||
accumulatedChange,
|
accumulatedChange,
|
||||||
|
instantChange,
|
||||||
originalElements,
|
originalElements,
|
||||||
originalElementsMap,
|
originalElementsMap,
|
||||||
shouldChangeByStepSize,
|
shouldChangeByStepSize,
|
||||||
nextValue,
|
nextValue,
|
||||||
property,
|
property,
|
||||||
scene,
|
scene,
|
||||||
|
originalAppState,
|
||||||
}) => {
|
}) => {
|
||||||
const elementsMap = scene.getNonDeletedElementsMap();
|
const elementsMap = scene.getNonDeletedElementsMap();
|
||||||
const elements = scene.getNonDeletedElements();
|
const elements = scene.getNonDeletedElements();
|
||||||
|
@ -38,6 +46,82 @@ const handlePositionChange: DragInputCallbackType<"x" | "y"> = ({
|
||||||
origElement.angle,
|
origElement.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (originalAppState.croppingElementId === origElement.id) {
|
||||||
|
const element = elementsMap.get(origElement.id);
|
||||||
|
|
||||||
|
if (!element || !isImageElement(element) || !element.crop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const crop = element.crop;
|
||||||
|
let nextCrop = crop;
|
||||||
|
const isFlippedByX = element.scale[0] === -1;
|
||||||
|
const isFlippedByY = element.scale[1] === -1;
|
||||||
|
const { width: uncroppedWidth, height: uncroppedHeight } =
|
||||||
|
getUncroppedWidthAndHeight(element);
|
||||||
|
|
||||||
|
if (nextValue !== undefined) {
|
||||||
|
if (property === "x") {
|
||||||
|
const nextValueInNatural =
|
||||||
|
nextValue * (crop.naturalWidth / uncroppedWidth);
|
||||||
|
|
||||||
|
if (isFlippedByX) {
|
||||||
|
nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: clamp(
|
||||||
|
crop.naturalWidth - nextValueInNatural - crop.width,
|
||||||
|
0,
|
||||||
|
crop.naturalWidth - crop.width,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: clamp(
|
||||||
|
nextValue * (crop.naturalWidth / uncroppedWidth),
|
||||||
|
0,
|
||||||
|
crop.naturalWidth - crop.width,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property === "y") {
|
||||||
|
nextCrop = {
|
||||||
|
...crop,
|
||||||
|
y: clamp(
|
||||||
|
nextValue * (crop.naturalHeight / uncroppedHeight),
|
||||||
|
0,
|
||||||
|
crop.naturalHeight - crop.height,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mutateElement(element, {
|
||||||
|
crop: nextCrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeInX =
|
||||||
|
(property === "x" ? instantChange : 0) * (isFlippedByX ? -1 : 1);
|
||||||
|
const changeInY =
|
||||||
|
(property === "y" ? instantChange : 0) * (isFlippedByY ? -1 : 1);
|
||||||
|
|
||||||
|
nextCrop = {
|
||||||
|
...crop,
|
||||||
|
x: clamp(crop.x + changeInX, 0, crop.naturalWidth - crop.width),
|
||||||
|
y: clamp(crop.y + changeInY, 0, crop.naturalHeight - crop.height),
|
||||||
|
};
|
||||||
|
|
||||||
|
mutateElement(element, {
|
||||||
|
crop: nextCrop,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (nextValue !== undefined) {
|
if (nextValue !== undefined) {
|
||||||
const newTopLeftX = property === "x" ? nextValue : topLeftX;
|
const newTopLeftX = property === "x" ? nextValue : topLeftX;
|
||||||
const newTopLeftY = property === "y" ? nextValue : topLeftY;
|
const newTopLeftY = property === "y" ? nextValue : topLeftY;
|
||||||
|
@ -97,8 +181,22 @@ const Position = ({
|
||||||
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
pointFrom(element.x + element.width / 2, element.y + element.height / 2),
|
||||||
element.angle,
|
element.angle,
|
||||||
);
|
);
|
||||||
const value =
|
let value = round(property === "x" ? topLeftX : topLeftY, 2);
|
||||||
Math.round((property === "x" ? topLeftX : topLeftY) * 100) / 100;
|
|
||||||
|
if (
|
||||||
|
appState.croppingElementId === element.id &&
|
||||||
|
isImageElement(element) &&
|
||||||
|
element.crop
|
||||||
|
) {
|
||||||
|
const flipAdjustedPosition = getFlipAdjustedCropPosition(element);
|
||||||
|
|
||||||
|
if (flipAdjustedPosition) {
|
||||||
|
value = round(
|
||||||
|
property === "x" ? flipAdjustedPosition.x : flipAdjustedPosition.y,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatsDragInput
|
<StatsDragInput
|
||||||
|
|
|
@ -23,12 +23,14 @@ import Collapsible from "./Collapsible";
|
||||||
import { useExcalidrawAppState, useExcalidrawSetAppState } from "../App";
|
import { useExcalidrawAppState, useExcalidrawSetAppState } from "../App";
|
||||||
import { getAtomicUnits } from "./utils";
|
import { getAtomicUnits } from "./utils";
|
||||||
import { STATS_PANELS } from "../../constants";
|
import { STATS_PANELS } from "../../constants";
|
||||||
import { isElbowArrow } from "../../element/typeChecks";
|
import { isElbowArrow, isImageElement } from "../../element/typeChecks";
|
||||||
import CanvasGrid from "./CanvasGrid";
|
import CanvasGrid from "./CanvasGrid";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import "./Stats.scss";
|
import "./Stats.scss";
|
||||||
import { isGridModeEnabled } from "../../snapping";
|
import { isGridModeEnabled } from "../../snapping";
|
||||||
|
import { getUncroppedWidthAndHeight } from "../../element/cropElement";
|
||||||
|
import { round } from "../../../math";
|
||||||
|
|
||||||
interface StatsProps {
|
interface StatsProps {
|
||||||
app: AppClassProperties;
|
app: AppClassProperties;
|
||||||
|
@ -128,6 +130,13 @@ export const StatsInner = memo(
|
||||||
const multipleElements =
|
const multipleElements =
|
||||||
selectedElements.length > 1 ? selectedElements : null;
|
selectedElements.length > 1 ? selectedElements : null;
|
||||||
|
|
||||||
|
const cropMode =
|
||||||
|
appState.croppingElementId && isImageElement(singleElement);
|
||||||
|
|
||||||
|
const unCroppedDimension = cropMode
|
||||||
|
? getUncroppedWidthAndHeight(singleElement)
|
||||||
|
: null;
|
||||||
|
|
||||||
const [sceneDimension, setSceneDimension] = useState<{
|
const [sceneDimension, setSceneDimension] = useState<{
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -244,8 +253,34 @@ export const StatsInner = memo(
|
||||||
<StatsRows>
|
<StatsRows>
|
||||||
{singleElement && (
|
{singleElement && (
|
||||||
<>
|
<>
|
||||||
|
{cropMode && (
|
||||||
|
<StatsRow heading>
|
||||||
|
{t("labels.unCroppedDimension")}
|
||||||
|
</StatsRow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{appState.croppingElementId &&
|
||||||
|
isImageElement(singleElement) &&
|
||||||
|
unCroppedDimension && (
|
||||||
|
<StatsRow columns={2}>
|
||||||
|
<div>{t("stats.width")}</div>
|
||||||
|
<div>{round(unCroppedDimension.width, 2)}</div>
|
||||||
|
</StatsRow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{appState.croppingElementId &&
|
||||||
|
isImageElement(singleElement) &&
|
||||||
|
unCroppedDimension && (
|
||||||
|
<StatsRow columns={2}>
|
||||||
|
<div>{t("stats.height")}</div>
|
||||||
|
<div>{round(unCroppedDimension.height, 2)}</div>
|
||||||
|
</StatsRow>
|
||||||
|
)}
|
||||||
|
|
||||||
<StatsRow heading data-testid="stats-element-type">
|
<StatsRow heading data-testid="stats-element-type">
|
||||||
{t(`element.${singleElement.type}`)}
|
{appState.croppingElementId
|
||||||
|
? t("labels.imageCropping")
|
||||||
|
: t(`element.${singleElement.type}`)}
|
||||||
</StatsRow>
|
</StatsRow>
|
||||||
|
|
||||||
<StatsRow>
|
<StatsRow>
|
||||||
|
@ -387,7 +422,8 @@ export const StatsInner = memo(
|
||||||
prev.selectedElements === next.selectedElements &&
|
prev.selectedElements === next.selectedElements &&
|
||||||
prev.appState.stats.panels === next.appState.stats.panels &&
|
prev.appState.stats.panels === next.appState.stats.panels &&
|
||||||
prev.gridModeEnabled === next.gridModeEnabled &&
|
prev.gridModeEnabled === next.gridModeEnabled &&
|
||||||
prev.appState.gridStep === next.appState.gridStep
|
prev.appState.gridStep === next.appState.gridStep &&
|
||||||
|
prev.appState.croppingElementId === next.appState.croppingElementId
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
getResizedElementAbsoluteCoords,
|
getResizedElementAbsoluteCoords,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
|
|
||||||
const MINIMAL_CROP_SIZE = 10;
|
export const MINIMAL_CROP_SIZE = 10;
|
||||||
|
|
||||||
export const cropElement = (
|
export const cropElement = (
|
||||||
element: ExcalidrawImageElement,
|
element: ExcalidrawImageElement,
|
||||||
|
@ -585,3 +585,41 @@ const adjustCropPosition = (
|
||||||
cropY,
|
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),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -157,6 +157,8 @@
|
||||||
"zoomToFit": "Zoom to fit all elements",
|
"zoomToFit": "Zoom to fit all elements",
|
||||||
"installPWA": "Install Excalidraw locally (PWA)",
|
"installPWA": "Install Excalidraw locally (PWA)",
|
||||||
"autoResize": "Enable text auto-resizing",
|
"autoResize": "Enable text auto-resizing",
|
||||||
|
"imageCropping": "Image cropping",
|
||||||
|
"unCroppedDimension": "Uncropped dimension",
|
||||||
"copyElementLink": "Copy link to object",
|
"copyElementLink": "Copy link to object",
|
||||||
"linkToElement": "Link to object"
|
"linkToElement": "Link to object"
|
||||||
},
|
},
|
||||||
|
|
|
@ -186,14 +186,14 @@ describe("Crop an image", () => {
|
||||||
// 50 x 50 square
|
// 50 x 50 square
|
||||||
UI.crop(image, "nw", naturalWidth, naturalHeight, [150, 50]);
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [150, 50]);
|
||||||
UI.crop(image, "n", naturalWidth, naturalHeight, [0, -100], true);
|
UI.crop(image, "n", naturalWidth, naturalHeight, [0, -100], true);
|
||||||
expect(image.width).toEqual(image.height);
|
expect(image.width).toBeCloseTo(image.height);
|
||||||
// image is at the corner, not space to its right to expand, should not be able to resize
|
// image is at the corner, not space to its right to expand, should not be able to resize
|
||||||
expect(image.height).toBeCloseTo(50);
|
expect(image.height).toBeCloseTo(50);
|
||||||
|
|
||||||
UI.crop(image, "nw", naturalWidth, naturalHeight, [-150, -100], true);
|
UI.crop(image, "nw", naturalWidth, naturalHeight, [-150, -100], true);
|
||||||
expect(image.width).toEqual(image.height);
|
expect(image.width).toBeCloseTo(image.height);
|
||||||
// max height should be reached
|
// max height should be reached
|
||||||
expect(image.height).toEqual(initialHeight);
|
expect(image.height).toBeCloseTo(initialHeight);
|
||||||
expect(image.width).toBe(initialHeight);
|
expect(image.width).toBe(initialHeight);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue