Higher DPI graphics with HTML5 canvas
Asked Answered
S

5

23

Is there a way to set a custom DPI/PPI when creating an image using the HTML5 canvas? I know how can I draw on the canvas and export it as an image, but how can I make sure the output image is of certain DPI/PPI. I guess using SVG elemnts to draw on the canvas is a way, but wouldn't that be flattened out when I export the whole canvas as an image? Or calculating the device DPI and then scaling the image to meet my DPI requirement, but that doesn't seem like the correct solution.

Sipe answered 23/1, 2013 at 20:33 Comment(0)
C
4

You cannot (ugh) access the DPI of a display of the current web page in any browser:

Detecting the system DPI/PPI from JS/CSS?

For printing: You most likely cannot set the DPI of exported <canvas> image (PNG, JPEG) using browser standard functions. However, if you use a pure Javascript encoder image encoder you are free to create any sort of binary file you wish and manually adjust the DPI value embedded int he binary.

https://gist.github.com/1245476

Corrugation answered 25/1, 2013 at 22:20 Comment(7)
Better than doing an entire encoder for the PNG/JPEG, use the native encoder, then rewrite the bytes in the header that define the pixel density metadata. In PNG it's the pHYs chunk [must be injected by converting the canvas to a blob, and slicing the chunk in], and in JPEG its easier as you must only rewrite bits 13-16.Matthews
This answer is no longer correct. window.devicePixelRatio gives you the DPI.Foah
@SteveBennettㄹ window.devicePixelRatio doesn't give the DPI, it gives a value like 1 or 2 indicating the ratio of physical pixels to CSS pixels (i.e. for Retina displays).Xenon
DPI doesn't matter for images, they are measured in pixels. DPI/PPI is purely arbitrary (an 100x100 image is 100x100 at 72 DPI as well as 300 DPI).Annapurna
You can do something like:- <div style="height:100px;width;200px"><canvas height="400" width="800" style="height:100%;width:100%"/></div>. This will create a canvas 4 time the size and control the displayed size with the containing div.Microclimatology
@MichaelDeal do u have an exampleHubby
@DallasClarke but what about exportForklift
X
75

Canvases have two different 'sizes': their DOM width/height and their CSS width/height. You can increase a canvas' resolution by increasing the DOM size while keeping the CSS size fixed, and then using the .scale() method to scale all of your future draws to the new bigger size. Here's an example:

function changeResolution(canvas, scaleFactor) {
    // Set up CSS size.
    canvas.style.width = canvas.style.width || canvas.width + 'px';
    canvas.style.height = canvas.style.height || canvas.height + 'px';

    // Resize canvas and scale future draws.
    canvas.width = Math.ceil(canvas.width * scaleFactor);
    canvas.height = Math.ceil(canvas.height * scaleFactor);
    var ctx = canvas.getContext('2d');
    ctx.scale(scaleFactor, scaleFactor);
}

The canvas default resolution is 96dpi (CSS inches, not based on the actual screen). So a scaleFactor of 2 gives 192dpi, 3 is 288dpi, etc. In fact, here's a version that should give your desired DPI:

function setDPI(canvas, dpi) {
    // Set up CSS size.
    canvas.style.width = canvas.style.width || canvas.width + 'px';
    canvas.style.height = canvas.style.height || canvas.height + 'px';

    // Resize canvas and scale future draws.
    var scaleFactor = dpi / 96;
    canvas.width = Math.ceil(canvas.width * scaleFactor);
    canvas.height = Math.ceil(canvas.height * scaleFactor);
    var ctx = canvas.getContext('2d');
    ctx.scale(scaleFactor, scaleFactor);
}

Have fun! Note that both these code samples can only be used once per canvas, they assume the current DOM size is the original (they could be tweaked to change that). Also the rescaling needs to happen before you do any drawing on the canvas. Thanks to this post for the method and information!

Edit: Here is a more robust function that will scale future draws and maintain existing canvas contents. This can be called to rescale multiple times.

function setDPI(canvas, dpi) {
    // Set up CSS size.
    canvas.style.width = canvas.style.width || canvas.width + 'px';
    canvas.style.height = canvas.style.height || canvas.height + 'px';

    // Get size information.
    var scaleFactor = dpi / 96;
    var width = parseFloat(canvas.style.width);
    var height = parseFloat(canvas.style.height);

    // Backup the canvas contents.
    var oldScale = canvas.width / width;
    var backupScale = scaleFactor / oldScale;
    var backup = canvas.cloneNode(false);
    backup.getContext('2d').drawImage(canvas, 0, 0);

    // Resize the canvas.
    var ctx = canvas.getContext('2d');
    canvas.width = Math.ceil(width * scaleFactor);
    canvas.height = Math.ceil(height * scaleFactor);

    // Redraw the canvas image and scale future draws.
    ctx.setTransform(backupScale, 0, 0, backupScale, 0, 0);
    ctx.drawImage(backup, 0, 0);
    ctx.setTransform(scaleFactor, 0, 0, scaleFactor, 0, 0);
}
Xenon answered 25/9, 2014 at 20:42 Comment(9)
This answer should be marked as right answer. Only issue here is there are no way export this canvas with configured DPI, so after call toDataUrl image dimensions would follow scaleFactor, but resolution of the PNG image would still be 96 (or equals to device DPI). Function to export as high resolution image toDataURLHD would be available as part of HTML5.1, for now PNG resolution can only be changed on server. Good example how to scale image on server using C# with changing resolution can be found here https://mcmap.net/q/92622/-how-to-resize-an-image-c.Dougdougal
"they could be tweaked to change that" - how?Walloping
Hey @Zac, I've added an example that can be called multiple times and will preserve canvas contents.Xenon
@Dougdougal Any clue how to get the desired DPI when exporting it. As you said, dpi is again changing to 96Knave
@LokeshJain at the time I worked on this problem finally decide to do server-side export. As I mentioned HTML5.1 should support this feature out of the box.Dougdougal
i tried this but my image comes back as 72dpi now. i wanted 300 dpi. how would i call this functionSloshy
What is the reason to use Math.ceil?Mercurial
From what I remember, canvases don't like non-integer pixel sizes, so we round up to the nearest pixel (up rather than down to get the higher DPI).Xenon
B"H ya but what if i want to export the iamge of a canvas at certain DPIForklift
C
4

You cannot (ugh) access the DPI of a display of the current web page in any browser:

Detecting the system DPI/PPI from JS/CSS?

For printing: You most likely cannot set the DPI of exported <canvas> image (PNG, JPEG) using browser standard functions. However, if you use a pure Javascript encoder image encoder you are free to create any sort of binary file you wish and manually adjust the DPI value embedded int he binary.

https://gist.github.com/1245476

Corrugation answered 25/1, 2013 at 22:20 Comment(7)
Better than doing an entire encoder for the PNG/JPEG, use the native encoder, then rewrite the bytes in the header that define the pixel density metadata. In PNG it's the pHYs chunk [must be injected by converting the canvas to a blob, and slicing the chunk in], and in JPEG its easier as you must only rewrite bits 13-16.Matthews
This answer is no longer correct. window.devicePixelRatio gives you the DPI.Foah
@SteveBennettㄹ window.devicePixelRatio doesn't give the DPI, it gives a value like 1 or 2 indicating the ratio of physical pixels to CSS pixels (i.e. for Retina displays).Xenon
DPI doesn't matter for images, they are measured in pixels. DPI/PPI is purely arbitrary (an 100x100 image is 100x100 at 72 DPI as well as 300 DPI).Annapurna
You can do something like:- <div style="height:100px;width;200px"><canvas height="400" width="800" style="height:100%;width:100%"/></div>. This will create a canvas 4 time the size and control the displayed size with the containing div.Microclimatology
@MichaelDeal do u have an exampleHubby
@DallasClarke but what about exportForklift
A
2

If you just want to set the dpi of the PNG (ie not increase the number of pixels) then this library lets you set the pHYs chunk (amongst other things):

https://github.com/imaya/CanvasTool.PngEncoder

Minimal example to export an HTML5 canvas to base64-encoded PNG:

        // convert dots per inch into dots per metre
        var pixelsPerM = dpi * 100 / 2.54;

        var param = {
            bitDepth : 8,
            colourType : 2,
            filterType : 0,
            height : canvas.height,
            interlaceMethod : 0,
            phys : {
                unit : 1,
                x : pixelsPerM,
                y : pixelsPerM
            },
            width : canvas.width
        };

        var array = canvas.getContext('2d').getImageData(0, 0, canvas.width,
                canvas.height).data;

        var png = new window.CanvasTool.PngEncoder(array, param).convert();

        var base64 = 'data:image/png;base64,' + btoa(png);
Aftermost answered 16/10, 2017 at 12:30 Comment(0)
D
0

The HTML canvas can be adjusted to any pixel ratio.

For example, if the device pixel ratio is 2, with a canvas of 600x400 pixels in CSS, the canvas real size should be 1200x800 pixels, and the canvas context scale should be 2.

<style>
  canvas {
    width: 600px;
    height: 400px;
  }
</style>
<canvas></canvas>
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const dpr = window.devicePixelRatio || 2

const { width, height } = canvas.getBoundingClientRect()

if (canvas.width !== width * dpr || canvas.height !== height * dpr) {
  canvas.width = width * dpr // 600 x 2 = 1200
  canvas.height = height * dpr // 400 x 2 = 800
}

// Reset scale to identical matrix to allow multiple re-scales.
ctx.setTransform(1, 0, 0, 1, 0, 0)

ctx.scale(dpr, dpr)

Now, drawings will be scaled to the device pixel ratio specified.

  • The canvas CSS width and height can be made 100% and calculated with ResizeObserver on resizes.
  • The resize and scale definition should be done once before rendering.
Deforest answered 24/4, 2024 at 0:37 Comment(1)
but what about rxoprted canvasForklift
A
-2

Use the library changedpi:

npm install changedpi --save

Also see

Example code that also allows to adapt the px size and resolution for png or jpg export:

Canvas2Image.saveAsImage('fileName.png', canvas, 2000, 3000, 300, 'png');

-

import Url from './url';
import * as ChangeDpi from 'changeDPI';

export default class Canvas2Image {
  static saveAsImage(fileName, canvas, width, height, dpi, type) {
    type = this._fixType(type);
    canvas = this._scaleCanvas(canvas, width, height);
    let dataUrl = canvas.toDataURL(type);
    let dataUrlWithDpi = ChangeDpi.changeDpiDataUrl(dataUrl, dpi)
    dataUrlWithDpi = dataUrlWithDpi.replace(type, 'image/octet-stream');
    Url.download(fileName, dataUrlWithDpi);
  }

  static _fixType(type) {
    type = type.toLowerCase().replace(/jpg/i, 'jpeg');
    const r = type.match(/png|jpeg|bmp|gif/)[0];
    return `image/${r}`;
  }

  static _scaleCanvas(canvas, width, height) {
    const w = canvas.width;
    const h = canvas.height;
    if (width === undefined) {
      width = w;
    }
    if (height === undefined) {
      height = h;
    }

    const retCanvas = document.createElement('canvas');
    const retCtx = retCanvas.getContext('2d');
    retCanvas.width = width;
    retCanvas.height = height;
    retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);
    return retCanvas;
  }
}

-

export default class Url {
  static download(fileName, url) {
    const element = document.createElement('a');
    element.setAttribute('href', url);
    element.setAttribute('download', fileName);
    element.style.display = 'none';
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);
  }

  static createUrlForBlob(blob) {
    return this._URL.createObjectURL(blob);
  }

  static clearBlobUrl(blobUrl) {
    this._URL.revokeObjectURL(blobUrl);
  }

  static get _URL() {
    return window.URL || window.webkitURL || window;
  }
}
Artis answered 12/5, 2020 at 7:3 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.