How to set HTML5 canvas size to match display size in device pixels [duplicate]
Asked Answered
W

2

6

Is it possible to set the size of the bitmap in an HTML5 canvas so that it exactly matches the display size in device pixels, ie the number of physical pixels used to display the canvas, even when window.devicePixelRatio is not an integer?

There is a good description of how to resize the canvas on webglfundamentals but it does not properly handle non-integer devicePixelRatios. My understanding so far:

  • The display size of the canvas is set in CSS with, eg, canvas {width: 200px;}.
  • The underlying bitmap size is set with, eg, <canvas width="100"/> in HTML or canvas.width = 100 in JS.
  • The bitmap will be stretched to fit the CSS size (how is affected by object-fit and object-position).
  • We can set bitmap size to be equal to some expression involving canvas.clientWidth.
  • canvas.clientWidth is an integer and its unit are CSS pixels and is the calculated width of the content (plus padding). I don't know if the browsers actually draw the content into a whole number of CSS pixels or a whole number of device pixels.

So webglfundamentals suggests something like

canvas.width = Math.floor(canvas.clientWidth * window.devicePixelRatio);

but if window.devicePixelRatio is a fraction, this sometimes doesn't work (2 pixel wide lines drawn on integer coordinates are fuzzy). My 1920x1080 screen has devicePixelRatio of 1.5 by default, and page zoom can affects this, so lots of reasons why devicePixelRatio is not an integer as a rule. What can we do?

Wehrmacht answered 9/11, 2018 at 20:50 Comment(7)
window.devicePixelRatio will never be an integer, according to MDN: A double-precision floating-point valuePuto
@JackBashford I think that he meant a natural number and not a fractionAssess
So he means he wants to round it @NielsNet?Puto
Can you not just set the canvas width and height to 100% using CSS?Babettebabeuf
Jonathan, no that only affects the display size without any regard to the bitmap size.Wehrmacht
The following helped me: https://mcmap.net/q/1138018/-how-draw-in-high-resolution-to-canvas-on-chrome-and-why-if-devicepixelratio-webkitbackingstorepixelratio-does-scaling-to-2x-improve-resolutionOllieollis
The webglfundamentals artical that you've linked mentions a way to get canvas's size in actual device pixels using ReziseObserver and devicePixelContentBoxSize. Though it is not yet supported in SafariBramblett
A
5

I know this is an old thread, but I hope I can help a bit any other wanderer coming to this.

There is a way to make a "canvas pixel" match "device pixel" as close to a 1:1 ratio as possible, at least from my experience while making a Canvas WebGL based game engine, even with non-integer devicePixelRatio values.

First, there are 3 things we need to understand:

  • CSS Size: Refers to the actual Canvas DOM element size in CSS pixel (px) units.

  • Canvas Size: Logical size of the canvas, as set using the element properties (i.e. canvasElem.width = 100;), this affects your render space.

  • Device Pixel Ratio: Value obtained from window which effectively describes the ratio between a physical pixel and a CSS pixel.

Brief Example

Let's say you have a devicePixelRatio of 2.625, and your mobile device reports a screen/window size of 411 x 487 -a weird size- you might say, but that is in CSS pixel units, if we multiply those by the devicePixelRatio we get a size of 1078 x 1278 which tells us that most likely the device screen is actually a 1080 x 1280 screen, as originally advertised by your favorite phone manufacturer.

Now, to size stuff...

Say we want a canvas filling the whole screen. Set your CSS Size match the screen size (or any box size you want).

canvas.style.width = '411px';
canvas.style.height = '487px';

Now set the Canvas Size to the same but scaled-up by devicePixelRatio:

canvas.width = 411 * window.devicePixelRatio;
canvas.height = 487 * window.devicePixelRatio;

Now you can render your images.

When the browser calculates the layouts, it will have to scale-down your canvas to make it fit in its DOM rectangle area, the down-scale factor is cssSize / canvasSize yielding a down-scale factor of 1 / devicePixelRatio.

And then when the browser needs to render the layout to physical screen, it will scale it up by devicePixelRatio, thus leaving a total scaling of 1.0.

TL;DR

Supersampling. Scale up your canvas logical size by devicePixelRatio while keeping your CSS size at whatever size you need, and let the browser do the rest.

NOTE: If your rendering expects the canvas to be of certain size, and this "scaling up" messes up your layout and/or calculations, use the canvas setTransform method to scale down your "logical units" by 1.0 / devicePixelRatio, no quality is loss at all.

Arnett answered 1/9, 2022 at 7:49 Comment(0)
M
0

1px is a logical length unit in CSS/DOM realm. Dot.

So I don't think that you will find reliable method for getting physical pixels. Check this.

That's why in Sciter I've made px units to represent always physical pixels of the device. And introduced dip units that are logical pixels - 1/96 of inch measured by the ruler on device surface (so 1dip ~ 1px of W3C CSS).

I simply do not understand how to do serious UI development without the ability to define objects in device pixels.

Mohun answered 10/11, 2018 at 17:20 Comment(2)
Thanks for replying as a UI authority and for the link to the well-seen question. I'll still leave the question open for a bit.Wehrmacht
It was Apple who, in their infinite wisdom -- practically to sell their first iPhone -- who came up with the rather idiotic additional "level of indirection" or complexity, if you will, that is now known by a great term of "logical pixel". And we've all been pulling our hair out ever since.Ruppert

© 2022 - 2024 — McMap. All rights reserved.