limage - composite.go
1 package limage
2
3 import (
4 "image/color"
5 "math/rand"
6
7 "vimagination.zapto.org/limage/internal"
8 "vimagination.zapto.org/limage/lcolor"
9 )
10
11 // Composite determines how two layers are composed together
12 type Composite uint32
13
14 // Composite constants
15 const (
16 CompositeNormal Composite = iota
17 CompositeDissolve
18 CompositeBehind
19 CompositeMultiply
20 CompositeScreen
21 CompositeOverlay
22 CompositeDifference
23 CompositeAddition
24 CompositeSubtract
25 CompositeDarkenOnly
26 CompositeLightenOnly
27 CompositeHue
28 CompositeSaturation
29 CompositeColor
30 CompositeValue
31 CompositeDivide
32 CompositeDodge
33 CompositeBurn
34 CompositeHardLight
35 CompositeSoftLight
36 CompositeGrainExtract
37 CompositeGrainMerge
38 CompositeLuminosity
39 CompositePlus
40 CompositeDestinationIn
41 CompositeDestinationOut
42 CompositeSourceAtop
43 CompositeDestinationAtop
44 CompositeColorErase
45 CompositeChroma
46 CompositeLightness
47 CompositeVividLight
48 CompositePinLight
49 CompositeLinearLight
50 CompositeHardMix
51 CompositeExclusion
52 CompositeLinearBurn
53 CompositeLuminance
54 CompositeErase
55 CompositeMerge
56 CompositeSplit
57 CompositePassThrough
58 )
59
60 var compositeNames = [...]string{
61 "Normal",
62 "Dissolve",
63 "Behind",
64 "Multiply",
65 "Screen",
66 "Overlay",
67 "Difference",
68 "Addition",
69 "Subtract",
70 "Darken Only",
71 "Lighten Only",
72 "Hue",
73 "Saturation",
74 "Color",
75 "Value",
76 "Divide",
77 "Dodge",
78 "Burn",
79 "Hard Light",
80 "Soft Light",
81 "Grain Extract",
82 "Grain Merge",
83 "Luminosity",
84 "Plus",
85 "Destination In",
86 "Destination Out",
87 "Source Atop",
88 "Destination Atop",
89 }
90
91 // String returns the name of the composition
92 func (c Composite) String() string {
93 if int(c) < len(compositeNames) {
94 return compositeNames[c]
95 }
96 return compositeNames[0]
97 }
98
99 // Composite performs the composition of two layers
100 func (c Composite) Composite(b, t color.Color) color.Color {
101 bottom := internal.ColourToNRGBA(b)
102 top := internal.ColourToNRGBA(t)
103 var f func(uint16, uint16) uint16
104 switch c {
105 case CompositeMultiply:
106 f = compositeMultiply
107 case CompositeScreen:
108 f = compositeScreen
109 case CompositeOverlay:
110 f = compositeOverlay
111 case CompositeDifference:
112 f = compositeDifference
113 case CompositeAddition:
114 f = compositeAddition
115 case CompositeSubtract:
116 f = compositeSubtract
117 case CompositeDarkenOnly:
118 f = compositeDarkenOnly
119 case CompositeLightenOnly:
120 f = compositeLightenOnly
121 case CompositeDivide:
122 f = compositeDivide
123 case CompositeDodge:
124 f = compositeDodge
125 case CompositeBurn:
126 f = compositeBurn
127 case CompositeHardLight:
128 f = compositeHardLight
129 case CompositeSoftLight:
130 f = compositeSoftLight
131 case CompositeGrainExtract:
132 f = compositeGrainExtract
133 case CompositeGrainMerge:
134 f = compositeGrainMerge
135 case CompositeBehind:
136 return bottom
137 case CompositeDissolve:
138 return compositeDissolve(bottom, top)
139 case CompositeHue:
140 return compositeHue(bottom, top)
141 case CompositeSaturation:
142 return compositeSaturation(bottom, top)
143 case CompositeColor:
144 return compositeColor(bottom, top)
145 case CompositeValue:
146 return compositeValue(bottom, top)
147 case CompositeLuminosity:
148 return compositeColor(top, bottom)
149 case CompositePlus:
150 return compositePlus(bottom, top)
151 case CompositeDestinationIn:
152 return compositeDstIn(bottom, top)
153 case CompositeDestinationOut:
154 return compositeDstOut(bottom, top)
155 case CompositeSourceAtop:
156 return compositeAtop(bottom, top)
157 case CompositeDestinationAtop:
158 return compositeAtop(top, bottom)
159 default: //Normal
160 return compositeNormal(bottom, top)
161 }
162 if bottom.A == 0 {
163 return color.NRGBA{}
164 }
165 ma := internal.Min(bottom.A, top.A)
166 return color.NRGBA64{
167 R: blend(bottom.A, bottom.R, ma, f(bottom.R, top.R)),
168 G: blend(bottom.A, bottom.G, ma, f(bottom.R, top.G)),
169 B: blend(bottom.A, bottom.B, ma, f(bottom.R, top.B)),
170 A: bottom.A,
171 }
172 }
173
174 func compositeNormal(bottom, top color.NRGBA64) color.NRGBA64 {
175 if bottom.A == 0 && top.A == 0 {
176 return color.NRGBA64{}
177 }
178 return color.NRGBA64{
179 R: blend(bottom.A, bottom.R, top.A, top.R),
180 G: blend(bottom.A, bottom.G, top.A, top.G),
181 B: blend(bottom.A, bottom.B, top.A, top.B),
182 A: uint16(0xffff - (0xffff-uint32(bottom.A))*(0xffff-uint32(top.A))/0xffff),
183 }
184 }
185
186 func compositeDissolve(bottom, top color.NRGBA64) color.NRGBA64 {
187 if uint16(rand.Int31n(0xffff)) < bottom.A {
188 top.A = 0xffff
189 return top
190 }
191 return bottom
192 }
193
194 func compositeMultiply(x, y uint16) uint16 {
195 return uint16(uint32(x) * uint32(y) / 0xffff)
196 }
197
198 func compositeScreen(x, y uint16) uint16 {
199 return uint16(0xffff - uint32(0xffff-x)*uint32(0xffff-y)/0xffff)
200 }
201
202 func compositeOverlay(ax, ay uint16) uint16 {
203 x := uint32(ax)
204 y := uint32(ay)
205 t := 0xffff - y
206 return uint16((0xffff-y)*(x*x/0xffff)/0xffff + y*(0xffff-(t*t/0xffff))/0xffff)
207 }
208
209 func compositeDifference(x, y uint16) uint16 {
210 if x > y {
211 return x - y
212 }
213 return y - x
214 }
215
216 func compositeAddition(x, y uint16) uint16 {
217 return clamp(uint32(x) + uint32(y))
218 }
219
220 func compositeSubtract(x, y uint16) uint16 {
221 if y > x {
222 return 0
223 }
224 return x - y
225 }
226
227 func compositeDarkenOnly(x, y uint16) uint16 {
228 return internal.Min(x, y)
229 }
230
231 func compositeLightenOnly(x, y uint16) uint16 {
232 return internal.Max(x, y)
233 }
234
235 func compositeDivide(x, y uint16) uint16 {
236 if y == 0 {
237 if x == 0 {
238 return 0
239 }
240 return 0xffff
241 }
242 return clamp(0xffff * uint32(x) / uint32(y))
243 }
244
245 func compositeDodge(x, y uint16) uint16 {
246 if y == 0xffff {
247 if x == 0 {
248 return 0
249 }
250 return 0xffff
251 }
252 return clamp(0xffff * uint32(x) / (0xffff - uint32(y)))
253 }
254
255 func compositeBurn(x, y uint16) uint16 {
256 if y == 0 {
257 if x == 0xffff {
258 return 0
259 }
260 return 0xffff
261 }
262 return clamp(0xffff - 0xffff*(0xffff-uint32(x))/uint32(y))
263 }
264
265 func compositeHardLight(x, y uint16) uint16 {
266 if y < 0x7fff {
267 return uint16(uint32(x) * (uint32(y) << 1) / 0xffff)
268 }
269 return uint16(0xffff - (0xffff-uint32(x))*(0x1fffe-uint32(y)<<1)/0xffff)
270 }
271
272 func compositeSoftLight(x, y uint16) uint16 {
273 return compositeOverlay(x, y)
274 }
275
276 func compositeGrainExtract(ax, ay uint16) uint16 {
277 x := uint32(ax)
278 y := uint32(ay)
279 if x+0x7fff < y {
280 return 0
281 }
282 return clamp(x - y + 0x7fff)
283 }
284
285 func compositeGrainMerge(x, y uint16) uint16 {
286 return clamp(uint32(x) + uint32(y) + 0x7fff)
287 }
288
289 func compositeHue(bottom, top color.NRGBA64) color.Color {
290 br, bg, bb, _ := top.RGBA()
291 if br == bg && br == bb {
292 return bottom
293 }
294 a := lcolor.RGBToHSV(bottom)
295 b := lcolor.RGBToHSV(top)
296 a.H = b.H
297 return a
298 }
299
300 func compositeSaturation(bottom, top color.NRGBA64) color.Color {
301 a := lcolor.RGBToHSV(bottom)
302 b := lcolor.RGBToHSV(top)
303 a.S = b.S
304 return a
305 }
306
307 func compositeColor(bottom, top color.NRGBA64) color.Color {
308 a := lcolor.RGBToHSL(bottom)
309 b := lcolor.RGBToHSL(top)
310 b.L = a.L
311 return b
312 }
313
314 func compositeValue(bottom, top color.NRGBA64) color.Color {
315 a := lcolor.RGBToHSV(bottom)
316 b := lcolor.RGBToHSV(top)
317 a.V = b.V
318 return a
319 }
320
321 func compositePlus(bottom, top color.NRGBA64) color.NRGBA64 {
322 return compositeNormal(bottom, top)
323 }
324
325 func compositeDstIn(bottom, top color.NRGBA64) color.NRGBA64 {
326 return compositeNormal(bottom, top)
327 }
328
329 func compositeDstOut(bottom, top color.NRGBA64) color.NRGBA64 {
330 return compositeNormal(bottom, top)
331 }
332
333 func compositeAtop(bottom, top color.NRGBA64) color.NRGBA64 {
334 return compositeNormal(bottom, top)
335 }
336
337 func clamp(n uint32) uint16 {
338 if n > 0xffff {
339 return 0xffff
340 }
341 return uint16(n)
342 }
343
344 func blend(aa1, ax1, aa2, ax2 uint16) uint16 {
345 a1 := uint32(aa1)
346 x1 := uint32(ax1)
347 a2 := uint32(aa2)
348 x2 := uint32(ax2)
349 k := 0xffff * a2 / (0xffff - (0xffff-a1)*(0xffff-a2)/0xffff)
350 return uint16((0xffff-k)*x1/0xffff + k*x2/0xffff)
351 }
352