A user account is required in order to edit this wiki, but we've had to disable public user registrations due to spam.

To request an account, ask an autoconfirmed user on IRC (such as one of these permanent autoconfirmed members) or send an e-mail to admin@wiki.whatwg.org with your desired username and an explanation of the first edit you'd like to make. (Do not use this e-mail address for any other inquiries, as they will be ignored or politely declined.)

CanvasRenderedPixelSize

From WHATWG Wiki
Jump to: navigation, search
This proposal is to allow Web developers to match canvas backing store resolution to device resolution.

Use Case Descriptions

Use case #1: a Web application needs to render content to a canvas (using 2D or WebGL contexts) being displayed on a high-DPI screen. For best quality output, the pixels of the canvas backing store need to match 1:1 with screen pixels.

Use case #2: As #1, and the user moves the page from screen to screen resulting in dynamic DPI changes.

Use case #3: As #1, and the canvas is inside a CSS scale transform which may change dynamically.

Use case #4: As #1, and the canvas's CSS content box has arbitrary fractional CSS pixel size and offset from the viewport.

Use case #5: As #3, but the canvas is inside an <iframe> in a page with a different origin which contains the CSS transform.

Current Usage and Workarounds

Safari shipped a canvas implementation that automatically resized the backing store but this caused various problems, such as incompatibility with some existing content and a need for new "HD" APIs that authors were not motivated to use. This approach does not work for WebGL in any case.

The current best workaround is to set the canvas backing store size by computing the canvas content box in CSS pixels relative to the viewport, as accurately as possible (e.g. using getBoxQuads), multiplying by window.devicePixelRatio, and using that to set the canvas width and height. This approach does not easily adapt to dynamic changes and does not handle use cases #4 or #5. In use-case #4 the computed device pixel size may be non-integer, in which case the UA is likely to snap the content-box to device pixel edges and the Web developer cannot predict how that snapping will be done. In use-case #5 the developer cannot know about the CSS transform in the foreign-origin content.

Goals

  1. Leave the application in control of setting the pixel size of the backing store.
  2. Retain the invariant that 1 unit in canvas coordinate space is 1 backing store pixel.
  3. Make it easy for applications to set the pixel size of the backing store so that backing store pixels are 1:1 with device pixels, when that's possible.

Non Goals

  1. Enable high-resolution backing store for existing content.

Proposed Solutions

Hixie proposed a canvas attribute to opt into automatic sizing of the backing store, but this violates goals #1 and #2. Violating goal #1 is a problem because applications often use temporary canvases and other assets which are dependent on the pixel size of the backing store. Goal #1 is also essential for WebGL.

Suggested Solution

Expose a preferred size for the canvas backing store directly to Web authors. Web authors who want to match screen resolution set the canvas width and height to that size. Expose an event on the canvas that fires when the preferred size changes.

Suggested IDL

Add two new DOM attributes to HTMLCanvasElement:

partial interface HTMLCanvasElement {
  readonly attribute long renderedPixelWidth;
  readonly attribute long renderedPixelHeight;
};

The values of these attributes are not normatively defined. For example, a UA might reduce these values to conserve system resources. However, under normal steady-state conditions a UA is expected to choose values such that, if the canvas element has a CSS background color exactly filling its content-box, and the UA renders that CSS background color as a rectangle whose edges are aligned to device pixel edges, then renderedPixelWidth and renderedPixelHeight are the device pixel size of that rectangle.

Add a new event renderedsizechange to HTMLCanvasElement. This event does not bubble and is not cancelable. Whenever the value that would be returned by renderedPixelWidth or renderedPixelHeight changes, queue a task to fire renderedsizechange at the HTMLCanvasElement if there is not already a task pending to fire such an event at that element.

Rationale:

This API appears to be the minimal API needed to achieve the goals, is opt-in, and can be used in several different ways depending on how ambitious the app developer is:

  • Simplest possible usage: set the canvas size once when loading and don't handle dynamic changes.
  <script>
  canvas.width = canvas.renderedPixelWidth; canvas.height = canvas.renderedPixelHeight;
  </script>
  • Update canvas size for every frame of a game:
  <script>
  function renderFrame() {
    canvas.width = canvas.renderedPixelWidth; canvas.height = canvas.renderedPixelHeight;
    ...
    window.requestAnimationFrame(renderFrame);
  }
  </script>
  • Update canvas size and re-render only when DPI changes:
  <script>
  function render() {
    canvas.width = canvas.renderedPixelWidth; canvas.height = canvas.renderedPixelHeight;
    ...
  }
  canvas.addEventHandler("renderedsizechange", render, false);
  </script>

Issues:

  • Hard to determine a rendered size when the canvas is not attached to the DOM. Perhaps in that case the current intrinsic size should be returned? (junov@chromium.org)
  • After a layout change that affects rendered pixel size, there is no guarantee that the size change event will be handled before the layout change is propagated to screen, so the content may be temporarily displayed in an inconsistent state. Note: the same issue exists with existing methods that may be based on mutation observers or window.onresize, for example. Though it is not the stated objective of this proposal to solve this problem, there may be an opportunity to do so. (junov)
  • Accessing rendered pixel size is layout-inducing. To avoid layout thrashing, we should consider making this an asynchronous getter (e.g. asyncGetBoundignClientRect). This would also prevent renderedsizechanged events from firing from within the evaluation of rendered pixel size, which is weird. (junov, credit: nduca)