Closed bensquire closed 11 months ago
The issue is that for correct quality on devices with a larger pixel ratio than 1 (window.devicePixelRatio
) like retina screens, you have to have a larger canvas than is actually rendered.
I was lazy and didn't resize it down to the correct value on click of the Download button.
What I should do is if (window.devicePixelRatio !== 1)
create an Offscreen Canvas of the actual crop size and copy the preview canvas to it (ctx.drawImage
can resize as its copying).
But yes your way is the simple solution of always rendering it at the actual size which works too (at the expense of some visual quality on retina devices) ;)
I am keen to improve quality if possible... If I were to use my method and the offscreen canvas, would that give me better quality output, or is it not as simple as that?
I've updated the example here: https://codesandbox.io/s/react-image-crop-demo-with-react-hooks-y831o?file=/src/App.tsx
async function onDownloadCropClick() {
const image = imgRef.current
const previewCanvas = previewCanvasRef.current
if (!image || !previewCanvas || !completedCrop) {
throw new Error('Crop canvas does not exist')
}
// This will size relative to the uploaded image
// size. If you want to size according to what they
// are looking at on screen, remove scaleX + scaleY
const scaleX = image.naturalWidth / image.width
const scaleY = image.naturalHeight / image.height
const offscreen = new OffscreenCanvas(
completedCrop.width * scaleX,
completedCrop.height * scaleY,
)
const ctx = offscreen.getContext('2d')
if (!ctx) {
throw new Error('No 2d context')
}
ctx.drawImage(
previewCanvas,
0,
0,
previewCanvas.width,
previewCanvas.height,
0,
0,
offscreen.width,
offscreen.height,
)
// You might want { type: "image/jpeg", quality: <0 to 1> } to
// reduce image size
const blob = await offscreen.convertToBlob({
type: 'image/png',
})
if (blobUrlRef.current) {
URL.revokeObjectURL(blobUrlRef.current)
}
blobUrlRef.current = URL.createObjectURL(blob)
hiddenAnchorRef.current!.href = blobUrlRef.current
hiddenAnchorRef.current!.click()
}
Note the comment about scaleX
+ scaleY
, remove those to get a crop exactly as the user sees it. Otherwise it will be based on the uploaded image which could be a lot bigger than that.
Sorry @DominicTobias I forgot to reply, this worked a treat thank-you.
I'm not sure if this is the intended behaviour or not, but I've seen a few issue in the repo complaining about huge files when extracting the final image from the preview canvas.
I think the issue comes down to people expecting the final image to have the width/height of the canvas, but instead ~it's the same~ has a higher resolution than the input image.
You can see this in the demo app by looking at the inspector:
While we have set a width/height of
355px x 200px
the actual canvas is5828px x 3276px
. When I export that image and compare it to the original, it's filesize has ballooned.For our own project, I've worked round this by building out a new version of
canvasPreview.tsx
, but I've removed the stuff we don't need; this allows me to set the desired output size. This probably isn't fit 4 purpose, because it's so heavily tailored towards our needs, but maybe it can help rectify an ongoing issue?