Titulum
Titulum

Reputation: 11476

How to draw an image in real size and ensure printing will be correct

I don't really know how to formulate my problem into one question but here is a good description:

Imagine I want to draw a square on an HTML canvas, with dimension 1" x 1". I know that using pixels as a printable length is meaningless, we need to take in account the DPI. In the following example I took a dpi of 300:

<canvas id="myCanvas" width="400" height="400"></canvas>

This is my Javascript:

var dpi = 300;
var cContext = document.getElementById("myCanvas").getContext("2d");

cContext.moveTo(50,  50);

// Draw a square
cContext.lineTo(350, 50);
cContext.lineTo(350, 350);
cContext.lineTo(50,  350);
cContext.lineTo(50,  50)

cContext.stroke();

The result is a nice square with width 300px in a 300dpi setting, so it should print a one inch square when printing on paper. But the problem is that it doesn't. I checked the printer settings and used 300dpi.

Can someone tell me what I'm doing wrong, or point me in the right direction?

Upvotes: 5

Views: 5590

Answers (2)

Blindman67
Blindman67

Reputation: 54039

Print quality/DPI

The printer's DPI setting is not related to the source image DPI and is commonly known as print quality.

You need the print size

The image DPI is dependent on its resolution (width and height in pixels) and the size it is printed at mm or inches to give printing resolution (iDPU "image Dots Per Unit" for further reference in this answer).

The image DPI is meaningless unless you also associate a fixed print size to the image.

If the printer has a DPI set differently to the iDPI of the image the printer (or driver) will either downsample or upsample the image DPI to match the required DPI. You want to avoid downsampling as that will reduce the image quality.

Page Size

To get the printer DPI to match the image iDPU . This example is for an A4 page in portrait mode. You can use any size print page but you will need to know the actual physical size.

const pageSizes = {
   a4 : {
      portrait : {
          inch : {
             width : 8.27,
             height : 11.69,
          },
          mm : {
             width : 210,
             height : 297,
          }
      },
      landscape : {
          inch : {
             height : 8.27,
             width : 11.69,
          },
          mm : {
             width : 297,
             height : 210,
          }
      },
}

Canvas resolution

Create a canvas with a iDPI 300 to be 2 inches by 2 inches.

const DPI = 300;  // need to have a selected dots per unit in this case inches
const units = "inch";
const pageLayout = "portrait";
const printOn = "a4";
// incase you are using mm you need to convert
const sizeWidth = 2;  // canvas intended print size in units = "inch"
const sizeHeight = 2;
var canvas = document.createElement("canvas");
canvas.width = DPI * sizeWidth;
canvas.height = DPI * sizeHeight;

Canvas size

Scale the canvas to fit the page at the correct size to match the print size. This will be the canvas print size/page print width

canvas.style.width = ((sizeWidth / pageSizes[printOn][pageLayout][units].width) * 100) + "%";

For the height you need to assume that the pixel aspect is square and the height of the page may be longer or shorter than the print page so you must use pixels.

canvas.style.height = Math.round((sizeHeight / pageSizes[printOn][pageLayout][units].width) * innerWidth) + "px";

Add the canvas to the page

document.body.appendChild(canvas);
  • Note: the canvas must be added to the page body or an element that is 100% of the page width. IE the width in pixels === innerWidth.

  • Note: the canvas should not have a border, padding, margin or inherit any CSS styles that affect its size.

Printing

  • Ensure the printer quality is set to 300DPI (or higher).
  • Select the page size (in this case A4 is 210 × 297 millimeters or 8.27 × 11.69 inches)
  • Select the layout to be portrait.
  • Select no borders on the print options.
  • Print

Printing with borders.

If you want borders on the printed page you need to use custom borders so that you know the border size. (NOTE example is only using inches)

 const border = { // in inches
     top : 0.4,
     bottom : 0.4,
     left : 0.4,
     right : 0.4,
 }
    

Create the canvas as shown above.

Size the canvas

canvas.style.width = ((sizeWidth / (pageSizes.a4.portrait.width - border.left - border.right) * 100) + "%";

The height becomes a little more complex as it needs the pixel width of the page adjusted for the borders.

canvas.style.height = Math.round((sizeHeight / (pageSizes.a4.portrait.width  - border.left - border.right)) * innerWidth * (1 - (border.left - border.right) / pageSizes.a4.portrait.width)  ) + "px";

Upvotes: 7

alebianco
alebianco

Reputation: 2555

I noticed in the comments you measured in cm and not inches (good choice, metric system FTW), so here's a commented example that handles those measures correctly

var dpi = 300; // the print resolution
var ppmm = dpi / 25.4; // get the pixel-per-millimeter ratio

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var x = 5 * ppmm; // 5mm
var y = 5 * ppmm; // 5mm
var w = 76 * ppmm; // 76mm
var h = 76 * ppmm // 76mm

ctx.rect(x, y, w, h); // drawing a rectangle the simple way
ctx.stroke();

var data = canvas.toDataURL(); // extract the image data

// inject the image data into a link, creating a downloadable file
var link = document.getElementById("link");
link.setAttribute('href', 'data:application/octet-stream;charset=utf-16le;' + data);
link.setAttribute('download', "image.png");
<!-- the canvas is scaled down from 300dpi to 96dpi it shouldn't affect the final image but it makes it nicer for the screen and more manageable -->
<canvas id="canvas" width="1125px" height="1125px" style="width:360px;height:360px"></canvas>
<a href="#" id="link">download</a>

to print a 300dpi image of 7.5cm you have to get the pixel-per-mm ratio and multiply your sizes to that.

When you download the image, it will be a 72dpi image of 1125x1125px, resample that to 300dpi and you'll get 270x270px which equals to 95,25mm when printed (for the whole image, padding and all).

the rectangle drawn will have a 897.6377952755906px side length, with prints out to 76mm at 300dpi

btw, that fractional pixel size is the reason why i scale down the canvas on the screen. Since we're prioritising the print, the screen image will suffer and look blurry, scaling it down will make it look crispier. Plus it's a more manageable size to see in the browser.

final note: apparently a retina screen uses a different dpi value to render the canvas, so the rectangle will look smaller. on a "normal" resolution monitor it will measure 7.6cm on screen too.

Upvotes: 2

Related Questions