utils.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. export const createImage = (url: string) =>
  2. new Promise<HTMLImageElement>((resolve, reject) => {
  3. const image = new Image()
  4. image.addEventListener('load', () => resolve(image))
  5. image.addEventListener('error', error => reject(error))
  6. image.setAttribute('crossOrigin', 'anonymous') // needed to avoid cross-origin issues on CodeSandbox
  7. image.src = url
  8. })
  9. export function getRadianAngle(degreeValue: number) {
  10. return (degreeValue * Math.PI) / 180
  11. }
  12. /**
  13. * Returns the new bounding area of a rotated rectangle.
  14. */
  15. export function rotateSize(width: number, height: number, rotation: number) {
  16. const rotRad = getRadianAngle(rotation)
  17. return {
  18. width:
  19. Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
  20. height:
  21. Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),
  22. }
  23. }
  24. /**
  25. * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
  26. */
  27. export default async function getCroppedImg(
  28. imageSrc: string,
  29. pixelCrop: { x: number; y: number; width: number; height: number },
  30. rotation = 0,
  31. flip = { horizontal: false, vertical: false },
  32. ): Promise<Blob> {
  33. const image = await createImage(imageSrc)
  34. const canvas = document.createElement('canvas')
  35. const ctx = canvas.getContext('2d')
  36. if (!ctx)
  37. throw new Error('Could not create a canvas context')
  38. const rotRad = getRadianAngle(rotation)
  39. // calculate bounding box of the rotated image
  40. const { width: bBoxWidth, height: bBoxHeight } = rotateSize(
  41. image.width,
  42. image.height,
  43. rotation,
  44. )
  45. // set canvas size to match the bounding box
  46. canvas.width = bBoxWidth
  47. canvas.height = bBoxHeight
  48. // translate canvas context to a central location to allow rotating and flipping around the center
  49. ctx.translate(bBoxWidth / 2, bBoxHeight / 2)
  50. ctx.rotate(rotRad)
  51. ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1)
  52. ctx.translate(-image.width / 2, -image.height / 2)
  53. // draw rotated image
  54. ctx.drawImage(image, 0, 0)
  55. const croppedCanvas = document.createElement('canvas')
  56. const croppedCtx = croppedCanvas.getContext('2d')
  57. if (!croppedCtx)
  58. throw new Error('Could not create a canvas context')
  59. // Set the size of the cropped canvas
  60. croppedCanvas.width = pixelCrop.width
  61. croppedCanvas.height = pixelCrop.height
  62. // Draw the cropped image onto the new canvas
  63. croppedCtx.drawImage(
  64. canvas,
  65. pixelCrop.x,
  66. pixelCrop.y,
  67. pixelCrop.width,
  68. pixelCrop.height,
  69. 0,
  70. 0,
  71. pixelCrop.width,
  72. pixelCrop.height,
  73. )
  74. return new Promise((resolve, reject) => {
  75. croppedCanvas.toBlob((file) => {
  76. if (file)
  77. resolve(file)
  78. else
  79. reject(new Error('Could not create a blob'))
  80. }, 'image/jpeg')
  81. })
  82. }