-
Notifications
You must be signed in to change notification settings - Fork 370
Allow out-of-standard-gamut colors with float canvases #4526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
458efac
c5f6709
741801d
d706a20
a461771
7cf11da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
… gamut
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2148,34 +2148,36 @@ premultiplication) in which the WebGPU numeric values are to be interpreted. | |
| WebGPU allows all of the color spaces in the {{PredefinedColorSpace}} enum. | ||
| Note, each color space is defined over an extended range, as defined by the referenced CSS definitions, | ||
| to represent color values outside of its space (in both chrominance and luminance). | ||
| Any numeric value in any color space is a well-defined "color" according to its extended color | ||
| space definition, regardless of whether the point represents a real or imaginary color | ||
| (WebGPU makes no distinction between real and imaginary colors). | ||
| That "color" can be represented in other color spaces as well, where it may be in-gamut. | ||
|
|
||
| <h4 data-dfn-type=dfn>Out-of-Standard-Gamut Color Representation | ||
| <h4 data-dfn-type=dfn>Out-of-Range Premultiplied RGBA Value | ||
| <span id=out-of-gamut-premultiplied-rgba-value><!-- historical permalink --></span> | ||
| </h4> | ||
|
|
||
| An [=out-of-standard-gamut color representation=] is one where any of the unpremultiplied | ||
| red/green/blue channel numeric values are outside of the 0-to-1 range. | ||
| In a premultiplied representation, the R/G/B value is outside the 0-to-`alpha` range. | ||
|
|
||
| Any numeric value in any gamut is a well-defined "color" according to that gamut's extended | ||
| color space (regardless of whether the point represents a real or imaginary color). | ||
| That "color" can be represented in other gamuts as well, where it may be in-gamut. | ||
| An [=out-of-range premultiplied RGBA value=] is one for which the unpremultiplied representation | ||
| of the same color would be outside of the range of the format. | ||
|
|
||
| <div class=example> | ||
| For example, the unpremultiplied sRGB RGBA value `[1.04, 0, 0, 0.5]` and the | ||
| premultiplied sRGB RGBA value `[0.52, 0, 0, 0.5]` both represent the | ||
| CSS color <code><a funcdef>color</a>(srgb 1.04 0 0 / 0.5)</code>, which is equivalent to the | ||
| in-gamut Display P3 color <code><a funcdef>color</a>(display-p3 0.95 0.21 0.15 / 0.5)</code>. | ||
|
|
||
| When represented in a {{PredefinedColorSpace/"srgb"}} {{GPUTextureFormat/"rgba8unorm"}} canvas, | ||
| both values are out-of-range for the purposes of this definition, | ||
| even though the premultiplied value is representable. | ||
| </div> | ||
|
|
||
| When a canvas containing such a value is | ||
| [$get a copy of the image contents of a context|used as an image source$], | ||
| the returned image must represent the original color values without clamping, | ||
| regardless of the canvas's {{GPUCanvasConfiguration/format}}. | ||
|
|
||
| However, when a canvas containing such a value is displayed *on screen*, **if and only if** the | ||
| canvas has a range-limited format (like `unorm` formats, which have a range of 0–1), | ||
| the visible result for the entire canvas is undefined. | ||
| However, when a ({{GPUCanvasAlphaMode/"premultiplied"}}) canvas containing such a value is | ||
| displayed *on screen*, the visible result for the entire canvas is undefined. | ||
|
|
||
| <div class=example> | ||
| For example, on a display with a color space of exactly Display P3, | ||
|
|
@@ -2187,33 +2189,29 @@ the visible result for the entire canvas is undefined. | |
| <tbody> | ||
| <tr><td>{{GPUCanvasAlphaMode/"opaque"}},<br>{{GPUTextureFormat/"rgba16float"}} | ||
| <td rowspan=2>`[1.04, 0, 0, 1.0]` | ||
| <td rowspan=2><code><a funcdef>color</a>(display-p3 0.95 0.21 0.15 / 1.0)</code>, | ||
| <br>because the format is not range-limited. | ||
| <td rowspan=2><code><a funcdef>color</a>(display-p3 0.95 0.21 0.15 / 1.0)</code>. | ||
| <tr><td>{{GPUCanvasAlphaMode/"premultiplied"}},<br>{{GPUTextureFormat/"rgba16float"}} | ||
| <tr><td>{{GPUCanvasAlphaMode/"premultiplied"}},<br>{{GPUTextureFormat/"rgba16float"}} | ||
| <td>`[0.52, 0, 0, 0.5]` | ||
| <td><code><a funcdef>color</a>(display-p3 0.95 0.21 0.15 / 0.5)</code>, | ||
| <br>because the format is not range-limited. | ||
| <td><code><a funcdef>color</a>(display-p3 0.95 0.21 0.15 / 0.5)</code>. | ||
| <tr><td>{{GPUCanvasAlphaMode/"premultiplied"}},<br>{{GPUTextureFormat/"rgba8unorm"}} | ||
| <td>`[0.50, 0, 0, 0.5]` | ||
| <td><code><a funcdef>color</a>(display-p3 0.92 0.20 0.14 / 0.5)</code>, | ||
| <br>because the color is in-gamut for {{PredefinedColorSpace/"srgb"}}. | ||
| <td><code><a funcdef>color</a>(display-p3 0.92 0.20 0.14 / 0.5)</code>. | ||
| <tr><td>{{GPUCanvasAlphaMode/"premultiplied"}},<br>{{GPUTextureFormat/"rgba8unorm"}} | ||
| <td>`[0.52, 0, 0, 0.5]` | ||
| <td>(undefined result), | ||
| <br>because the format is range-limited | ||
| <br>and the color is out-of-standard-gamut for {{PredefinedColorSpace/"srgb"}}. | ||
| <!-- POSTV1(#4108): add example for XR format --> | ||
| <br>because the unpremultiplied color cannot be represented in this format. | ||
| <!-- POSTV1(#4108): add example for XR formats, which *probably* handle this --> | ||
| </table> | ||
|
|
||
| The last result is undefined because the color could be: | ||
| Visible results may be undefined because the value could be lost at different steps of display: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wanted to write out an example of the actual blending math, but that ends up demonstrating a completely different problem, which is that you get very different results doing nonlinear blending in display color space (e.g. in display-p3), vs in srgb.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it gets complicated. |
||
|
|
||
| - blended directly to the display buffer as | ||
| - Value blended directly to the display buffer as | ||
| <code><a funcdef>color</a>(display-p3 0.95 0.21 0.15 / 0.5)</code>. | ||
| - blended through an intermediate sRGB buffer as `[0.5, 0, 0, 0.5]`, | ||
| - Value blended through an intermediate sRGB buffer as `[0.5, 0, 0, 0.5]`, | ||
| then sent to the display buffer resulting in the appearance of | ||
| <code><a funcdef>color</a>(display-p3 0.92 0.2 0.14 / 0.5)</code>. | ||
| - displayed by some other process. | ||
| - Value displayed by some other process. | ||
| </div> | ||
|
|
||
| Note: | ||
|
|
@@ -2234,9 +2232,11 @@ are ignored. | |
| Note: | ||
| Grayscale images generally represent RGB values `(V, V, V)`, or RGBA values `(V, V, V, A)` in their color space. | ||
|
|
||
| Colors must not be lossily clamped during conversion: converting from one color space to another | ||
| may result in an [=out-of-standard-gamut color representation=] in the destination gamut, | ||
| which must be preserved. Refer to that section for details. | ||
| Colors are not lossily clamped during conversion: converting from one color space to another | ||
| will result in values outside the range [0, 1] if the source color values were outside the range | ||
| of the destination color space's gamut. For an sRGB destination, for example, this can occur if the | ||
| source is rgba16float, in a wider color space like Display-P3, or is premultiplied and contains | ||
| [=out-of-range premultiplied RGBA values=]. | ||
|
|
||
| Similarly, precision must be preserved through color space conversion, with intermediate | ||
| computations being at least as precise as the less-precise of the source and destination | ||
|
|
@@ -13816,9 +13816,9 @@ is being composited into (e.g. an HTML page rendering, or a 2D canvas). | |
| When alpha is 0, no color is represented, regardless of the RGB values. | ||
| (Attempting to compute the color results in division by zero.) | ||
|
|
||
| In this mode, the canvas texture may hold [=out-of-standard-gamut color representations=]. | ||
| If the canvas has a range-limited format and is displayed on screen, out-of-standard-gamut | ||
| values have undefined display results. Refer to that section for details. | ||
| In this mode, with some texture formats, the canvas texture can hold | ||
| [=out-of-range premultiplied RGBA values=], which have undefined display results. | ||
| Refer to that section for details. | ||
| </dl> | ||
|
|
||
| # Errors & Debugging # {#errors-and-debugging} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking that I understand.
Consider a canvas that is premultiplied,
rgba16float,srgbbeing displayed on a P3 monitor.If the value
[0.52, 0, 0, 0.5]is written to the canvas, and the canvas is displayed, then the result is undefined even though it's within the gamut of the display, because of various machinations that might happen throughout the display pipeline.This seems reasonable to me. Would the idea be to make this valid when in extended mode?