Color Interpolation in SVG

I recently added support for the value linearRGB on SVG property color-interpolation-filters to Inkscape. Here is a short post on what that means:

SVG 1.1 specifies two possible values for the color space used while compositing or while interpolating between two color values (as in a gradient or animation). The values are linearRGB and sRGB. The first, linearRGB, as the name suggests, describes a color space where the brightness (intensity) of a color is proportional to the numerical value that describes that color. This seems at first like an obvious choice. If shining one lamp on a surface produces an intensity of 100 lux, shining two identical lamps produces an intensity of 200 lux. But it turns out that this doesn’t match human vision very well. The eye’s sensors (rods and cones) and the circuitry (neurons) that process the signals are not linear. Doubling the intensity does not appear to the eye as doubling the brightness. When you have only eight bits (or 256 levels) per color channel to store brightness levels you want to maximize the usefulness of those bits. As the eye can distinguish between closer values of brightness at lower values than higher it is better to use a non-linear color space for recording brightness. One way of doing this is to use gamma correction. This can be described by the function:

Vout = A Vinɣ

where Vout is the intensity to be displayed, A is a constant, Vin is the input level. Typical values of ɣ are around 2.2 to 2.4 for TV and computer monitor signals. A secondary motivation for using a ɣ around those values was that it is close to the typical brightness response to the applied voltage of the electron gun in CRTs, making manufacturing of CRTs easier. (With LCD screens, which have very non-linear response functions, the gamma correction is handled by circuitry.)

The sRGB color space uses an effective ɣ of about 2.2. The exact formula is a bit more complicated than that shown above but it is close enough for the purposes here. Below is a comparison between linearRGB and sRGB. The linearRGB example is encoded with an inverse gamma correction so when your display applies the sRGB conversion the result is a linear brightness. Key points: an sRGB value of 0.5 (on a scale of 0 to 1) corresponds to a brightness of about 20% of that for a value of 1 and a brightness of 50% corresponds to an sRGB value of about 0.73.

Two lines with blocks of different brightness.

The top row consists of blocks with linear steps in brightness. The bottom row consists of blocks with even steps in sRGB color space. Note the rather large perceived step in brightness in the top row between the two darkest blocks and the rather small difference in the two lightest. How different the blocks look will depend on how your screen is calibrated (or not calibrated).

When interpolating between colors, which color space should one use?

Consider a linear gradient between red and blue. If one interpolates between the two colors using sRGB, the middle of the gradient appears dark. This is easy to understand. Consider one color component (red or blue) where the maximum value is 1. At the middle, the interpolated value is half the maximum value (0.5). If the interpolation is done in sRGB, this corresponds to a brightness of about 20% of the maximum value, not the 50% one would first expect. To get the expected value, one needs to interpolate in linearRGB space and then encode that value in sRGB color space (0.73 as discussed above). Clearly, using linearRGB while interpolating gives a better result.

Two gradients using different color spaces for interpolation.

The top gradient was created using interpolation in the sRGB color space while the bottom was created using linearRGB.

So why not use linearRGB in SVG drawings? The primary one is that browsers don’t support it! In addition most (all?) graphics libraries don’t support it. It is not part of the PostScript and PDF standards nor the Cairo rendering library that Inkscape (as of version 0.49) uses for rendering. The Batik based SVG viewer Squiggle does implement linearRGB interpolation for gradients but not for compositing. Here is a simple test:

Three gradients using different values of 'color-interpolation'.

SVG test file. The top gradient uses the SVG default interpolation (sRGB), the middle gradient uses sRGB interpolation, and the bottom uses linearRGB. If the bottom gradient looks like the other two then your browser doesn’t implement linearRGB interpolation.

Using linearRGB would also slow down rendering as objects would have to be converted from sRGB colorspace to linearRGB for compositing and the back to sRGB for display. And, for most purposed, using sRGB for compositing works well enough.

There is one place where browers do support linearRGB and that is in filters. In fact, the default color space for SVG 1.1 filters is linearRGB. I don’t know for sure why the writers of the original SVG specification chose to make that the default color space for filters while using sRGB for everything else. Perhaps it was viewed that filters would be used most frequently to build up complex effects that simulate point light sources shining on protrusions which would naturally call for using a linear color space. The introductory example filter in the SVG 1.1 specification shows just such an effect.

Up until now, Inkscape rendered all filters using the sRGB color space. This lead to rendering differences between browers and Inkscape for non-Inkscape created filters (Inkscape for some time now has set color-interpolation-filters to sRGB when using an Inkscape stock filter effect or when creating a filter effect inside Inkscape).

Should you expect Inkscape to support linearRGB outside of filters? Probably not. Filters are always rendered as bitmaps and it was fairly easy to add code to track the color space of the intermediate bitmap surfaces used while building up a filter effect and make the necessary conversions. Outside of filters, Inkscape uses direct calls to the Cairo rendering library and Cairo knows nothing about color spaces so it would be quite difficult to implement mixed color spaces (using sRGB some places and linearRGB other places). And as browers don’t currently support linearRGB, it would not be all that useful.

A variety of squares with tests for sRGB and linearRGB interpolation in filters.

SVG test file. For each tested filter primitive there are two sets of squares; ‘color-interpolation-filters’ is set to sRGB on the left and linearRGB on the right. A test passes if only one large square is seen. It fails if the inner square is a different color than the outer. Several of the tests rely on feFlood be rendered correctly. There is some discussion as to how the color in feFlood (as well as the lighting primitives) should be rendered so not all the tests may be valid. There may also be artifacts if the image isn’t viewed at a 100% scale and even at 100% the two vertical lines in the feConvolveMatrix filter are expected.

This entry was posted in Inkscape, SVG. Bookmark the permalink.

One Response to Color Interpolation in SVG

  1. John Cliff says:

    Just a heads up that the gradient test and the color matrix and merge do render correctly in linearRGB with chrome.