pixel-data-js 0.5.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,651 @@
1
+ import type { BlendColor32, Color32 } from '../_types'
2
+ import { BlendMode } from './blend-modes'
3
+
4
+ export const overwritePerfect: BlendColor32 = (src, _dst) => src
5
+
6
+ export const sourceOverPerfect: BlendColor32 = (src, dst) => {
7
+ const sa = (src >>> 24) & 0xFF
8
+ if (sa === 255) return src
9
+ if (sa === 0) return dst
10
+
11
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
12
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
13
+
14
+ const da = (dst >>> 24) & 0xFF
15
+
16
+ // Alpha Lerp inlined
17
+ const invA = 255 - sa
18
+ const r = (sr * sa + dr * invA) / 255 | 0
19
+ const g = (sg * sa + dg * invA) / 255 | 0
20
+ const b = (sb * sa + db * invA) / 255 | 0
21
+ const a = (255 * sa + da * invA) / 255 | 0
22
+
23
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
24
+ }
25
+
26
+ export const darkenPerfect: BlendColor32 = (src, dst) => {
27
+ const sa = (src >>> 24) & 0xFF
28
+ if (sa === 0) return dst
29
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
30
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
31
+
32
+ const br = sr < dr ? sr : dr
33
+ const bg = sg < dg ? sg : dg
34
+ const bb = sb < db ? sb : db
35
+
36
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
37
+
38
+ // Alpha Lerp inlined
39
+ const invA = 255 - sa
40
+
41
+ const r = (br * sa + dr * invA) / 255 | 0
42
+ const g = (bg * sa + dg * invA) / 255 | 0
43
+ const b = (bb * sa + db * invA) / 255 | 0
44
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
45
+
46
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
47
+ }
48
+
49
+ /** (src * dst) / 255 */
50
+ export const multiplyPerfect: BlendColor32 = (src, dst) => {
51
+ const sa = (src >>> 24) & 0xFF
52
+ if (sa === 0) return dst
53
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
54
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
55
+
56
+ // Consistent floor rounding for all channels
57
+ const br = (sr * dr / 255) | 0
58
+ const bg = (sg * dg / 255) | 0
59
+ const bb = (sb * db / 255) | 0
60
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
61
+
62
+ // Alpha Lerp inlined
63
+ const invA = 255 - sa
64
+ const da = (dst >>> 24) & 0xFF
65
+
66
+ const r = (br * sa + dr * invA) / 255 | 0
67
+ const g = (bg * sa + dg * invA) / 255 | 0
68
+ const b = (bb * sa + db * invA) / 255 | 0
69
+ const a = (255 * sa + da * invA) / 255 | 0
70
+
71
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
72
+ }
73
+
74
+ /** 255 - (255-src)/dst */
75
+ export const colorBurnPerfect: BlendColor32 = (src, dst) => {
76
+ const sa = (src >>> 24) & 0xFF
77
+ if (sa === 0) return dst
78
+
79
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
80
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
81
+ const br = dr === 255 ? 255 : sr === 0 ? 0 : Math.max(0, (255 - (((255 - dr) * 255 / sr) | 0)))
82
+ const bg = dg === 255 ? 255 : sg === 0 ? 0 : Math.max(0, (255 - (((255 - dg) * 255 / sg) | 0)))
83
+ const bb = db === 255 ? 255 : sb === 0 ? 0 : Math.max(0, (255 - (((255 - db) * 255 / sb) | 0)))
84
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
85
+
86
+ const invA = 255 - sa
87
+ const da = (dst >>> 24) & 0xFF
88
+
89
+ const r = (br * sa + dr * invA) / 255 | 0
90
+ const g = (bg * sa + dg * invA) / 255 | 0
91
+ const b = (bb * sa + db * invA) / 255 | 0
92
+ const a = (255 * sa + da * invA) / 255 | 0
93
+
94
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
95
+ }
96
+
97
+ /** src + dst - 255 */
98
+ export const linearBurnPerfect: BlendColor32 = (src, dst) => {
99
+ const sa = (src >>> 24) & 0xFF
100
+ if (sa === 0) return dst
101
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
102
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
103
+
104
+ // Math: Base + Blend - 255 (clamped to 0)
105
+ const brU = dr + sr - 255
106
+ const br = brU < 0 ? 0 : brU
107
+ const bgU = dg + sg - 255
108
+ const bg = bgU < 0 ? 0 : bgU
109
+ const bbU = db + sb - 255
110
+ const bb = bbU < 0 ? 0 : bbU
111
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
112
+
113
+ // Alpha Lerp inlined
114
+ const invA = 255 - sa
115
+ const r = (br * sa + dr * invA) / 255 | 0
116
+ const g = (bg * sa + dg * invA) / 255 | 0
117
+ const b = (bb * sa + db * invA) / 255 | 0
118
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
119
+
120
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
121
+ }
122
+
123
+ export const darkerPerfect: BlendColor32 = (src, dst) => {
124
+ const sa = (src >>> 24) & 0xFF
125
+ if (sa === 0) return dst
126
+
127
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
128
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
129
+
130
+ // 1. Calculate Luminosity (Photoshop Weights: R:0.3, G:0.59, B:0.11)
131
+ // Scaled by 256 for integer math: 77, 151, 28
132
+ const lumSrc = (sr * 77 + sg * 151 + sb * 28)
133
+ const lumDst = (dr * 77 + dg * 151 + db * 28)
134
+
135
+ // 2. Selection Logic
136
+ // Pick the perceptually darker pixel
137
+ let br, bg, bb
138
+ if (lumSrc < lumDst) {
139
+ br = sr
140
+ bg = sg
141
+ bb = sb
142
+ } else {
143
+ br = dr
144
+ bg = dg
145
+ bb = db
146
+ }
147
+
148
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
149
+
150
+ // 3. Alpha Lerp inlined
151
+ const invA = 255 - sa
152
+ const r = (br * sa + dr * invA) / 255 | 0
153
+ const g = (bg * sa + dg * invA) / 255 | 0
154
+ const b = (bb * sa + db * invA) / 255 | 0
155
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
156
+
157
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
158
+ }
159
+
160
+ /** Math.max(src, dst) */
161
+ export const lightenPerfect: BlendColor32 = (src, dst) => {
162
+ const sa = (src >>> 24) & 0xFF
163
+ if (sa === 0) return dst
164
+
165
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
166
+ const br = (src & 0xFF) > dr ? (src & 0xFF) : dr
167
+ const bg = ((src >>> 8) & 0xFF) > dg ? ((src >>> 8) & 0xFF) : dg
168
+ const bb = ((src >>> 16) & 0xFF) > db ? ((src >>> 16) & 0xFF) : db
169
+
170
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
171
+ // Alpha Lerp inlined
172
+ const invA = 255 - sa
173
+ const r = (br * sa + dr * invA) / 255 | 0
174
+ const g = (bg * sa + dg * invA) / 255 | 0
175
+ const b = (bb * sa + db * invA) / 255 | 0
176
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
177
+
178
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
179
+ }
180
+
181
+ /**
182
+ * 255 - ((255 - src) * (255 - dst))
183
+ */
184
+ export const screenPerfect: BlendColor32 = (src, dst) => {
185
+ const sa = (src >>> 24) & 0xFF
186
+ if (sa === 0) return dst
187
+
188
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
189
+
190
+ const br = 255 - (((255 - (src & 0xFF)) * (255 - dr) / 255) | 0)
191
+ const bg = 255 - (((255 - ((src >>> 8) & 0xFF)) * (255 - dg) / 255) | 0)
192
+ const bb = 255 - (((255 - ((src >>> 16) & 0xFF)) * (255 - db) / 255) | 0)
193
+
194
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
195
+
196
+ // Alpha Lerp inlined
197
+ const invA = 255 - sa
198
+
199
+ const r = (br * sa + dr * invA) / 255 | 0
200
+ const g = (bg * sa + dg * invA) / 255 | 0
201
+ const b = (bb * sa + db * invA) / 255 | 0
202
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
203
+
204
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
205
+ }
206
+
207
+ /** src === 255 ? 255 : Math.min(255, (dst << 8) / (255 - src)) */
208
+ export const colorDodgePerfect: BlendColor32 = (src, dst) => {
209
+ const sa = (src >>> 24) & 0xFF
210
+ if (sa === 0) return dst
211
+
212
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
213
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
214
+
215
+ const br = sr === 255 ? 255 : Math.min(255, ((dr * 255 / (255 - sr)) | 0))
216
+ const bg = sg === 255 ? 255 : Math.min(255, ((dg * 255 / (255 - sg)) | 0))
217
+ const bb = sb === 255 ? 255 : Math.min(255, ((db * 255 / (255 - sb)) | 0))
218
+
219
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
220
+
221
+ // Alpha Lerp inlined
222
+ const invA = 255 - sa
223
+ const r = (br * sa + dr * invA) / 255 | 0
224
+ const g = (bg * sa + dg * invA) / 255 | 0
225
+ const b = (bb * sa + db * invA) / 255 | 0
226
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
227
+
228
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
229
+ }
230
+
231
+ /** src + dst */
232
+ export const linearDodgePerfect: BlendColor32 = (src, dst) => {
233
+ const sa = (src >>> 24) & 0xFF
234
+ if (sa === 0) return dst
235
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
236
+
237
+ const brU = (src & 0xFF) + dr
238
+ const br = brU > 255 ? 255 : brU
239
+ const bgU = ((src >>> 8) & 0xFF) + dg
240
+ const bg = bgU > 255 ? 255 : bgU
241
+ const bbU = ((src >>> 16) & 0xFF) + db
242
+ const bb = bbU > 255 ? 255 : bbU
243
+
244
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
245
+
246
+ // Alpha Lerp inlined
247
+ const invA = 255 - sa
248
+ const r = (br * sa + dr * invA) / 255 | 0
249
+ const g = (bg * sa + dg * invA) / 255 | 0
250
+ const b = (bb * sa + db * invA) / 255 | 0
251
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
252
+
253
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
254
+ }
255
+
256
+ export const lighterPerfect: BlendColor32 = (src, dst) => {
257
+ const sa = (src >>> 24) & 0xFF
258
+ if (sa === 0) return dst
259
+
260
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
261
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
262
+
263
+ // Calculate Luminosity (Photoshop uses Weights: R:0.3, G:0.59, B:0.11)
264
+ // We use integer math (scaled by 256) for speed.
265
+ const lumSrc = (sr * 77 + sg * 151 + sb * 28)
266
+ const lumDst = (dr * 77 + dg * 151 + db * 28)
267
+
268
+ // Selection Logic (Base result)
269
+ let br, bg, bb
270
+ if (lumSrc > lumDst) {
271
+ br = sr
272
+ bg = sg
273
+ bb = sb
274
+ } else {
275
+ br = dr
276
+ bg = dg
277
+ bb = db
278
+ }
279
+
280
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
281
+
282
+ // Alpha Lerp
283
+ const invA = 255 - sa
284
+ const r = (br * sa + dr * invA) / 255 | 0
285
+ const g = (bg * sa + dg * invA) / 255 | 0
286
+ const b = (bb * sa + db * invA) / 255 | 0
287
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
288
+
289
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
290
+ }
291
+
292
+ /** src < 128 ? (2 * src * dst) : (255 - 2 * (255 - src) * (255 - dst)) */
293
+ export const overlayPerfect: BlendColor32 = (src, dst) => {
294
+ const sa = (src >>> 24) & 0xFF
295
+ if (sa === 0) return dst
296
+
297
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
298
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
299
+ const br = dr < 128 ? (2 * sr * dr / 255) | 0 : 255 - ((2 * (255 - sr) * (255 - dr) / 255) | 0)
300
+ const bg = dg < 128 ? (2 * sg * dg / 255) | 0 : 255 - ((2 * (255 - sg) * (255 - dg) / 255) | 0)
301
+ const bb = db < 128 ? (2 * sb * db / 255) | 0 : 255 - ((2 * (255 - sb) * (255 - db) / 255) | 0)
302
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
303
+
304
+ // Alpha Lerp inlined
305
+ const invA = 255 - sa
306
+ const r = (br * sa + dr * invA) / 255 | 0
307
+ const g = (bg * sa + dg * invA) / 255 | 0
308
+ const b = (bb * sa + db * invA) / 255 | 0
309
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
310
+
311
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
312
+ }
313
+
314
+ /** ((255 - dst) * ((src * dst) >> 8) + dst * (255 - (((255 - src) * (255 - dst)) >> 8))) >> 8 */
315
+ export const softLightPerfect: BlendColor32 = (src, dst) => {
316
+ const sa = (src >>> 24) & 0xFF
317
+ if (sa === 0) return dst
318
+
319
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
320
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
321
+ const br = (((255 - dr) * ((sr * dr / 255) | 0) + dr * (255 - (((255 - sr) * (255 - dr) / 255) | 0))) / 255) | 0
322
+ const bg = (((255 - dg) * ((sg * dg / 255) | 0) + dg * (255 - (((255 - sg) * (255 - dg) / 255) | 0))) / 255) | 0
323
+ const bb = (((255 - db) * ((sb * db / 255) | 0) + db * (255 - (((255 - sb) * (255 - db) / 255) | 0))) / 255) | 0
324
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
325
+
326
+ // Alpha Lerp inlined
327
+ const invA = 255 - sa
328
+ const r = (br * sa + dr * invA) / 255 | 0
329
+ const g = (bg * sa + dg * invA) / 255 | 0
330
+ const b = (bb * sa + db * invA) / 255 | 0
331
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
332
+
333
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
334
+ }
335
+
336
+ /** If src < 128 (50% gray), Multiply; otherwise, Screen */
337
+ export const hardLightPerfect: BlendColor32 = (src, dst) => {
338
+ const sa = (src >>> 24) & 0xFF
339
+ if (sa === 0) return dst
340
+
341
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
342
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
343
+ const br = sr < 128 ? (2 * sr * dr / 255) | 0 : 255 - ((2 * (255 - sr) * (255 - dr) / 255) | 0)
344
+ const bg = sg < 128 ? (2 * sg * dg / 255) | 0 : 255 - ((2 * (255 - sg) * (255 - dg) / 255) | 0)
345
+ const bb = sb < 128 ? (2 * sb * db / 255) | 0 : 255 - ((2 * (255 - sb) * (255 - db) / 255) | 0)
346
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
347
+
348
+ // Alpha Lerp inlined
349
+ const invA = 255 - sa
350
+ const r = (br * sa + dr * invA) / 255 | 0
351
+ const g = (bg * sa + dg * invA) / 255 | 0
352
+ const b = (bb * sa + db * invA) / 255 | 0
353
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
354
+
355
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
356
+ }
357
+
358
+ /**
359
+ * If src < 128: Burn(dst, 2 * src)
360
+ * If src >= 128: Dodge(dst, 2 * (src - 128))
361
+ */
362
+ export const vividLightPerfect: BlendColor32 = (src, dst) => {
363
+ const sa = (src >>> 24) & 0xFF
364
+ if (sa === 0) return dst
365
+
366
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
367
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
368
+ const br = sr < 128 ? (sr === 0 ? 0 : Math.max(0, (255 - (((255 - dr) * 255 / (2 * sr)) | 0)))) : (sr === 255 ? 255 : Math.min(255, ((dr * 255 / (2 * (255 - sr))) | 0)))
369
+ const bg = sg < 128 ? (sg === 0 ? 0 : Math.max(0, (255 - (((255 - dg) * 255 / (2 * sg)) | 0)))) : (sg === 255 ? 255 : Math.min(255, ((dg * 255 / (2 * (255 - sg))) | 0)))
370
+ const bb = sb < 128 ? (sb === 0 ? 0 : Math.max(0, (255 - (((255 - db) * 255 / (2 * sb)) | 0)))) : (sb === 255 ? 255 : Math.min(255, ((db * 255 / (2 * (255 - sb))) | 0)))
371
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
372
+
373
+ // Alpha Lerp inlined
374
+ const invA = 255 - sa
375
+ const r = (br * sa + dr * invA) / 255 | 0
376
+ const g = (bg * sa + dg * invA) / 255 | 0
377
+ const b = (bb * sa + db * invA) / 255 | 0
378
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
379
+
380
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
381
+ }
382
+
383
+ /** dst + 2 * src - 255 (Clamped to 0-255) */
384
+ export const linearLightPerfect: BlendColor32 = (src, dst) => {
385
+ const sa = (src >>> 24) & 0xFF
386
+ if (sa === 0) return dst
387
+
388
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
389
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
390
+ const brU = dr + 2 * sr - 255
391
+ const br = brU < 0 ? 0 : brU > 255 ? 255 : brU
392
+ const bgU = dg + 2 * sg - 255
393
+ const bg = bgU < 0 ? 0 : bgU > 255 ? 255 : bgU
394
+ const bbU = db + 2 * sb - 255
395
+ const bb = bbU < 0 ? 0 : bbU > 255 ? 255 : bbU
396
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
397
+
398
+ // Alpha Lerp inlined
399
+ const invA = 255 - sa
400
+ const r = (br * sa + dr * invA) / 255 | 0
401
+ const g = (bg * sa + dg * invA) / 255 | 0
402
+ const b = (bb * sa + db * invA) / 255 | 0
403
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
404
+
405
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
406
+ }
407
+
408
+ /** src < 128 ? min(dst, 2 * src) : max(dst, 2 * (src - 128)) */
409
+ export const pinLightPerfect: BlendColor32 = (src, dst) => {
410
+ const sa = (src >>> 24) & 0xFF
411
+ if (sa === 0) return dst
412
+
413
+ const dr = dst & 0xFF
414
+ const dg = (dst >>> 8) & 0xFF
415
+ const db = (dst >>> 16) & 0xFF
416
+ const sr = src & 0xFF
417
+ const sg = (src >>> 8) & 0xFF
418
+ const sb = (src >>> 16) & 0xFF
419
+
420
+ const br = sr < 128 ? (dr < (sr << 1) ? dr : (sr << 1)) : (dr > ((sr - 128) << 1) ? dr : ((sr - 128) << 1))
421
+ const bg = sg < 128 ? (dg < (sg << 1) ? dg : (sg << 1)) : (dg > ((sg - 128) << 1) ? dg : ((sg - 128) << 1))
422
+ const bb = sb < 128 ? (db < (sb << 1) ? db : (sb << 1)) : (db > ((sb - 128) << 1) ? db : ((sb - 128) << 1))
423
+
424
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
425
+
426
+ const invA = 255 - sa
427
+ const da = (dst >>> 24) & 0xFF
428
+ const r = (br * sa + dr * invA + 128) / 255 | 0
429
+ const g = (bg * sa + dg * invA + 128) / 255 | 0
430
+ const b = (bb * sa + db * invA + 128) / 255 | 0
431
+ const a = (255 * sa + da * invA + 128) / 255 | 0
432
+
433
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
434
+ }
435
+
436
+ /** (Vivid Light logic forced to 0 or 255) */
437
+ export const hardMixPerfect: BlendColor32 = (src, dst) => {
438
+ const sa = (src >>> 24) & 0xFF
439
+ if (sa === 0) return dst
440
+
441
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
442
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
443
+ const br = (sr < 128 ? (sr === 0 ? 0 : Math.max(0, (255 - (((255 - dr) * 255 / (2 * sr)) | 0)))) : (sr === 255 ? 255 : Math.min(255, ((dr * 255 / (2 * (255 - sr))) | 0)))) < 128 ? 0 : 255
444
+ const bg = (sg < 128 ? (sg === 0 ? 0 : Math.max(0, (255 - (((255 - dg) * 255 / (2 * sg)) | 0)))) : (sg === 255 ? 255 : Math.min(255, ((dg * 255 / (2 * (255 - sg))) | 0)))) < 128 ? 0 : 255
445
+ const bb = (sb < 128 ? (sb === 0 ? 0 : Math.max(0, (255 - (((255 - db) * 255 / (2 * sb)) | 0)))) : (sb === 255 ? 255 : Math.min(255, ((db * 255 / (2 * (255 - sb))) | 0)))) < 128 ? 0 : 255
446
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
447
+
448
+ // Alpha Lerp inlined
449
+ const invA = 255 - sa
450
+ const r = (br * sa + dr * invA) / 255 | 0
451
+ const g = (bg * sa + dg * invA) / 255 | 0
452
+ const b = (bb * sa + db * invA) / 255 | 0
453
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
454
+
455
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
456
+ }
457
+
458
+ /** Math.abs(src - dst) */
459
+ export const differencePerfect: BlendColor32 = (src, dst) => {
460
+ const sa = (src >>> 24) & 0xFF
461
+ if (sa === 0) return dst
462
+
463
+ const dr = dst & 0xFF
464
+ const dg = (dst >>> 8) & 0xFF
465
+ const db = (dst >>> 16) & 0xFF
466
+ const sr = src & 0xFF
467
+ const sg = (src >>> 8) & 0xFF
468
+ const sb = (src >>> 16) & 0xFF
469
+
470
+ const br = Math.abs(dr - sr)
471
+ const bg = Math.abs(dg - sg)
472
+ const bb = Math.abs(db - sb)
473
+
474
+ if (sa === 255) {
475
+ return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
476
+ }
477
+
478
+ const invA = 255 - sa
479
+ const da = (dst >>> 24) & 0xFF
480
+ const r = (br * sa + dr * invA + 128) / 255 | 0
481
+ const g = (bg * sa + dg * invA + 128) / 255 | 0
482
+ const b = (bb * sa + db * invA + 128) / 255 | 0
483
+ const a = (255 * sa + da * invA + 128) / 255 | 0
484
+
485
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
486
+ }
487
+
488
+ /** dst + src - ((dst * src) >> 7) */
489
+ export const exclusionPerfect: BlendColor32 = (src, dst) => {
490
+ const sa = (src >>> 24) & 0xFF
491
+ if (sa === 0) return dst
492
+
493
+ const dr = dst & 0xFF
494
+ const dg = (dst >>> 8) & 0xFF
495
+ const db = (dst >>> 16) & 0xFF
496
+ const sr = src & 0xFF
497
+ const sg = (src >>> 8) & 0xFF
498
+ const sb = (src >>> 16) & 0xFF
499
+
500
+ // Using >> 7 (divide by 128) instead of / 255
501
+ // This is equivalent to (2 * s * d) / 256
502
+ const br = dr + sr - ((dr * sr) >> 7)
503
+ const bg = dg + sg - ((dg * sg) >> 7)
504
+ const bb = db + sb - ((db * sb) >> 7)
505
+
506
+ if (sa === 255) {
507
+ return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
508
+ }
509
+
510
+ const invA = 255 - sa
511
+ const da = (dst >>> 24) & 0xFF
512
+ const r = (br * sa + dr * invA) / 255 | 0
513
+ const g = (bg * sa + dg * invA) / 255 | 0
514
+ const b = (bb * sa + db * invA) / 255 | 0
515
+ const a = (255 * sa + da * invA) / 255 | 0
516
+
517
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
518
+ }
519
+
520
+ /** Math.max(0, dst - src) */
521
+ export const subtractPerfect: BlendColor32 = (src, dst) => {
522
+ const sa = (src >>> 24) & 0xFF
523
+ if (sa === 0) return dst
524
+
525
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
526
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
527
+ const brU = dr - sr
528
+ const br = brU < 0 ? 0 : brU
529
+ const bgU = dg - sg
530
+ const bg = bgU < 0 ? 0 : bgU
531
+ const bbU = db - sb
532
+ const bb = bbU < 0 ? 0 : bbU
533
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
534
+
535
+ // Alpha Lerp inlined
536
+ const invA = 255 - sa
537
+ const r = (br * sa + dr * invA) / 255 | 0
538
+ const g = (bg * sa + dg * invA) / 255 | 0
539
+ const b = (bb * sa + db * invA) / 255 | 0
540
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
541
+
542
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
543
+ }
544
+
545
+ /** sr === 0 ? 255 : Math.min(255, (dr << 8) / sr) */
546
+ export const dividePerfect: BlendColor32 = (src, dst) => {
547
+ const sa = (src >>> 24) & 0xFF
548
+ if (sa === 0) return dst
549
+
550
+ const dr = dst & 0xFF, dg = (dst >>> 8) & 0xFF, db = (dst >>> 16) & 0xFF
551
+ const sr = src & 0xFF, sg = (src >>> 8) & 0xFF, sb = (src >>> 16) & 0xFF
552
+ const br = sr === 0 ? 255 : Math.min(255, ((dr * 255 / sr) | 0))
553
+ const bg = sg === 0 ? 255 : Math.min(255, ((dg * 255 / sg) | 0))
554
+ const bb = sb === 0 ? 255 : Math.min(255, ((db * 255 / sb) | 0))
555
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
556
+
557
+ // Alpha Lerp inlined
558
+ const invA = 255 - sa
559
+ const r = (br * sa + dr * invA) / 255 | 0
560
+ const g = (bg * sa + dg * invA) / 255 | 0
561
+ const b = (bb * sa + db * invA) / 255 | 0
562
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) / 255 | 0
563
+
564
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
565
+ }
566
+
567
+ export const PERFECT_BLENDER_REGISTRY = [
568
+ [BlendMode.overwrite, overwritePerfect],
569
+ [BlendMode.sourceOver, sourceOverPerfect],
570
+
571
+ [BlendMode.darken, darkenPerfect],
572
+ [BlendMode.multiply, multiplyPerfect],
573
+ [BlendMode.colorBurn, colorBurnPerfect],
574
+ [BlendMode.linearBurn, linearBurnPerfect],
575
+ [BlendMode.darkerColor, darkerPerfect],
576
+
577
+ [BlendMode.lighten, lightenPerfect],
578
+ [BlendMode.screen, screenPerfect],
579
+ [BlendMode.colorDodge, colorDodgePerfect],
580
+ [BlendMode.linearDodge, linearDodgePerfect],
581
+ [BlendMode.lighterColor, lighterPerfect],
582
+
583
+ [BlendMode.overlay, overlayPerfect],
584
+ [BlendMode.softLight, softLightPerfect],
585
+ [BlendMode.hardLight, hardLightPerfect],
586
+ [BlendMode.vividLight, vividLightPerfect],
587
+ [BlendMode.linearLight, linearLightPerfect],
588
+ [BlendMode.pinLight, pinLightPerfect],
589
+ [BlendMode.hardMix, hardMixPerfect],
590
+
591
+ [BlendMode.difference, differencePerfect],
592
+ [BlendMode.exclusion, exclusionPerfect],
593
+ [BlendMode.subtract, subtractPerfect],
594
+ [BlendMode.divide, dividePerfect],
595
+ ] as const
596
+
597
+ export type RegisteredPerfectBlender = typeof PERFECT_BLENDER_REGISTRY[number][1]
598
+ export type PerfectBlendModeIndex = number & { readonly __brandBlendModeIndex: unique symbol }
599
+
600
+ export const PERFECT_BLEND_MODES: BlendColor32[] = []
601
+ for (const [index, blend] of PERFECT_BLENDER_REGISTRY) {
602
+ PERFECT_BLEND_MODES[index as PerfectBlendModeIndex] = blend
603
+ }
604
+
605
+ export const PERFECT_BLEND_TO_INDEX = new Map(
606
+ PERFECT_BLENDER_REGISTRY.map((entry, index) => {
607
+ return [
608
+ entry[1],
609
+ index as PerfectBlendModeIndex,
610
+ ]
611
+ }),
612
+ ) as { get: (blend: RegisteredPerfectBlender) => PerfectBlendModeIndex }
613
+
614
+ export const INDEX_TO_PERFECT_BLEND = new Map(
615
+ PERFECT_BLENDER_REGISTRY.map((entry, index) => {
616
+ return [
617
+ index as PerfectBlendModeIndex,
618
+ entry[1],
619
+ ]
620
+ }),
621
+ ) as { get: (index: PerfectBlendModeIndex) => RegisteredPerfectBlender }
622
+
623
+ export type PerfectBlendModes = {
624
+ [K in keyof typeof BlendMode]: RegisteredPerfectBlender
625
+ }
626
+
627
+ export const PERFECT_BLEND_MODE_BY_NAME: PerfectBlendModes = {
628
+ overwrite: overwritePerfect,
629
+ sourceOver: sourceOverPerfect,
630
+ darken: darkenPerfect,
631
+ multiply: multiplyPerfect,
632
+ colorBurn: colorBurnPerfect,
633
+ linearBurn: linearBurnPerfect,
634
+ darkerColor: darkerPerfect,
635
+ lighten: lightenPerfect,
636
+ screen: screenPerfect,
637
+ colorDodge: colorDodgePerfect,
638
+ linearDodge: linearDodgePerfect,
639
+ lighterColor: lighterPerfect,
640
+ overlay: overlayPerfect,
641
+ softLight: softLightPerfect,
642
+ hardLight: hardLightPerfect,
643
+ vividLight: vividLightPerfect,
644
+ linearLight: linearLightPerfect,
645
+ pinLight: pinLightPerfect,
646
+ hardMix: hardMixPerfect,
647
+ difference: differencePerfect,
648
+ exclusion: exclusionPerfect,
649
+ subtract: subtractPerfect,
650
+ divide: dividePerfect,
651
+ } as const
@@ -0,0 +1,31 @@
1
+ // The enum index IS the permanent ID.
2
+ // do not change the order, Adding to it is ok.
3
+ export enum BlendMode {
4
+ overwrite,
5
+ sourceOver,
6
+
7
+ darken,
8
+ multiply,
9
+ colorBurn,
10
+ linearBurn,
11
+ darkerColor,
12
+
13
+ lighten,
14
+ screen,
15
+ colorDodge,
16
+ linearDodge,
17
+ lighterColor,
18
+
19
+ overlay,
20
+ softLight,
21
+ hardLight,
22
+ vividLight,
23
+ linearLight,
24
+ pinLight,
25
+ hardMix,
26
+
27
+ difference,
28
+ exclusion,
29
+ subtract,
30
+ divide,
31
+ }