Open juj opened 4 years ago
cc @whatwg/canvas
This looks like correct behavior to me. toDataURL returns the size of the backing buffer (canvas.width, canvas.height). devicePixelRatio is mostly irrelevant to toDataURL. There's no upscale/downscale happening anywhere.
The paragraph you quote is indeed slightly weird, as it's referring the file format's explicit resolution (like SVG), while talking about the bitmap data. We should amend that.
CC @kenrussell @jdashg
toDataURL returns the size of the backing buffer (canvas.width, canvas.height). devicePixelRatio is mostly irrelevant to toDataURL. There's no upscale/downscale happening anywhere.
Hmm, are you sure? The backing buffer in this test is 300x150, but the resulting image is 600x300? That looks like a 2x resize?
What makes you think that image.width
is in CSS Pixels? It's not.
Oh? https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement writes
HTMLImageElement.width
An integer value that reflects the width HTML attribute, indicating the rendered width of the image in CSS pixels.
I did not dare to call that being wrong, but perhaps that is the root confusion then?
Well. I think there's a bit of a confusion here. Let me try again.
If you load a random image with an image element that is 80x80, the size of the object will be "devicePixelRatio-independent". I.e., talking about "hardware pixels" is irrelevant - the width * dpr
means nothing. That said, the HTMLImageElement that is used to present this is 80x80 in "CSS Pixels", which means a 80*dpr
monitor pixels size. What happens in the end is just an upscaling of the original image while compositing it. Nowhere in memory a 80*dpr
image is created, in theory.
The exact same thing happens with canvas. A 300x150 canvas will have a backing storage of 300x150, even if the logical size of the element is 300 CSS Pixels (and 300*dpr
monitor pixels). There's an upscaling when compositing (or whatever, depending if there are other CSS transforms at play).
Thanks for the clarification. I was indeed not aware that the native/"hardware size" of an Image is interpreted "hidden" like this - unlike it is with canvas.
Is there a JavaScript API to get the actual pixel size of an image? It is far from irrelevant when interacting with canvases, where drawing is done in hardware pixel units (e.g. in WebGL).
Is there a JavaScript API to get the actual pixel size of an image?
Oh hmm, nevermind, the size will be image.naturalWidth/.naturalHeight
pixels.
Thanks for the fast turnaround, I suppose this can be closed - I was confused by the wording on MDN, where the vocabulary for Image was unfamiliar coming from working with WebGL canvases.
Note that the quoted text in the OP is meant to be addressing the topic of high-resolution backing canvases. If your canvas is using a 2x "high DPI" backing store, then you want toDataUrl() to produce an image that'll, assigned to an <img>
with no additional sizing information, produces an image natively sized the same as the <canvas>
natively sizes. That requires downsampling it to a 1x resolution, currently; if it just directly reflected the backing store, it would indeed double in size.
(Can additional headers be sent in a data url? If so, as the DPI-header proposal matures, we could handle such a case that way instead, keeping the exact image data and informing the <img>
to render it as a 2x/etc image.)
So far, however, high-resolution backing canvases have been dropped as a feature; right now you achieve that by just making a double-size canvas and setting its CSS width and height to half its native width and height, giving you an effective 2x resolution. If you want a 1x data url out of that, you need to draw it into a half-sized canvas, so the drawing operation takes care of the downsampling for you.
https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width:
The IDL attributes width and height must return the rendered width and height of the image, in CSS pixels, if the image is being rendered, and is being rendered to a visual medium; or else the density-corrected intrinsic width and height of the image, in CSS pixels, if the image has intrinsic dimensions and is available but not being rendered to a visual medium; or else 0, if the image is not available or does not have intrinsic dimensions. [CSS]
On setting, they must act as if they reflected the respective content attributes of the same name.
The IDL attributes naturalWidth and naturalHeight must return the density-corrected intrinsic width and height of the image, in CSS pixels, if the image has intrinsic dimensions and is available, or else 0.
In short, naturalWidth/Height
are the "source ~pixel size, if available". width/height
are dual-purpose: If in the dom/css/'rendering', it's the css pixel counts, else it's the naturalWidth/Height
"resource pixel counts".
WebGL has no clue about its CSS size, and so doesn't change behavior based on CSS style.width/height. (It only checks canvas width/height, which is always the backing-store size, which is drawingBufferWidth/Height in WebGL)
Reopening this as per https://github.com/whatwg/html/issues/5387#issuecomment-602550868 we should change the paragraph quoted in OP.
https://html.spec.whatwg.org/multipage/canvas.html#serialising-bitmaps-to-a-file
Not sure how this wording should be interpreted, but to me it looks like the implementation of
canvas.toDataURL()
is broken in all current browsers with respect to CSS Scaling (window.devicePixelRatio
handling).The spec states in https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl for canvas.toDataURL() that
Let file be a serialization of this canvas element's bitmap as a file, passing type and quality if given.
. The function name.toDataURL()
and the wording about serialization suggests that the conversion should be as lossless as possible, i.e. a "dump" of the contents of the canvas to the specified format.However this is not the case in the implementation of any current browser, but when testing the following code in Safari, Firefox and Chrome on a MacBook with
window.devicePixelRatio==2
, they all upscale the resulting image file to 2x of its original size.Testing on my Macbook prints
Not sure if this is accidentally intended by the spec, or a bug of the implementation in all browsers, but such resizing of the image certainly goes against the spirit of what serialization should be about?
There are 4K laptops out there that have
window.devicePixelRatio==4
, for those taking a "serialization" of a fullscreen 3840x2160 canvas would result in a 15360x8640 pixel image file getting generated. (e.g. in our use case for WebGL unit test harness)Also,
window.devicePixelRatio
is affected by the user agent's UI zoom level. If the test page is visited with browser page zoomed in to +300% zoom level (refresh the page with that zoom level to reload), then the page outputsIt would be very counterintuitive if a function that intends to serialize the contents of a canvas to an image file, would perform an upscaling (or lossy downscaling if browser zoom is at < 100%) based on user's browser zoom level.
Either all browser implementations have this wrong, or if the spec originally intended it to work like this, I think the spec should be reworded so that a proper non-resizing serialization is done. What do you think?