|
export const createImage = (url: string) => |
|
new Promise<HTMLImageElement>((resolve, reject) => { |
|
const image = new Image() |
|
image.addEventListener('load', () => resolve(image)) |
|
image.addEventListener('error', error => reject(error)) |
|
image.setAttribute('crossOrigin', 'anonymous') |
|
image.src = url |
|
}) |
|
|
|
export function getRadianAngle(degreeValue: number) { |
|
return (degreeValue * Math.PI) / 180 |
|
} |
|
|
|
export function getMimeType(fileName: string): string { |
|
const extension = fileName.split('.').pop()?.toLowerCase() |
|
switch (extension) { |
|
case 'png': |
|
return 'image/png' |
|
case 'jpg': |
|
case 'jpeg': |
|
return 'image/jpeg' |
|
case 'gif': |
|
return 'image/gif' |
|
case 'webp': |
|
return 'image/webp' |
|
default: |
|
return 'image/jpeg' |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
export function rotateSize(width: number, height: number, rotation: number) { |
|
const rotRad = getRadianAngle(rotation) |
|
|
|
return { |
|
width: |
|
Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height), |
|
height: |
|
Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height), |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
export default async function getCroppedImg( |
|
imageSrc: string, |
|
pixelCrop: { x: number; y: number; width: number; height: number }, |
|
fileName: string, |
|
rotation = 0, |
|
flip = { horizontal: false, vertical: false }, |
|
): Promise<Blob> { |
|
const image = await createImage(imageSrc) |
|
const canvas = document.createElement('canvas') |
|
const ctx = canvas.getContext('2d') |
|
const mimeType = getMimeType(fileName) |
|
|
|
if (!ctx) |
|
throw new Error('Could not create a canvas context') |
|
|
|
const rotRad = getRadianAngle(rotation) |
|
|
|
|
|
const { width: bBoxWidth, height: bBoxHeight } = rotateSize( |
|
image.width, |
|
image.height, |
|
rotation, |
|
) |
|
|
|
|
|
canvas.width = bBoxWidth |
|
canvas.height = bBoxHeight |
|
|
|
|
|
ctx.translate(bBoxWidth / 2, bBoxHeight / 2) |
|
ctx.rotate(rotRad) |
|
ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1) |
|
ctx.translate(-image.width / 2, -image.height / 2) |
|
|
|
|
|
ctx.drawImage(image, 0, 0) |
|
|
|
const croppedCanvas = document.createElement('canvas') |
|
|
|
const croppedCtx = croppedCanvas.getContext('2d') |
|
|
|
if (!croppedCtx) |
|
throw new Error('Could not create a canvas context') |
|
|
|
|
|
croppedCanvas.width = pixelCrop.width |
|
croppedCanvas.height = pixelCrop.height |
|
|
|
|
|
croppedCtx.drawImage( |
|
canvas, |
|
pixelCrop.x, |
|
pixelCrop.y, |
|
pixelCrop.width, |
|
pixelCrop.height, |
|
0, |
|
0, |
|
pixelCrop.width, |
|
pixelCrop.height, |
|
) |
|
|
|
return new Promise((resolve, reject) => { |
|
croppedCanvas.toBlob((file) => { |
|
if (file) |
|
resolve(file) |
|
else |
|
reject(new Error('Could not create a blob')) |
|
}, mimeType) |
|
}) |
|
} |
|
|
|
export function checkIsAnimatedImage(file) { |
|
return new Promise((resolve, reject) => { |
|
const fileReader = new FileReader() |
|
|
|
fileReader.onload = function (e) { |
|
const arr = new Uint8Array(e.target.result) |
|
|
|
|
|
const fileName = file.name.toLowerCase() |
|
if (fileName.endsWith('.gif')) { |
|
|
|
resolve(true) |
|
} |
|
|
|
else if (isWebP(arr)) { |
|
resolve(checkWebPAnimation(arr)) |
|
} |
|
else { |
|
resolve(false) |
|
} |
|
} |
|
|
|
fileReader.onerror = function (err) { |
|
reject(err) |
|
} |
|
|
|
|
|
fileReader.readAsArrayBuffer(file) |
|
}) |
|
} |
|
|
|
|
|
function isWebP(arr) { |
|
return ( |
|
arr[0] === 0x52 && arr[1] === 0x49 && arr[2] === 0x46 && arr[3] === 0x46 |
|
&& arr[8] === 0x57 && arr[9] === 0x45 && arr[10] === 0x42 && arr[11] === 0x50 |
|
) |
|
} |
|
|
|
|
|
function checkWebPAnimation(arr) { |
|
|
|
for (let i = 12; i < arr.length - 4; i++) { |
|
if (arr[i] === 0x41 && arr[i + 1] === 0x4E && arr[i + 2] === 0x49 && arr[i + 3] === 0x4D) |
|
return true |
|
} |
|
return false |
|
} |
|
|