[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
intervals
<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))
and
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
exactly.
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.
Comments?
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