Reputation: 11476
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
Reputation: 54039
The printer's DPI setting is not related to the source image DPI and is commonly known as print quality.
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.
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,
}
},
}
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;
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.
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
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