A 6,000 × 4,000 px landscape photo has 600 source pixels per inch at 10 inches wide. But if you want it to fill a 10 × 8-inch print, the photo's 3:2 rectangle is wider than the 5:4 print. A full-bleed export needs a crop. What matters for sharpness is how many source pixels survive that crop.

Calculate the crop before creating the canvas

For a full-bleed print, let the shorter supply of pixels set the scale. Divide each decoded image dimension by the corresponding print dimension, then take the smaller result. That gives the source pixels per inch for the crop. Multiplying the print dimensions back by that value gives the width and height of the source rectangle.

function printCrop(sw, sh, widthIn, heightIn) { for (const n of [sw, sh, widthIn, heightIn]) { if (!Number.isFinite(n) || n <= 0) { throw new RangeError("Image and print dimensions must be positive"); } } const sourcePpi = Math.min(sw / widthIn, sh / heightIn); const cropWidth = Math.min(sw, widthIn * sourcePpi); const cropHeight = Math.min(sh, heightIn * sourcePpi); return { sourcePpi, cropWidth, cropHeight, x: (sw - cropWidth) / 2, y: (sh - cropHeight) / 2, }; } const crop = printCrop(6000, 4000, 10, 8); // 500 PPI, with a 5000 × 4000 crop beginning at (500, 0). 

At 10 × 8 inches, exactly 1,000 pixels of source width are outside the crop. If the subject is near one side, change x within 0 to sw - cropWidth to keep it in frame. Moving the crop changes what is visible. The resolution stays the same until the crop dimensions change.

Draw it at the delivery resolution

Keep the source crop in image pixels. Size the output canvas separately based on what the lab requests. Here a 10 × 8-inch print at 300 PPI becomes a 3,000 × 2,400 px image. The nine-argument form of Canvas drawImage takes both rectangles explicitly.

// bitmap is a decoded image, such as an ImageBitmap. const widthIn = 10; const heightIn = 8; const requestedPpi = 300; const source = printCrop(bitmap.width, bitmap.height, widthIn, heightIn); const canvas = document.createElement("canvas"); canvas.width = Math.round(widthIn * requestedPpi); canvas.height = Math.round(heightIn * requestedPpi); canvas.getContext("2d").drawImage( bitmap, source.x, source.y, source.cropWidth, source.cropHeight, 0, 0, canvas.width, canvas.height, ); 

Here the canvas downsamples a 500 PPI source crop. With a 2,400 × 1,600 px original, the same print would have 200 source PPI. Drawing that smaller image into a 3,000 × 2,400 output can satisfy a file-dimension requirement, but the added pixels do not recover source detail. If you display a quality warning, base it on sourcePpi.

These calculations apply to a crop that fills the print. A fit-with-borders preview is a different layout. When exporting, check the lab's required pixel dimensions and selected print size. A browser export's density metadata may differ from the PPI used to size the canvas.

AI assistance was used to draft and edit this article. The calculations and code examples were checked before publication.