summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSøren Sandmann Pedersen <ssp@redhat.com>2013-01-24 02:18:51 -0500
committerSøren Sandmann Pedersen <ssp@redhat.com>2013-01-24 02:18:51 -0500
commit905997574e05f4471a54d647ede49f30a8336fb9 (patch)
tree6eaafcb00e1bc7a28434cec2e726e8846825fd56
parent15e3121a17ac1d00df3f03bcea945c36148de301 (diff)
more blending
-rw-r--r--blend.txt229
-rw-r--r--pics.c4
2 files changed, 132 insertions, 101 deletions
diff --git a/blend.txt b/blend.txt
index 3f81a59..fede834 100644
--- a/blend.txt
+++ b/blend.txt
@@ -20,7 +20,8 @@ Consider these two images:
SRC ("triangle") DEST ("ampersand")
-When we combine them, each pixel can be divided into four regions:
+When we combine them, each pixel of the result can be divided into
+four regions:
\ /
\ /
@@ -40,12 +41,12 @@ color, and the 'both' region is filled with the destination color like
this:
\/
- /\
+ /\
the effect is as if the destination image is trimmed to match the
source image, and then held up in front of it:
- SRC dest_atop DEST
+ SRC dest_atop DEST
The Porter/Duff operator that does this is called "Dest Atop".
@@ -59,34 +60,73 @@ either 'source' or 'blank'. The destination-only region can be either
The formula for the operators is a linear combination of the contents
of the four regions, where the weights are the areas of each region:
- A_src * { C_s, 0 } + A_dest * { C_d, 0 } + A_both * { C_s, C_d, 0 }
+ A_src * [s] + A_dest * [d] + A_both * [b]
-Where C_s is the source pixel and C_d is the destination pixel. The
-areas are given by these formulas:
+Where [s] is either 0 or the color of the source pixel, [d] either 0
+or the color of the destination pixel, and [b] is either 0, the color
+of the source pixel, or the color of the destination pixel. The areas
+are given by these formulas:
A_src = as * (1 - ad)
A_dst = ad * (1 - as)
A_both = as * ad
-The resulting alpha channel is computed in a similar way. There is
-full coverage in all regions that are not blank:
+The resulting value corresponds to the amount of light reflected by
+the covered part of the pixel, so in order to get the right color
+value, we have to divide by alpha. Since divisions are expensive and
+since source and destination inputs have to be multiplied with their
+corresponding alpha values anyway, pixels are often stored in a format
+where all the color channels have been multiplied with the alpha
+channel.
- A_src * { 1, 0 } + A_dest * ( 1, 0 } + A_both * { 1, 1, 0 }
+With this format, the formula becomes:
-FIXME: something about alpha channels and premultiplication.
+ (1 - ad) * [s] + (1 - as) * [d] + as * ad * [b]
-It is important to understand that despite being referred to as alpha
-"blending", and despite alpha often being used to model opacity,
-conceptually Porter/Duff is not a way to blend the source and
-destination shapes. It is a way to overlay, combine and cut them as if
-they were pieces of cardboard. The only places where source and
-destination pixels are actually blended is where the antialiased edges
-meet.
+where [s] and [d] are the premultiplied source and destination colors,
+and [b] is either 0, or one of the premultiplied source or destination
+colors divided by their corresponding alpha channel (this division is
+then canceled by either the /as/ or the /ad/ in front of [b]).
+
+The alpha channel is computed with the same formula. In this case [s]
+is either 0 or as, [d] is either 0 or ad, and [b] is either 0, or
+as/as = 1, or ad/ad = 1.
+
+In other words, with premultiplied pixels, this formula:
+
+ (1 - ad) * [s] + (1 - as) * [d] + as * ad * [b]
+
+works for all four channels of a premultiplied pixel.
+
+Here is a table of all the Porter/Duff operators:
+
+ [s] [d] [b]
+
+ Src s 0 s/as
+ Atop 0 d s/as
+ Over s d s/as
+ In 0 0 s/as
+ Out s 0 0
+ Dest 0 d d/ad
+ DestAtop s 0 d/ad
+ DestOver s d d/as
+ DestIn 0 0 d/ad
+ DestOut 0 d 0
+ Clear 0 0 0
+ Xor s d 0
-Here are all of the twelve Porter/Duff operators:
+And here is how they look:
[table.png]
+It is important to understand that despite being referred to as alpha
+blending, and despite alpha often being used to model opacity,
+conceptually Porter/Duff is not a way to blend the source and
+destination shapes. It is instead a way to overlay, combine and cut
+them as if they were pieces of cardboard. The only places where source
+and destination pixels are actually blended is where the antialiased
+edges meet.
+
Blending
@@ -116,15 +156,14 @@ And the result looks like this:
src ColorDodge dest
-Unlike the regular Over operator, in this case there is a substantial
-piece of the output where the source and the destination are actually
-mixed.
+Unlike with the regular Over operator, the source and destination
+image are actually blended in the area where both are present.
Layers in Photoshop and Gimp are not tailored to each other (except
for layer masks, which we will ignore here), so the compositing of the
layer stack is done with the source-only and destination-only region
-fixed to source and destination respectively. However, there is
-nothing in principle stopping us from setting the source-only and
+set to source and destination respectively. However, there is nothing
+in principle stopping us from setting the source-only and
destination-only regions to blank, but keep the blend mode in the
'both' region so that tailoring could be supported alongside
blending. For example, we could set the 'source' region to blank, the
@@ -133,45 +172,32 @@ ColorDodge:
colordodge-dest-diagram.png
-The result looks like this:
-
- ColorDodge dest
+Which looks like this:
-The default blend mode in Photoshop/Gimp is called 'Normal' and the
-"blend" it computes is simply the top layer, which results in the
-'Over' operator that corresponds to stacking opaque images on top of
-each other.
+ ColorDodge dest
-This idea naturally leads to an extension of the Porter/Duff algebra,
-where the 'both' behavior is selected from a large set of blend
-modes. By using these three simple blend modes:
+With three very simple blend modes:
- Source: B(s,d) = s
- Dest: B(s,d) = d
- Zero: B(s,d) = 0
+ Source: B(s, d) = s
+ Dest: B(s, d) = d
+ Zero: B(s, d) = 0
-we get the twelve original Porter/Duff operators. By selecting other
-blend modes a large number of operators become available that allows
-various trimmings and mixings of the source and destination images.
-Each of these operators is characterized by the answers to three
-questions:
+we can generate all of the original Porter/Duff operators.
- - Is the source tailored to the destination?
- - Is the destination tailored to the source?
- - What happens in the area where both source and destination are present?
+Reinterpreting Porter/Duff in this way leads to a natural
+generalization where the blend mode can be chosen from a large set of
+formulas. Each such formula gives rise to four new compositing
+operators characterized by whether the source and destination are
+blank or contain the corresponding pixel color.
-By answering 'Zero', 'Source', or 'Dest' to the third question the
-original Porter/Duff operators fall out as special cases.
+The general formula is still an area weighted average with
+premultiplied pixels:
-The general formula is still an area weighted average:
+ (1 - ad) * [s] + (1 - as) * [d] + as * ad * B(s / as, d / ad)
- A_src * { C_s, 0 } + A_dest * { C_d, 0 } + A_both * B(C_s, C_d)
-
-where instead of { C_s, C_d, 0 }, the blend mode can now by chosen
-from a large set of formulas. In this way, each new blend modes gives
-rise to four new compositing operators defined by whether the source
-and destination regions are blank or contain the corresponding pixel
-value.
+where [s] and [d] are 0 or the source and destination color
+respectively, but where B(s, d) is no longer restricted to 0, s, and
+d.
Here is a table of the operators that are generated by various blend
modes:
@@ -184,63 +210,69 @@ The three first rows are the original Porter/Duff operators.
Alpha Channels
With the classic Porter/Duff operators the output alpha channel is
-generally computed by plugging 1 into the area weighted average
-whenever a region is filled with either source or destination, and 0
-whenever a region is blank.
+computed by plugging 1 into the area weighted average whenever a
+region is filled with either source or destination, and 0 whenever a
+region is blank. We can also view this as plugging in 1, 1 in the
+blend mode formula:
+
+ Source: B(s, d) = s; B(1, 1) = 1
+ Dest: B(s, d) = d; B(1, 1) = 1
+ Zero: B(s, d) = 0; B(1, 1) = 0
-With most blend modes, the alpha value of the 'both' area can be
-considered 1, ie. 'covered', and so the formula still works. However,
-there are some exceptions. The first one is the blend mode 'Plus':
+Most of the new blend mode formulas satisfy B(1, 1) = 1, which means
+it is tempting to define the alpha computation to be exactly the same
+as the color computation, just as it is for the Porter/Duff
+operators. As long as B(1, 1) = 1, we get:
- B(s, d) = s + d
+ ar = (1 - ad) * as + (1 - as) * ad + as * ad * B(as/as, ad/ad)
+ = (1 - ad) * as + (1 - as) * ad + as * ad * B(1, 1)
-The 'both' variant of this blend mode is almost equivalent to the
-useful operator Add that simply adds all channels together. The reason
-it isn't exactly equivalent is that if we consider the alpha channel 1
-in the 'both' area, the output alpha value will be:
+A very nice result of this is that the ADD operator that simply adds
+all four channels can be defined in this framework with the blend
+mode:
- a_r = a_s + a_d - a_s * a_d
+ Plus: B(s,d) = s + d
-where the Add operator has a_s * a_d. We can fix this by defining that
-the alpha value of the 'both' area to be B(1, 1). That is, plug s=1
-and d=1 into the blend mode formula. Most blend modes produce 1 when
-s=1 and d=1, but Plus produces 2, which means the resulting alpha
-channel is a_s + a_d as desired. This definition also works for the
-'Zero' blend mode: it produces 0 as desired. Using this definition
-also means that when computing with premultiplied pixels, the
-computation is exactly the same for all four channels. To see this,
-consider the blend computation with premultiplied pixels:
+When plugging in 1, 1, you get:
- B_{pre} (s, d) = a_s * a_d * B (C_s / a_s, C_d / a_s)
+ ar = (1 - ad) * as + (1 - as) * ad + as * ad * B(1, 1)
+ = (as - ad * as + ad - as * ad + as * ad * (1 + 1)
+ = as + ad
-and see what happens when you plug in the alpha channel:
+and similarly for the color channels:
- BP(as, ad) = a_s * a_d * B (a_s / a_s, a_d / a_d) = a_s * a_d * B(1, 1)
+ r = (1 - ad) * s + (1 - as) * d + as * ad * (s/as + d/ad)
+ = s - s * ad + d - as * d + ad * s + as * d
+ = s + d
-There are still two annoying exceptions: The blend modes Exclusion and
-Difference do not produce 1 when you plug in (s = 1, d = 1), they
-produce 0. There is some logic to that, because these blend modes are
-concerned with the difference between source and destination, and the
-difference in coverage in the 'both' region (where source and
-destination are both present by definition) would in fact be
-zero. Nonetheless, making the alpha channel 0 in the 'both' region
-would mean that you couldn't see the blended result, so that's not a
-useful option.
+Unfortunately, there are two annoying exceptions: The blend modes
+Exclusion and Difference do not produce 1 when you plug in (s = 1, d =
+1), they produce 0. There is some logic to that, because these blend
+modes are concerned with the difference between source and
+destination, and the difference in coverage in the 'both' region
+(where source and destination are both present by definition) would in
+fact be zero. Nonetheless, making the alpha channel 0 in the 'both'
+region would mean that you couldn't see the blended result, so that's
+not a useful option.
-Finally, the HSL blend modes don't work on a channel-by-channel basis,
-so it will never be possible to do the same computation on all four
-channels. These will just have to be special cased.
+The right solution may be instead to define inverted versions:
-Instead, if we were to actually extend Pixman / cairo / Xrender along
-the lines of this post, it might be better to leave out those blend
-modes and instead add the inverted versions:
+ Inverted Exclusion: B(s,d) = 1 - Exclusion(s,d)
+ = 1 - s - d + 2 * s * d
- Inverted Exclusion: B(s,d) = 1 - Exclusion(s,d)
- Inverted Difference: B(s,d) = 1 - Difference(s,d)
+ Inverted Difference: B(s,d) = 1 - Difference(s,d)
+ = 1 - |s - d|
These will produce the correct alpha channel, and users can still
-achieve the non-inverted blend modes by using solid white with the
-inverted blend modes using two passes if necessary.
+achieve the non-inverted blend modes by using the inverted blend modes
+twice.
+
+Finally, there are four "non-separable" blend modes that don't operate
+on a per-channel basis. These instead conceptually convert the source
+and destination into the HSL representation and then for each of the
+H, S, L channels pick either the source or the destination
+value. There is obviously no way to unify this operation into one
+formula that applies to all four channels.
Conclusion
@@ -266,7 +298,7 @@ There is a proposal for extending CSS
[https://dvcs.w3.org/hg/FXTF/rawfile/tip/compositing/index.html] with
support for blending and compositing that also tries to unify
porter/duff compositing with blend modes. In this scheme a Porter/Duff
-operator and a blend mode are chosen orthogonally.
+operator and a blend mode are chosen orthogonally.
For a given pixel, the source and the destination are blended
according to the chosen blendmode, and then a linear interpolation
@@ -314,4 +346,3 @@ this is not to pick some formula that happens to produce useful
results in certain special cases.
-->
-
diff --git a/pics.c b/pics.c
index dfb9e34..50c0ec2 100644
--- a/pics.c
+++ b/pics.c
@@ -5,8 +5,8 @@
#define SOURCE_COLOR 0.8, 0.3, 0.2
#define DEST_COLOR 0.2, 0.2, 0.4
-#define LIGHT 0.95
-#define DARK 0.9
+#define LIGHT 0.98
+#define DARK 0.95
#define LIGHT_CHECK LIGHT, LIGHT, LIGHT
#define DARK_CHECK DARK, DARK, DARK
#define DARKEN (1 - DARK / LIGHT)