Pass-through options from DOM.context2d to getContext#273
Conversation
mbostock
left a comment
There was a problem hiding this comment.
We shouldn’t code for specific options and should instead support passthrough. If options is an object, we pass it through to canvas.getContext, and we don’t add any try-catch around it. For backwards compatibility, if options is a number, it’s promoted to an options object {scale}.
Although, I’m also tempted to do nothing here. For the most part the DOM API in standard library is deprecated. It’s better to just have people write against the standard APIs so that when code is copy-pasted outside of Observable there are no gratuitous dependencies on the standard library.
|
It's fine to just do nothing. Easy enough to make this helper outside of stlib for special cases. (But it's very useful in general to not have to worry about dpr.) |
src/dom/context2d.js
Outdated
| if (options == null) { | ||
| options = {}; |
There was a problem hiding this comment.
It’d be better to be strict here and only promote options to an object when it is undefined, ideally using default arguments (options = {}). If the user passes in null explicitly, we should respect that and pass null to canvas.getContext.
src/dom/context2d.js
Outdated
| if (options == +options) { | ||
| if (options == null) { | ||
| options = {}; | ||
| } else if (options == +options) { |
There was a problem hiding this comment.
This should be strict based on the type (typeof options === "number") rather than using coercion.
There was a problem hiding this comment.
Trying to be fully compatible with the current implementation, I wanted to support dpr passed as a string. Not sure why anyone would do that, though :)
There was a problem hiding this comment.
Full compatibility is not practical; someone could pass an object that implements valueOf, and this would have worked with the old API, but is ambiguous with named options under the new API. We should only support the intended pattern of passing a number.
src/dom/context2d.js
Outdated
| options = {}; | ||
| } else if (options == +options) { | ||
| scale = +options; | ||
| options = {}; |
There was a problem hiding this comment.
This is probably fine, but it’d be slightly better to pass undefined, I think.
|
we might want to document the willReadFrequently boolean option as well? |
…ale, colorSpace}, allowing the creation of wide-gamut graphics on supported hardware and software. Note: this necessitates a try/catch because Safari will throw if the os requirements are not met. Tests/demo: https://observablehq.com/@fil/colorspace-display-p3-context2d
|
I believe the options handling can be simplified: export default function(width, height, options = {}) {
const {
scale = devicePixelRatio,
...contextOptions
} = !isNaN(options) ? {scale: options} : options;
const canvas = document.createElement("canvas");
canvas.width = width * scale;
canvas.height = height * scale;
canvas.style.width = width + "px";
const context = canvas.getContext("2d", contextOptions);
context.scale(scale, scale);
return context;
} |
| const {scale = devicePixelRatio, ...contextOptions} = !isNaN(options) | ||
| ? {...(options != null && {scale: options})} | ||
| : options; |
There was a problem hiding this comment.
| const {scale = devicePixelRatio, ...contextOptions} = !isNaN(options) | |
| ? {...(options != null && {scale: options})} | |
| : options; | |
| const {scale = devicePixelRatio, ...contextOptions} = options && !isNaN(options) | |
| ? {scale: +options} | |
| : options; |
There was a problem hiding this comment.
0 is currently working as before (in the sense that it returns a canvas of size 0)—it would be simpler if we didn't support this "feature".
There was a problem hiding this comment.
With the additional null check in the earlier comment we can remove options && here.
There was a problem hiding this comment.
(btw, that parseFloat was the result of a brainfart - I meant to write +options)
| If you are using [2D Canvas](https://www.w3.org/TR/2dcontext/) (rather than [WebGL](https://webglfundamentals.org/)), you should use [DOM.context2d](#DOM_context2d) instead of DOM.canvas for automatic pixel density scaling. | ||
|
|
||
| <a href="#DOM_context2d" name="DOM_context2d">#</a> DOM.<b>context2d</b>(<i>width</i>, <i>height</i>[, <i>dpi</i>]) [<>](https://github.com/observablehq/stdlib/blob/main/src/dom/context2d.mjs "Source") | ||
| <a href="#DOM_context2d" name="DOM_context2d">#</a> DOM.<b>context2d</b>(<i>width</i>, <i>height</i>[, <i>options</i>]) [<>](https://github.com/observablehq/stdlib/blob/main/src/dom/context2d.mjs "Source") |
There was a problem hiding this comment.
Shouldn't we list both signatures here?
There was a problem hiding this comment.
It's there: "As a shorthand notation, options having only scale can be specified as a number." (could be phrased better?)
There was a problem hiding this comment.
I feel the shorthand should be mentioned earlier and emphasized, since it's the format that you'll encounter throughout Observable and the notation that you'll commonly use to disable dpr scaling.
| canvas.height = height * dpi; | ||
| export default function (width, height, options = {}) { | ||
| const {scale = devicePixelRatio, ...contextOptions} = !isNaN(options) | ||
| ? {...(options != null && {scale: options})} |
There was a problem hiding this comment.
There's no need for the destructuring here, and the null check should be explicit. If options is undefined, we already get {}.
| ? {...(options != null && {scale: options})} | |
| ? options === null ? {} : {scale: options} |
|
Chrome (>= 111) supports display-p3 |
The third argument to DOM.context2d can be an options object with {scale, colorSpace}, allowing the creation of wide-gamut graphics on supported hardware and software.
Note: this necessitates a try/catch because Safari will throw if the os requirements are not met.
Tests/demo: https://observablehq.com/@fil/colorspace-display-p3-context2d
Draft, mostly for discussion.