summaryrefslogtreecommitdiff
path: root/blend-wp.txt
blob: 4ecfb1918486622fae42a219309ccdc536578134 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
Note that the master is currently the server text - copy that before
making changes.



<script type="text/javascript"
  src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>In the Porter/Duff compositing algebra, images are equipped with an alpha channel that determines on a per-pixel basis whether the image is there or not. When the alpha channel is 1, the image is fully there, when it is 0, the image isn't there at all. When it is in between, the image is partially there. In other words, the alpha channel describes the the shape of the image, it does not describe opacity. The way to think of images with an alpha channel is as irregularly shaped pieces of cardboard, not as colored glass. Consider these two images:
<blockquote><img src="wp-content/uploads/2013/03/source.png">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="wp-content/uploads/2013/03/dest.png"></blockquote>

When we combine them, each pixel of the result can be divided into four regions:<blockquote><img src="wp-content/uploads/2013/03/diagram.png"></blockquote>

One region where only the source is present, one where only the destination is present, one where both are present, and one where neither is present.

By deciding on what happens in each of the four regions, various effects can be generated. For example, if the destination-only region is treated as blank, the source-only region is filled with the source color, and the 'both' region is filled with the destination color like this:
<blockquote><img src="wp-content/uploads/2013/03/destatop-diagram.png"></blockquote>
The effect is as if the destination image is trimmed to match the source image, and then held up in front of it:
<blockquote><img src="wp-content/uploads/2013/03/destatop.png"></blockquote>
The Porter/Duff operator that does this is called "Dest Atop".

There are twelve of these operators, each one characterized by its behavior in the three regions: source, destination and both (the 'neither' region is always blank). The source-only region can be either 'source' or 'blank'. The destination-only region can be either 'destination' or 'blank'. The 'both' region can be either 'blank', 'source', or 'destination'.

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:
<blockquote>\(A_\text{src} \cdot [s] + A_\text{dest} \cdot [d] + A_\text{both} \cdot [b]\)</blockquote>

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:<blockquote>
\(A_\text{src} = \alpha_\text{s} \cdot (1 - \alpha_\text{d})\)
\(A_\text{dst} = \alpha_\text{d} \cdot (1 - \alpha_\text{s})\)
\(A_\text{both} = \alpha_\text{s} \cdot \alpha_\text{d}\)
</blockquote>

The alpha channel of the result is computed in a similar way. There is
full coverage in all regions that are not blank:
<blockquote>
\(A_\text{src} \cdot [\text{as}] + A_\text{dest} \cdot [\text{ad}] + A_\text{both} \cdot [\text{ab}]\)
</blockquote>
where \([\text{as}]\) and \([\text{ad}]\) are either 0 or 1 depending on whether the source and destination regions are present, and where \([\text{ab}]\) is 0 when the 'both' region is blank, and 1 otherwise.

Here is a table of all the Porter/Duff operators:
<blockquote><table>
<tr><td/><td>\([\text{s}]\)</td><td>\([\text{d}]\)</td><td>\([\text{b}]\)</td></tr>
<tr><td>Src</td><td>\(s\)</td><td>\(0\)</td><td>s</td></tr>
<tr><td>Atop</td><td>\(0\)</td><td>\(d\)</td><td>s</td></tr>
<tr><td>Over</td><td>\(s\)</td><td>\(d\)</td><td>s</td></tr>
<tr><td>In</td><td>\(0\)</td><td>\(0\)</td><td>s</td></tr>
<tr><td>Out</td><td>\(s\)</td><td>\(0\)</td><td>\(0\)</td></tr>
<tr><td>Dest</td><td>\(0\)</td><td>\(d\)</td><td>d</td></tr>
<tr><td>DestAtop</td><td>\(s\)</td><td>\(0\)</td><td>d</td></tr>
<tr><td>DestOver</td><td>\(s\)</td><td>\(d\)</td><td>d</td></tr>
<tr><td>DestIn</td><td>\(0\)</td><td>\(0\)</td><td>d</td></tr>
<tr><td>DestOut</td><td>\(0\)</td><td>\(d\)</td><td>\(0\)</td></tr>
<tr><td>Clear</td><td>\(0\)</td><td>\(0\)</td><td>\(0\)</td></tr>
<tr><td>Xor</td><td>\(s\)</td><td>\(d\)</td><td>\(0\)</td></tr>
</table></blockquote>
And here is how they look:
<blockquote><img src="wp-content/uploads/2013/03/table.png"/></blockquote>
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.

<b>Blending</b>
Photoshop and the Gimp have the concept of layers which are images stacked on top of each other. In Porter/Duff, stacking images on top of each other is done with the "Over" operator, which is also what Photoshop/Gimp use by default to composite layers:
<blockquote><img src="wp-content/uploads/2013/03/over-diagram.png"></blockquote>
Conceptually, two pieces of cardboard are held up with one in front of the other. Neither shape is trimmed, and in places where both are present, only the top layer is visible.

A layer in these programs also has an associated <em>Blend Mode</em> which can be used to modify what happens in places where both are visible. For example, the 'Color Dodge' blend mode computes a mix of source and destination according to this formula:<blockquote>
\(\begin{equation*}
B(s,d)= 
\begin{cases} 0 & \text{if \(d=0\),}
\\
1 & \text{if \(d \ge (1 - s)\),}
\\
d / (1 - s) & \text{otherwise}
\end{cases}
\end{equation*}\)
</blockquote>

The pixel diagram is this:
<blockquote><img src="wp-content/uploads/2013/03/colordodge-diagram.png"></blockquote>
And the result looks like this:
<blockquote><img src="wp-content/uploads/2013/03/colordodge.png"></blockquote>

Unlike with the regular Over operator, in this case there is substantial chunk of the output where the result is actually a mix of the source and destination.

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 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 'destination' region to destination, and the 'both' region to ColorDodge:
<blockquote><img src="wp-content/uploads/2013/03/colordodge-dest-diagram.png"/></blockquote>
The result looks like this:
<blockquote><img src="wp-content/uploads/2013/03/colordodge-dest.png"></blockquote>
Here are all the possibilities that involve a ColorDodge blend mode:
<blockquote>
<img src="wp-content/uploads/2013/03/colordodge-none.png"/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="wp-content/uploads/2013/03/colordodge-source.png"/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="wp-content/uploads/2013/03/colordodge-dest.png"/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src="wp-content/uploads/2013/03/colordodge-both.png"/></blockquote>
With three trivial blend modes:
<table>
<tr><td>Source:</td><td>\(B(s, d) = s\)</td></tr>
<tr><td>Dest:</td><td>\(B(s, d) = d\)</td></tr>
<tr><td>Zero:</td><td>\(B(s, d) = 0\)</td></tr>
</table>
we can generate all of the twelve original Porter/Duff operators.

Reinterpreting Porter/Duff in this way leads to a natural generalization where the blend mode can be chosen from a large set of formulas, where 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.

The general formula is still an area weighted average:
<blockquote>\(A_\text{src} \cdot [s] + A_\text{dest} \cdot [d] + A_\text{both}\cdot B(s, d)\)</blockquote>
where [s] and [d] are the source and destination colors respectively or 0, but where \(B(s, d)\) is no longer restricted to one of \(0\), \(s\), and \(d\), but can instead be chosen from a large set of formulas.

In this way, each new blend mode gives rise to four new compositing operators characterized by whether or the source and destination regions are present or blank.

The output of the alpha channel is the same as before:
<blockquote>\(A_\text{src} \cdot [as] + A_\text{dest} \cdot [ad] + A_\text{both} \cdot [\text{ab}]\)</blockquote>
except that [ab] is now determined by the blend mode. For the Zero blend mode there is no coverage in the both region, so [ab] is 0; for most others, there is full coverage, so [ab] is 1.

Here is a table of the operators that are generated by various blend modes:
<blockquote><img src="wp-content/uploads/2013/03/colordodge-table.png"></blockquote>
The three first rows are the original Porter/Duff operators.


<!--

Alpha Channels


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.

With this format, the formula becomes:

    (1 - ad) * [s] + (1 - as) * [d] + as * ad * [b]

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.



With the classic Porter/Duff operators the output alpha channel is
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

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:

    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)

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:

	Plus:	B(s,d) = s + d

When plugging in 1, 1, you get:

     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 similarly for the color channels:

     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

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.

The right solution may be instead to define inverted versions:

    Inverted Exclusion:		B(s,d) = 1 - Exclusion(s,d)
				       = 1 - s - d + 2 * 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 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

It is possible to generalize the Porter/Duff compositing algebra by
allowing more behaviors in the 'both' region of the Porter/Duff
pixel. If the effect on the alpha channel is defined to be B(1,1),
then all four channels can be computed with the same formula:

  (1 - ad) * [s] * S + (1 - as) * [d] * D + as * ad * B(S/as, D/ad)

where [s] and [d] are 1 or 0 depending on whether the source and
destination are drawn, and B is the blend mode. When B(s,d) is chosen
to be 0, s, or d, the formula reduces to the twelve original
Porter/Duff operators.

-->

<!--

CSS compositing spec

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.

For a given pixel, the source and the destination are blended
according to the chosen blendmode, and then a linear interpolation
between the source and the blended result is computed with the
destination alpha as the interpolation weight. This result is then
used as the source in compositing with the chosen Porter/Duff
operator. The full formula is this:

      Fa * as * [ (1 - ad) * Cs + ad * B(s,d) ] + Fb * ad * Cd

where Fa and Fb are weight factors corresponding to the chosen
Porter/Duff operator.

This formula has some useful properties: If the blend mode is chosen
to be Normal, that is, B(s,d) = Cs, then the formula becomes identical
to whatever Porter/Duff operator is involved. If the Porter/Duff
operator is chosen to be Over, then the formula becomes identical to
the Photoshop/Gimp layer blending. When the operator is Source, the
formula produces the atop-like result shown above.

However, there is no conceptual underlying reason that the formula has
these properties. You can't give a graphical interpretation of it the
way you can with Porter/Duff; the useful properties the formula has
might as well be numerological accidents. In fact, for many operators,
the formula produces nonsensical results.

For example, here is what it looks like when the operator is DEST_OVER
and the blend mode is ColorDodge:

    DEST_OVER / COLOR_DODGE

What is this? It's DEST_OVER, except that the antialiasing is messed
up with a weird purple shadow. There is no situation where this is the
operation you need.

There is also a bunch of operators where the blend mode is simply
ignored: DEST, DEST_IN, DEST_OUT, CLEAR.

The fundamental problem is that it doesn't make any sense to select a
Porter/Duff compositing operators and a blend mode orthogonally. The
Porter/Duff operator and the blend mode both specify what happens in
the region where source and destination overlaps, and if they disagree
on what happens there, it is not clear what happens. The answer to
this is not to pick some formula that happens to produce useful
results in certain special cases.

-->