[Bug 12444] New: Canvas rendering should be done in linear color space (gamma 1) and the result displayed in sRGB color space (approximately gamma 2.2)

http://www.w3.org/Bugs/Public/show_bug.cgi?id=12444

           Summary: Canvas rendering should be done in linear color space
                    (gamma 1) and the result displayed in sRGB color space
                    (approximately gamma 2.2)
           Product: HTML WG
           Version: unspecified
          Platform: All
        OS/Version: All
            Status: NEW
          Severity: normal
          Priority: P2
         Component: HTML Canvas 2D Context (editor: Ian Hickson)
        AssignedTo: ian@hixie.ch
        ReportedBy: w3c@hugues.info
         QAContact: public-html-bugzilla@w3.org
                CC: mike@w3.org, public-html-wg-issue-tracking@w3.org,
                    public-html@w3.org


Created attachment 975
  --> http://www.w3.org/Bugs/Public/attachment.cgi?id=975
This image shows a comparison of a series of images computed incorrectly
(ignoring the gamma) and correctly (done in linear space).

Canvas rendering should be done in linear color space (gamma 1) and the result
displayed in sRGB color space (approximately gamma 2.2)

Conventional CRT monitors have a non-linear response of intensity to input
voltage. This response is characterized by a power function. The displayed
intensity is the input value raised to a power of about 2.5. This 2.5 value is
called the gamma of the monitor. The input value represents the color value
sent by the graphics adapter to the monitor. It can be represented by a value
ranging from 0 (that represents black) to 1 (that represents the full
intensity, white, (255 in unsigned 8-bit terms)).

Other computer monitors, like LCD monitors, also follow this gamma law. LCD
monitors don’t have the same physical response as CRT monitors, but computer
LCD monitors emulate the CRT response to display values similarly to CRT
monitors.

This gamma function completely changes how intensities (and thus colors) are
displayed on the screen. All color values that are not exactly 0 or 1 (since
applying a power function to these values leaves them unaffected) will be much
darker than one might expect from their value. For example, a color of 0.5 will
not be half as bright as 1, but actually 0.5^2.5 times, that is 0.177. This is
much less than 0.5, almost 3 times darker. The color that is half as bright as
white would be 0.5^(1 / 2.5), that is 0.758.

Most users are completely unaware of this, and this is actually not a problem
at all. When they pick a color using a color picker, they choose a color by how
it is displayed, not by its numerical value, thus unwittingly compensating the
gamma.

Moreover, the human visual system is not linear but logarithmic. Because of
this, a value will /feel/ for example half as bright when it is actually much
darker. This is also why gamma is actually a good thing. Thanks to it, there is
more data precision for darker values, to which the human eye is more
sensitive. With only 8 bits of precision per color channel, it is not possible
to store linear values without noticeable precision artifacts, while storing
gamma-encoded values gives reasonable results.

As long as the system only directly displays colors chosen by users on the
monitor, the gamma is not a problem. Where things become more tricky, is when
gamma-encoded color values are used in computations. When this happens,
problems arise. The bad news is that the vast majority of software that
manipulate color values is doing this.

Yes. Averaging two color values by simply adding them and dividing the result
by 2 will not give the correct result at all. For example, to compute the
average of color values 0.01 and 0.79, simply doing 0.01 * 0.5 + 0.79 * 0.5,
what would give 0.4, is incorrect. Indeed, the values must first be converted
to their linear intensity by applying the gamma power. Then, the computation
can be done. At the end, the result must be converted to be displayed on the
screen, by applying the inverse of the gamma (this is called “gamma
correction”). This gives the following computation: (0.01^2.5 * 0.5 + 0.79^2.5
* 0.5)^(1 / 2.5), what gives almost 0.6. That is a difference of 50%!

It is thus essential to take gamma into account when making computations with
color values to have a correct result displayed on the monitor. When drawing on
canvas, a lot of such computations is done: blending colors together, drawing
antialiased shapes or text, scaling images,…

Let’s repeat this: antialiasing will not give good results if the gamma is not
taken into account. Just open your favorite image editing software. Create a
new image with a pure green background (0, 255, 0). Now select the most
aggressive fuchsia (255, 0, 255). Draw antialiased text. Do you see dark edges
around your characters? If you don’t, I would like to know the name of your
software :).

Now the good news is that because we are now aware of the situation, we can now
handle things correctly.

The Internet has chosen sRGB as its standard color space. This color space has
been defined in such a way that the vast majority of computers, that use a
conventional computer monitor and don’t do any color management, will comply
(more or less) to the standard. sRGB defines an overall gamma of approximately
2.2. The reason it is 2.2 and not 2.5 is because the viewing conditions of sRGB
assume a dimly lit environment (such as an office), which require a resulting
gamma slightly above 1 for best results.

The sRGB gamma is not exactly 2.2, though. The standard defines a
transformation that approximates a gamma 2.2 curve. The following functions
should be used:

float sRGB_to_linear(float c)
{
    if (c <= 0.04045)
    {
        return c / 12.92;
    }

    return pow((c + 0.055) / 1.055, 2.4);
}

float linear_to_sRGB(float c)
{
    if (c <= 0.0031308)
    {
        return 12.92 * c;
    }

    return 1.055 * pow(c, 1 / 2.4) - 0.055;
}

To make computations with sRGB color values, the first step is to convert them
all to linear with the first function. Then, the computation can be done (in a
linear space, where all math works as expected :)). At the end, the result is
converted from linear to sRGB with the second function and can be displayed.
(The computations above done with a 2.5 gamma can easily be adapted to sRGB.
The results will vary slightly, but the idea is the same. This is left as an
exercise to the reader :).)

The attachment shows a comparison of a series of images computed incorrectly
(ignoring the gamma) and correctly (done in linear space).

Now, about canvas. Because canvas can do all sort of interesting computations
with colors, including alpha blending, antialiasing and image scaling, it
should definitely do all those computations in a linear space.

The best way to achieve this, is to store all canvas pixels in floating-point
in linear space. All sRGB input colors (being a single value or pixels from an
image) would be converted to linear space and all operations would be done in
floating-point. At display, the resulting image would be converted to sRGB. The
problem with this approach is that it has high memory requirements and would
maybe not be suitable for embedded devices.

Another possibility is to keep the canvas pixels stored in 8-bit sRGB (thus
gamma-encoded), ready for display, but all operations that read from the canvas
would receive color values converted on-the-fly to linear space. Like with the
previous method, all computations would of course be done in linear space
(preferably in floating-point), so any input sRGB color value (including image
pixels) must be converted to linear, but this can of course be done one pixel
at a time. When storing a pixel in the canvas, it would be converted on-the-fly
to sRGB. Hardware acceleration already exists for this, as exposed by the
GL_ARB_framebuffer_sRGB OpenGL extension (promoted to the core in OpenGL 3.0).
The GL_EXT_texture_sRGB extension provides on-the-fly sRGB-to-linear color
conversion and linear filtering for sRGB textures stored with 8 bits per color
channel.

Functions giving access to pixel values (reading and writing) should exist in
two variants: one using 8-bit sRGB values, and one using floating-point (or
16-bit integer) linear values.

With the above features, canvas would ensure the best possible rendering
results, giving the web a powerful high-quality graphical component (even
better than what most stand-alone applications frameworks are providing).


Here are some references on the subject.

Wikipedia - sRGB
http://en.wikipedia.org/wiki/SRGB

GPU Gems 3
The Importance of Being Linear
http://http.developer.nvidia.com/GPUGems3/gpugems3_ch24.html

Gamma Correction in Computer Graphics
http://www.teamten.com/lawrence/graphics/gamma/

Gamma error in picture scaling
http://www.4p8.com/eric.brasseur/gamma.html

John Hable - Uncharted 2: HDR Lighting
http://darkchris.ath.cx/papers/Uncharted%202%20-%20HDR%20Lighting.pdf

Gamma FAQ
http://www.poynton.com/GammaFAQ.html

Frequently-questioned answers about gamma - Gamma FQA
http://www.poynton.com/notes/color/GammaFQA.html

Wikipedia - Gamma correction
http://en.wikipedia.org/wiki/Gamma_correction

Simon's Graphics Blog
Gamma-Correct Rendering
http://www.sjbrown.co.uk/2004/05/14/gamma-correct-rendering/

What's wrong with 8-bit linear RGB?
(or, gamma is a feature, not a bug!)
http://tulrich.com/webgl/rgb/linear_vs_srgb.html

Martin Breidt - Be gamma correct!
http://scripts.breidt.net/tutorials.html#gamma

PNG Specification - Gamma Tutorial
http://www.libpng.org/pub/png/spec/1.2/PNG-GammaAppendix.html

Why use sRGB to store images?
http://mysite.verizon.net/spitzak/conversion/whysrgb.html

Yet Another Gamma Correction Page
http://www.graphics.cornell.edu/~westin/gamma/gamma.html


Hugues De Keyzer

-- 
Configure bugmail: http://www.w3.org/Bugs/Public/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
You are the QA contact for the bug.

Received on Thursday, 7 April 2011 21:56:35 UTC