mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-29 03:33:47 +02:00
improve image cropper
This commit is contained in:
526
src/composables/__tests__/useImageCropper.test.js
Normal file
526
src/composables/__tests__/useImageCropper.test.js
Normal file
@@ -0,0 +1,526 @@
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
vi.mock('vue-sonner', () => ({
|
||||
toast: { error: vi.fn() }
|
||||
}));
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({ t: (key) => key })
|
||||
}));
|
||||
|
||||
import {
|
||||
applyTransforms,
|
||||
cropImage,
|
||||
useImageCropper
|
||||
} from '../useImageCropper';
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
*
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
function makeImage(width, height) {
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param root0
|
||||
* @param root0.left
|
||||
* @param root0.top
|
||||
* @param root0.width
|
||||
* @param root0.height
|
||||
* @param root0.imgW
|
||||
* @param root0.imgH
|
||||
* @param root0.rotate
|
||||
* @param root0.flipH
|
||||
* @param root0.flipV
|
||||
*/
|
||||
function makeCropperResult({
|
||||
left = 0,
|
||||
top = 0,
|
||||
width = 100,
|
||||
height = 75,
|
||||
imgW = 100,
|
||||
imgH = 75,
|
||||
rotate = 0,
|
||||
flipH = false,
|
||||
flipV = false
|
||||
} = {}) {
|
||||
return {
|
||||
coordinates: { left, top, width, height },
|
||||
image: {
|
||||
width: imgW,
|
||||
height: imgH,
|
||||
transforms: {
|
||||
rotate,
|
||||
flip: { horizontal: flipH, vertical: flipV }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mockCtx;
|
||||
let canvasInstances;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function setupCanvasMocks() {
|
||||
canvasInstances = [];
|
||||
mockCtx = {
|
||||
drawImage: vi.fn(),
|
||||
fillRect: vi.fn(),
|
||||
translate: vi.fn(),
|
||||
rotate: vi.fn(),
|
||||
scale: vi.fn(),
|
||||
fillStyle: ''
|
||||
};
|
||||
|
||||
const origCreateElement = document.createElement.bind(document);
|
||||
vi.spyOn(document, 'createElement').mockImplementation((tag) => {
|
||||
if (tag === 'canvas') {
|
||||
const canvas = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
getContext: vi.fn(() => mockCtx),
|
||||
toDataURL: vi.fn(() => 'data:image/jpeg;base64,mock'),
|
||||
toBlob: vi.fn((cb) =>
|
||||
cb(new Blob(['mock'], { type: 'image/png' }))
|
||||
)
|
||||
};
|
||||
canvasInstances.push(canvas);
|
||||
return canvas;
|
||||
}
|
||||
return origCreateElement(tag);
|
||||
});
|
||||
|
||||
return { mockCtx, canvasInstances };
|
||||
}
|
||||
|
||||
// ─── useImageCropper composable ──────────────────────────────────────
|
||||
|
||||
describe('useImageCropper', () => {
|
||||
test('returns all expected properties', () => {
|
||||
const result = useImageCropper();
|
||||
expect(result).toHaveProperty('cropperRef');
|
||||
expect(result).toHaveProperty('cropperImageSrc');
|
||||
expect(result).toHaveProperty('resetCropState');
|
||||
expect(result).toHaveProperty('loadImageForCrop');
|
||||
expect(result).toHaveProperty('getCroppedBlob');
|
||||
});
|
||||
|
||||
test('initial state is correct', () => {
|
||||
const { cropperRef, cropperImageSrc } = useImageCropper();
|
||||
expect(cropperRef.value).toBeNull();
|
||||
expect(cropperImageSrc.value).toBe('');
|
||||
});
|
||||
|
||||
test('resetCropState clears all state', () => {
|
||||
const { cropperImageSrc, resetCropState } = useImageCropper();
|
||||
cropperImageSrc.value = 'data:image/png;base64,test';
|
||||
resetCropState();
|
||||
expect(cropperImageSrc.value).toBe('');
|
||||
});
|
||||
|
||||
test('getCroppedBlob returns null when cropperRef is null', async () => {
|
||||
const { getCroppedBlob } = useImageCropper();
|
||||
const result = await getCroppedBlob();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('getCroppedBlob returns null when getResult returns no coordinates', async () => {
|
||||
const { getCroppedBlob, cropperRef } = useImageCropper();
|
||||
cropperRef.value = { getResult: () => ({}) };
|
||||
const result = await getCroppedBlob();
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── cropImage ───────────────────────────────────────────────────────
|
||||
|
||||
describe('cropImage', () => {
|
||||
beforeEach(() => {
|
||||
setupCanvasMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('returns null when no cropperResult', async () => {
|
||||
const result = await cropImage(makeImage(100, 75), 1, null);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('returns null when cropperResult has no coordinates', async () => {
|
||||
const result = await cropImage(makeImage(100, 75), 1, { image: {} });
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('returns null when no originalImage', async () => {
|
||||
const result = await cropImage(null, 1, makeCropperResult());
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test('returns original file when no transforms and crop covers full image', async () => {
|
||||
const file = new File(['test'], 'test.png', { type: 'image/png' });
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 200,
|
||||
height: 150,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
const result = await cropImage(img, 1, cropResult, file);
|
||||
expect(result).toBe(file);
|
||||
});
|
||||
|
||||
test('returns original file when crop is within 1px tolerance', async () => {
|
||||
const file = new File(['test'], 'test.png', { type: 'image/png' });
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 1,
|
||||
top: 1,
|
||||
width: 199,
|
||||
height: 149,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
const result = await cropImage(img, 1, cropResult, file);
|
||||
expect(result).toBe(file);
|
||||
});
|
||||
|
||||
test('does NOT return original file when crop is partial', async () => {
|
||||
const file = new File(['test'], 'test.png', { type: 'image/png' });
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 10,
|
||||
top: 10,
|
||||
width: 100,
|
||||
height: 75,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
const result = await cropImage(img, 1, cropResult, file);
|
||||
expect(result).not.toBe(file);
|
||||
expect(result).toBeInstanceOf(Blob);
|
||||
});
|
||||
|
||||
test('does NOT return original file when transforms are applied', async () => {
|
||||
const file = new File(['test'], 'test.png', { type: 'image/png' });
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 200,
|
||||
height: 150,
|
||||
imgW: 200,
|
||||
imgH: 150,
|
||||
rotate: 90
|
||||
});
|
||||
|
||||
const result = await cropImage(img, 1, cropResult, file);
|
||||
expect(result).not.toBe(file);
|
||||
expect(result).toBeInstanceOf(Blob);
|
||||
});
|
||||
|
||||
test('respects previewScale when computing crop dimensions', async () => {
|
||||
const img = makeImage(1600, 1200);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 50,
|
||||
top: 50,
|
||||
width: 200,
|
||||
height: 150,
|
||||
imgW: 400,
|
||||
imgH: 300
|
||||
});
|
||||
|
||||
await cropImage(img, 0.25, cropResult);
|
||||
|
||||
// cropW = 200 / 0.25 = 800, cropH = 150 / 0.25 = 600
|
||||
const cropCanvas = canvasInstances[canvasInstances.length - 1];
|
||||
expect(cropCanvas.width).toBe(800);
|
||||
expect(cropCanvas.height).toBe(600);
|
||||
});
|
||||
|
||||
test('fills canvas with white before drawing', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 10,
|
||||
top: 10,
|
||||
width: 50,
|
||||
height: 50,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
expect(mockCtx.fillStyle).toBe('#ffffff');
|
||||
expect(mockCtx.fillRect).toHaveBeenCalledWith(0, 0, 50, 50);
|
||||
});
|
||||
|
||||
test('draws image with negative crop offset', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 10,
|
||||
top: 20,
|
||||
width: 100,
|
||||
height: 75,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
expect(mockCtx.drawImage).toHaveBeenCalledWith(img, -10, -20);
|
||||
});
|
||||
|
||||
test('creates canvas via toBlob with image/png format', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 10,
|
||||
top: 10,
|
||||
width: 50,
|
||||
height: 50,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
const result = await cropImage(img, 1, cropResult);
|
||||
|
||||
const cropCanvas = canvasInstances[canvasInstances.length - 1];
|
||||
expect(cropCanvas.toBlob).toHaveBeenCalledWith(
|
||||
expect.any(Function),
|
||||
'image/png'
|
||||
);
|
||||
expect(result).toBeInstanceOf(Blob);
|
||||
});
|
||||
|
||||
test('does not return original file when no file parameter provided', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 200,
|
||||
height: 150,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
const result = await cropImage(img, 1, cropResult);
|
||||
// No file passed, so it should crop even if covering full image
|
||||
expect(result).toBeInstanceOf(Blob);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── cropImage with transforms ───────────────────────────────────────
|
||||
|
||||
describe('cropImage with transforms', () => {
|
||||
beforeEach(() => {
|
||||
setupCanvasMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('creates intermediate transform canvas for rotation', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 150,
|
||||
height: 200,
|
||||
imgW: 200,
|
||||
imgH: 150,
|
||||
rotate: 90
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
// Two canvases: one for transform, one for crop
|
||||
expect(canvasInstances.length).toBe(2);
|
||||
|
||||
// Transform canvas dimensions for 90° rotation: W×H -> H×W
|
||||
const transformCanvas = canvasInstances[0];
|
||||
expect(transformCanvas.width).toBe(150); // height becomes width
|
||||
expect(transformCanvas.height).toBe(200); // width becomes height
|
||||
});
|
||||
|
||||
test('applies rotation transform on intermediate canvas', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 150,
|
||||
height: 200,
|
||||
imgW: 200,
|
||||
imgH: 150,
|
||||
rotate: 90
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
expect(mockCtx.translate).toHaveBeenCalled();
|
||||
expect(mockCtx.rotate).toHaveBeenCalledWith(Math.PI / 2);
|
||||
});
|
||||
|
||||
test('applies horizontal flip via scale(-1, 1)', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 200,
|
||||
height: 150,
|
||||
imgW: 200,
|
||||
imgH: 150,
|
||||
flipH: true
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
expect(mockCtx.scale).toHaveBeenCalledWith(-1, 1);
|
||||
});
|
||||
|
||||
test('applies vertical flip via scale(1, -1)', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 200,
|
||||
height: 150,
|
||||
imgW: 200,
|
||||
imgH: 150,
|
||||
flipV: true
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
expect(mockCtx.scale).toHaveBeenCalledWith(1, -1);
|
||||
});
|
||||
|
||||
test('applies both flips together', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 200,
|
||||
height: 150,
|
||||
imgW: 200,
|
||||
imgH: 150,
|
||||
flipH: true,
|
||||
flipV: true
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
expect(mockCtx.scale).toHaveBeenCalledWith(-1, 1);
|
||||
expect(mockCtx.scale).toHaveBeenCalledWith(1, -1);
|
||||
});
|
||||
|
||||
test('does not create transform canvas when no transforms', async () => {
|
||||
const img = makeImage(200, 150);
|
||||
const cropResult = makeCropperResult({
|
||||
left: 10,
|
||||
top: 10,
|
||||
width: 50,
|
||||
height: 50,
|
||||
imgW: 200,
|
||||
imgH: 150
|
||||
});
|
||||
|
||||
await cropImage(img, 1, cropResult);
|
||||
|
||||
// Only one canvas: the crop canvas, no transform canvas
|
||||
expect(canvasInstances.length).toBe(1);
|
||||
expect(mockCtx.translate).not.toHaveBeenCalled();
|
||||
expect(mockCtx.rotate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── applyTransforms ─────────────────────────────────────────────────
|
||||
|
||||
describe('applyTransforms', () => {
|
||||
beforeEach(() => {
|
||||
setupCanvasMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('returns canvas with swapped dimensions for 90° rotation', () => {
|
||||
const img = makeImage(200, 150);
|
||||
const result = applyTransforms(img, 90, false, false);
|
||||
expect(result.width).toBe(150);
|
||||
expect(result.height).toBe(200);
|
||||
});
|
||||
|
||||
test('returns canvas with same dimensions for 180° rotation', () => {
|
||||
const img = makeImage(200, 150);
|
||||
const result = applyTransforms(img, 180, false, false);
|
||||
expect(result.width).toBe(200);
|
||||
expect(result.height).toBe(150);
|
||||
});
|
||||
|
||||
test('returns canvas with swapped dimensions for 270° rotation', () => {
|
||||
const img = makeImage(200, 150);
|
||||
const result = applyTransforms(img, 270, false, false);
|
||||
expect(result.width).toBe(150);
|
||||
expect(result.height).toBe(200);
|
||||
});
|
||||
|
||||
test('returns canvas with same dimensions for 0° rotation', () => {
|
||||
const img = makeImage(200, 150);
|
||||
const result = applyTransforms(img, 0, false, false);
|
||||
expect(result.width).toBe(200);
|
||||
expect(result.height).toBe(150);
|
||||
});
|
||||
|
||||
test('calls translate to center of canvas', () => {
|
||||
const img = makeImage(200, 150);
|
||||
applyTransforms(img, 90, false, false);
|
||||
// For 90°: rotW=150, rotH=200
|
||||
expect(mockCtx.translate).toHaveBeenCalledWith(75, 100);
|
||||
});
|
||||
|
||||
test('calls scale for horizontal flip', () => {
|
||||
const img = makeImage(200, 150);
|
||||
applyTransforms(img, 0, true, false);
|
||||
expect(mockCtx.scale).toHaveBeenCalledWith(-1, 1);
|
||||
});
|
||||
|
||||
test('calls scale for vertical flip', () => {
|
||||
const img = makeImage(200, 150);
|
||||
applyTransforms(img, 0, false, true);
|
||||
expect(mockCtx.scale).toHaveBeenCalledWith(1, -1);
|
||||
});
|
||||
|
||||
test('does not call scale when no flips', () => {
|
||||
const img = makeImage(200, 150);
|
||||
applyTransforms(img, 90, false, false);
|
||||
expect(mockCtx.scale).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('draws image centered at origin', () => {
|
||||
const img = makeImage(200, 150);
|
||||
applyTransforms(img, 0, false, false);
|
||||
expect(mockCtx.drawImage).toHaveBeenCalledWith(img, -100, -75);
|
||||
});
|
||||
|
||||
test('handles square image correctly', () => {
|
||||
const img = makeImage(100, 100);
|
||||
const result = applyTransforms(img, 90, false, false);
|
||||
expect(result.width).toBe(100);
|
||||
expect(result.height).toBe(100);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,98 @@ import { useI18n } from 'vue-i18n';
|
||||
|
||||
const MAX_PREVIEW_SIZE = 800;
|
||||
|
||||
/**
|
||||
* @param {HTMLImageElement|HTMLCanvasElement} img
|
||||
* @param {number} angleDeg
|
||||
* @param {boolean} flipH
|
||||
* @param {boolean} flipV
|
||||
* @returns {HTMLCanvasElement}
|
||||
*/
|
||||
export function applyTransforms(img, angleDeg, flipH, flipV) {
|
||||
const angleRad = (angleDeg * Math.PI) / 180;
|
||||
const absCos = Math.abs(Math.cos(angleRad));
|
||||
const absSin = Math.abs(Math.sin(angleRad));
|
||||
|
||||
const rotW = Math.round(img.width * absCos + img.height * absSin);
|
||||
const rotH = Math.round(img.width * absSin + img.height * absCos);
|
||||
|
||||
const cvs = document.createElement('canvas');
|
||||
cvs.width = rotW;
|
||||
cvs.height = rotH;
|
||||
const ctx = cvs.getContext('2d');
|
||||
|
||||
ctx.translate(rotW / 2, rotH / 2);
|
||||
ctx.rotate(angleRad);
|
||||
if (flipH) ctx.scale(-1, 1);
|
||||
if (flipV) ctx.scale(1, -1);
|
||||
ctx.drawImage(img, -img.width / 2, -img.height / 2);
|
||||
|
||||
return cvs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLImageElement|HTMLCanvasElement} originalImage
|
||||
* @param {number} previewScale
|
||||
* @param {{ coordinates: object, image: object }} cropperResult
|
||||
* @param {File} [originalFile]
|
||||
* @returns {Promise<Blob|null>}
|
||||
*/
|
||||
export function cropImage(
|
||||
originalImage,
|
||||
previewScale,
|
||||
cropperResult,
|
||||
originalFile
|
||||
) {
|
||||
if (!cropperResult?.coordinates || !originalImage) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const { coordinates } = cropperResult;
|
||||
const scale = previewScale;
|
||||
const transforms = cropperResult.image?.transforms || {};
|
||||
const angle = transforms.rotate || 0;
|
||||
const flipH = transforms.flip?.horizontal || false;
|
||||
const flipV = transforms.flip?.vertical || false;
|
||||
const hasTransform = angle !== 0 || flipH || flipV;
|
||||
|
||||
const cropX = Math.round(coordinates.left / scale);
|
||||
const cropY = Math.round(coordinates.top / scale);
|
||||
const cropW = Math.round(coordinates.width / scale);
|
||||
const cropH = Math.round(coordinates.height / scale);
|
||||
|
||||
if (!hasTransform) {
|
||||
const noCrop =
|
||||
cropX <= 1 &&
|
||||
cropY <= 1 &&
|
||||
Math.abs(cropW - originalImage.width) <= 1 &&
|
||||
Math.abs(cropH - originalImage.height) <= 1;
|
||||
if (noCrop && originalFile) {
|
||||
return Promise.resolve(originalFile);
|
||||
}
|
||||
}
|
||||
|
||||
const source = hasTransform
|
||||
? applyTransforms(originalImage, angle, flipH, flipV)
|
||||
: originalImage;
|
||||
|
||||
const cropCanvas = document.createElement('canvas');
|
||||
cropCanvas.width = cropW;
|
||||
cropCanvas.height = cropH;
|
||||
const ctx = cropCanvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, cropW, cropH);
|
||||
|
||||
ctx.drawImage(source, -cropX, -cropY);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
cropCanvas.toBlob(resolve, 'image/png');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function useImageCropper() {
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -12,6 +104,9 @@ export function useImageCropper() {
|
||||
const originalImage = ref(null);
|
||||
const previewScale = ref(1);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function resetCropState() {
|
||||
cropperImageSrc.value = '';
|
||||
originalImage.value = null;
|
||||
@@ -19,7 +114,7 @@ export function useImageCropper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Downscaling for preview
|
||||
* Downscale for interactive preview, keep original for final crop.
|
||||
* @param {File} file
|
||||
*/
|
||||
function loadImageForCrop(file) {
|
||||
@@ -68,38 +163,12 @@ export function useImageCropper() {
|
||||
*/
|
||||
function getCroppedBlob(originalFile) {
|
||||
const result = cropperRef.value?.getResult();
|
||||
if (!result?.coordinates || !originalImage.value) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const { coordinates } = result;
|
||||
const scale = previewScale.value;
|
||||
const srcX = Math.round(coordinates.left / scale);
|
||||
const srcY = Math.round(coordinates.top / scale);
|
||||
const srcW = Math.round(coordinates.width / scale);
|
||||
const srcH = Math.round(coordinates.height / scale);
|
||||
|
||||
const img = originalImage.value;
|
||||
const noCrop =
|
||||
srcX <= 1 &&
|
||||
srcY <= 1 &&
|
||||
Math.abs(srcW - img.width) <= 1 &&
|
||||
Math.abs(srcH - img.height) <= 1;
|
||||
|
||||
// pass no crop
|
||||
if (noCrop && originalFile) {
|
||||
return Promise.resolve(originalFile);
|
||||
}
|
||||
|
||||
const cropCanvas = document.createElement('canvas');
|
||||
cropCanvas.width = srcW;
|
||||
cropCanvas.height = srcH;
|
||||
const ctx = cropCanvas.getContext('2d');
|
||||
ctx.drawImage(img, srcX, srcY, srcW, srcH, 0, 0, srcW, srcH);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
cropCanvas.toBlob(resolve, 'image/png');
|
||||
});
|
||||
return cropImage(
|
||||
originalImage.value,
|
||||
previewScale.value,
|
||||
result,
|
||||
originalFile
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user