[cairo] How to do non premultiplied alpha <=> premultiplied alpha conversion

Bertram Felgenhauer bertram.felgenhauer at googlemail.com
Thu Aug 25 18:31:27 PDT 2005

We were discussing this on IRC:

<biesi> what's the correct way to convert a double color to an int?
  * 255 and round, or * 255 and truncate?
<ds> imo, *256, truncate, clamp
<int-e> at least that's what you get if you divide 0..1 into 256 equal
<int-e> *255 and truncate will only give you 255 for exact 1.0; *255 and
  round will give you 0 and 255 half as often than the other values
  (assuming uniformely distributed real numbers)

This lead to a discussion how to do alpha multiplication (for
premultiplied alpha) and its inverse correctly.

Here's an idea:

Let I={0,...,255}, D=[0.0,1.0]. I represents discrete color values; D
represents the ideal, continuous color spectrum.

Given transformations. d: I -> D and i: D -> I with desired rounding
properties (more on that below), we can define

     ALPHA(x, a) = i(d(x)*d(a))


     INV_ALPHA = i(d(x)/d(a)).

The best transformation from i to d seems to be to divide D into 256 equal
length intervals; the corresponding formula is

   i(x) = clamp(floor(x*256.0))

where clamp clamps values to 0..255 (see above).

Note: Formulas in this mail are C expressions; if constants are real values,
that will be denoted by using a float constant (with a .). If all involved
terms are integers, division truncates.

There are (at least) two interesting choices for d:

1) d(x) = x/255.0

   This transformation allows 0.0 and 1.0 to be represented exactly in the
   discrete data, which is desirable for alpha values and probably for colors
   as well. Note that i(d(x)) = x for all x.

   Using this definition, the following formulas can be obtained:

   ALPHA(x, a)     = i(d(x)*d(a))
                   = clamp(floor((x/255.0)*(a/255.0)*256.0))
                   = clamp(floor((x*a*256.0/255.0)/255.0)))
                   = clamp(floor(x*a*(255.0/254+1/254.0-1/255.0)/255.0))
                   = clamp(floor((x*a)/254.0 - (x*a)/(254.0*255.0*255.0)))

   the attached program verifies that

   ALPHA(x, a)     = clamp((x*a-1)/254)

   INV_ALPHA(x, a) = i(d(x)/d(a))
                   = clamp(floor(color/255.0)/(alpha/255.0)*256)
                   = clamp(color*256/alpha)

2) d(x) = (x+0.5)/256.0

   This transformation minimizes the average quantization error introduced
   by d. Of course, i(d(x)) = x for all x.

   ALPHA(x, a)     = i(d(x)*d(a))
                   = clamp(floor((x+0.5)/256.0*(a+0.5)/256.0)*256.0)
                   = clamp(floor((x*a+0.5*x+0.5*a+0.25)/256))
                   = (x*a+(x+a)/2)/256

   INV_ALPHA(x, a) = i(d(x)/d(a))
                   = clamp(floor((x+0.5)/(a+0.5)*256.0))
                   = clamp((512*x+256)/(2*a+1))

Another possibility is to combine the two different formulas:

3) use 1) for alpha values, 2) for color values. While this seems strange
   at first, being able to represent alpha=0 and alpha=1 exactly seems to
   be much more important than being able to represent color values 0 and 1

   ALPHA(x, a)     = i(d2(x)*d1(a))
                   = clamp(floor((x+0.5)/256.0*a/255.0*256.0))
                   = clamp(floor((x+0.5)*a/255.0))
                   = (x*a+a/2)/255

   (The clamp is not necessary because d2(x)<1.0 for all x.)

   INV_ALPHA(x, a) = i(d2(x)/d1(a))
                   = clamp(floor((x+0.5) / 256.0 / a * 255.0) * 256.0))
                   = clamp(floor((255.0*x+127.5)/a))
                   = clamp((255*x+127)/a)

3) is my favorite choice at the moment.


Bertram Felgenhauer
-------------- next part --------------
A non-text attachment was scrubbed...
Name: alpha_test.c
Type: application/octet-stream
Size: 952 bytes
Desc: not available
Url : http://lists.freedesktop.org/archives/cairo/attachments/20050826/b24b464d/alpha_test.obj

More information about the cairo mailing list