0

I've seen this problem online e.g:

HTML5 canvas drawImage with at an angle

http://creativejs.com/2012/01/day-10-drawing-rotated-images-into-canvas/

But I was unsuccessful in applying them to my problem so far.

const TO_RADIANS = Math.PI / 180

export function cropPreview(
  image: HTMLImageElement,
  canvas: HTMLCanvasElement,
  crop: PixelCrop,
  scale = 1,
  rotate = 0,
) {
  const ctx = canvas.getContext('2d')

  if (!ctx) {
    throw new Error('No 2d context')
  }

  const scaleX = image.naturalWidth / image.width
  const scaleY = image.naturalHeight / image.height
  const pixelRatio = window.devicePixelRatio || 1

  canvas.width = Math.floor(crop.width * pixelRatio * scaleX)
  canvas.height = Math.floor(crop.height * pixelRatio * scaleY)

  ctx.scale(pixelRatio, pixelRatio)
  ctx.imageSmoothingQuality = 'high'

  const cropX = crop.x * scaleX
  const cropY = crop.y * scaleY
  const cropWidth = crop.width * scaleX
  const cropHeight = crop.height * scaleY

  const rotateRads = rotate * TO_RADIANS
  const centerX = image.width / 2
  const centerY = image.height / 2

  ctx.save()
  ctx.translate(centerX, centerY)
  ctx.rotate(rotateRads)

  ctx.drawImage(image, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight)

  ctx.restore()
}

Here's a live playground with the problem, just choose an image and apply some rotation: CodeSandbox image rotation

What am I doing wrong here? The bottom image should show exactly what is in the crop area even with rotation applied.

Wrongly transformed crop preview

Dominic
  • 62,658
  • 20
  • 139
  • 163
  • Mirror as in flip the image? – kelsny Mar 04 '22 at 15:23
  • Sorry will edit - I mean it should match not flip – Dominic Mar 04 '22 at 15:25
  • Works for me, the image rotates. – Grumpy Mar 04 '22 at 15:26
  • The image rotates but goes off screen and all sorts. Just updated screenshot to make that clearer. I know I need to somehow apply centerX/Y to drawImage but tried all sorts of combos but no luck – Dominic Mar 04 '22 at 15:27
  • I'm not sure why you're rotating the cropped image; I removed the rotation and cropX cropY and the preview displays fine. However when I update the crop area by moving it, it does not update the preview, so you should probably fix that next. – kelsny Mar 04 '22 at 15:31
  • @youdateme it does not work without rotation code: https://ibb.co/vkrF1Fp – Dominic Mar 04 '22 at 17:48
  • @Dominic is the bottom canvas supposed to grow when an angle is applied to the image and rotation? If not I have already found the problem. For example a rectangle of width 500px and height of 200px when rotated x degrees will have a new width and height. Should the bottom canvas grow to accommodate or just have the image centered and rotated. – SharpInnovativeTechnologies Mar 04 '22 at 18:03
  • @SharpInnovativeTechnologies not 100% sure what you mean but it shouldn't grow, it should be exactly the same size as the crop area in the image above and show exactly what you see inside that crop area. So if you rotate nothing should change regarding the dimensions – Dominic Mar 04 '22 at 18:05
  • @Dominic Awesome! Should the image be centered according to canvas or according to original center (relative to scale of course)? – SharpInnovativeTechnologies Mar 04 '22 at 18:06
  • The center of the preview would be the center of the crop rectangle - not the center of the whole image – Dominic Mar 04 '22 at 18:07
  • 1
    Although I haven't coded up a full solution I will give you the basic idea that I came up with. First using Javascript create a non mounted canvas element (make sure it is large enough to completely contain the image once it has been scaled and rotated). Then rotate that canvas context, and write the image to it, then you need to reverse rotate your mounted canvas context, and write the cropped image (using the nonmounted context as the source) to your mounted context... Its a silly solution... – SharpInnovativeTechnologies Mar 04 '22 at 21:32
  • Here is an example of why you need to draw the image bigger and centered before moving it to your mounted canvas. https://codesandbox.io/s/test-forked-p2z949?file=/src/cropPreview.ts If you don't draw it bigger it will crop the image unnecessarily. – SharpInnovativeTechnologies Mar 04 '22 at 21:33
  • @SharpInnovativeTechnologies Thanks for the effort, yeah calcs are being thrown off by a separate issue when the image is too large for the view which doesn't help – Dominic Mar 06 '22 at 14:39

1 Answers1

0

Here is your updated function


export async function cropPreview(
  image: HTMLImageElement,
  canvas: HTMLCanvasElement,
  crop: PixelCrop,
  scale = 1,
  rotate = 0,
) {
  const ctx = canvas.getContext('2d')

  if (!ctx) {
    throw new Error('No 2d context')
  }

  const scaleX = image.naturalWidth / image.width
  const scaleY = image.naturalHeight / image.height
  const pixelRatio = window.devicePixelRatio || 1

  canvas.width = Math.floor(crop.width * pixelRatio * scaleX)
  canvas.height = Math.floor(crop.height * pixelRatio * scaleY)

  ctx.scale(pixelRatio, pixelRatio)
  ctx.imageSmoothingQuality = 'high'

  const cropX = crop.x * scaleX
  const cropY = crop.y * scaleY
  const cropWidth = crop.width * scaleX
  const cropHeight = crop.height * scaleY

  const rotateRads = rotate * TO_RADIANS
  const centerX = image.width / 2
  const centerY = image.height / 2

  ctx.save()

  // 5) Move the crop origin to the canvas origin (0,0)
  ctx.translate(-cropX, -cropY) 
  // 4) Move the origin to the center of the original position
  ctx.translate(centerX, centerY) 
  // 3) Rotate around the origin
  ctx.rotate(rotateRads) 
  // 2) Scaled the image
  ctx.scale(scale, scale) 
  // 1) Move the center of the image to the origin (0,0)
  ctx.translate(-centerX, -centerY) 
  ctx.drawImage(
    image,
    0,
    0,
    image.width,
    image.height,
    0,
    0,
    image.width,
    image.height,
  )

  ctx.restore()
}

Instead of trying to figure out how to crop you can simply let the canvas to crop it.

The transfromations are applied in the reverse order as they appear, so what I am doing is

  1. Move the center of the image to the origin (0,0)
  2. Scaled the image
  3. Rotate around the origin
  4. Move the origin to the center of the original position
  5. Move the crop origin to the canvas origin (0,0)

enter image description here

Bob
  • 13,867
  • 1
  • 5
  • 27
  • thanks, I updated the code on the sandbox! Sadly it's coming out strange for me, does it work for you? Thought it might be scaling issue I'm on retina (pixel ratio 2) but didn't seem to help https://ibb.co/Yy7HGxx – Dominic Mar 06 '22 at 12:36
  • Updated with a sample image selecting and cropping very like in your example. The selection is buggy as well but the cropping algorithm seems correct. – Bob Mar 06 '22 at 14:20
  • Thanks a lot! yeah it's some issue when the image is too large with other parts of code – Dominic Mar 06 '22 at 14:37