pixel-data-js 0.3.0 → 0.5.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.
Files changed (40) hide show
  1. package/dist/index.dev.cjs +1405 -70
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +1355 -68
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +1405 -70
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +581 -64
  8. package/dist/index.prod.js +1355 -68
  9. package/dist/index.prod.js.map +1 -1
  10. package/package.json +14 -3
  11. package/src/Algorithm/floodFillSelection.ts +229 -0
  12. package/src/Canvas/PixelCanvas.ts +31 -0
  13. package/src/Canvas/ReusableCanvas.ts +44 -0
  14. package/src/Canvas/_constants.ts +2 -0
  15. package/src/Clipboard/getImageDataFromClipboard.ts +42 -0
  16. package/src/Clipboard/writeImageDataToClipboard.ts +25 -0
  17. package/src/Clipboard/writeImgBlobToClipboard.ts +13 -0
  18. package/src/ImageData/{extractImageData.ts → extractImageDataPixels.ts} +21 -3
  19. package/src/ImageData/imageDataToAlphaMask.ts +35 -0
  20. package/src/ImageData/imageDataToDataUrl.ts +27 -0
  21. package/src/ImageData/imageDataToImgBlob.ts +31 -0
  22. package/src/ImageData/imgBlobToImageData.ts +52 -0
  23. package/src/ImageData/invertImageData.ts +10 -0
  24. package/src/ImageData/resizeImageData.ts +75 -0
  25. package/src/ImageData/{writeImageData.ts → writeImageDataPixels.ts} +22 -3
  26. package/src/Input/fileInputChangeToImageData.ts +37 -0
  27. package/src/Input/fileToImageData.ts +75 -0
  28. package/src/Input/getSupportedRasterFormats.ts +74 -0
  29. package/src/Mask/extractMask.ts +86 -0
  30. package/src/Mask/mergeMasks.ts +1 -6
  31. package/src/PixelData/blendColorPixelData.ts +9 -9
  32. package/src/PixelData/fillPixelData.ts +51 -12
  33. package/src/PixelData/invertPixelData.ts +16 -0
  34. package/src/PixelData/pixelDataToAlphaMask.ts +28 -0
  35. package/src/Rect/trimRectBounds.ts +118 -0
  36. package/src/_types.ts +37 -20
  37. package/src/blend-modes.ts +506 -66
  38. package/src/color.ts +6 -6
  39. package/src/globals.d.ts +2 -0
  40. package/src/index.ts +37 -1
@@ -1,5 +1,7 @@
1
1
  import type { BlendColor32, Color32 } from './_types'
2
2
 
3
+ export const overwriteColor32: BlendColor32 = (src, dst) => src
4
+
3
5
  export const sourceOverColor32: BlendColor32 = (src, dst) => {
4
6
  const a = (src >>> 24) & 0xFF
5
7
  if (a === 255) return src
@@ -25,9 +27,165 @@ export const sourceOverColor32: BlendColor32 = (src, dst) => {
25
27
  return ((outA << 24) | outRB | outG) >>> 0 as Color32
26
28
  }
27
29
 
30
+ /** Math.min(src, dst) */
31
+ export const darkenColor32: BlendColor32 = (src, dst) => {
32
+ const sa = (src >>> 24) & 0xFF
33
+ if (sa === 0) return dst
34
+ const br = Math.min(src & 0xFF, dst & 0xFF)
35
+ const bg = Math.min((src >> 8) & 0xFF, (dst >> 8) & 0xFF)
36
+ const bb = Math.min((src >> 16) & 0xFF, (dst >> 16) & 0xFF)
37
+
38
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
39
+
40
+ const dr = dst & 0xFF
41
+ const dg = (dst >> 8) & 0xFF
42
+ const db = (dst >> 16) & 0xFF
43
+
44
+ // Alpha Lerp inlined
45
+ const invA = 255 - sa
46
+ const r = (br * sa + dr * invA) >> 8
47
+ const g = (bg * sa + dg * invA) >> 8
48
+ const b = (bb * sa + db * invA) >> 8
49
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
50
+
51
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
52
+ }
53
+
54
+ /** (src * dst) / 255 */
55
+ export const multiplyColor32: BlendColor32 = (src, dst) => {
56
+ const sa = (src >>> 24) & 0xFF
57
+ if (sa === 0) return dst
58
+
59
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
60
+
61
+ const br = ((src & 0xFF) * dr + 128) >> 8
62
+ const bg = (((src >> 8) & 0xFF) * dg) >> 8
63
+ const bb = (((src >> 16) & 0xFF) * db) >> 8
64
+
65
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
66
+
67
+ // Alpha Lerp inlined
68
+ const invA = 255 - sa
69
+ const r = (br * sa + dr * invA) >> 8
70
+ const g = (bg * sa + dg * invA) >> 8
71
+ const b = (bb * sa + db * invA) >> 8
72
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
73
+
74
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
75
+ }
76
+
77
+ /** 255 - (255-src)/dst */
78
+ export const colorBurnColor32: BlendColor32 = (src, dst) => {
79
+ const sa = (src >>> 24) & 0xFF
80
+ if (sa === 0) return dst
81
+
82
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
83
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
84
+
85
+ const br = dr === 255 ? 255 : Math.max(0, 255 - ((255 - dr) << 8) / (sr || 1))
86
+ const bg = dg === 255 ? 255 : Math.max(0, 255 - ((255 - dg) << 8) / (sg || 1))
87
+ const bb = db === 255 ? 255 : Math.max(0, 255 - ((255 - db) << 8) / (sb || 1))
88
+
89
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
90
+
91
+ // Alpha Lerp inlined
92
+ const invA = 255 - sa
93
+ const r = (br * sa + dr * invA) >> 8
94
+ const g = (bg * sa + dg * invA) >> 8
95
+ const b = (bb * sa + db * invA) >> 8
96
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
97
+
98
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
99
+ }
100
+
101
+ /** src + dst - 255 */
102
+ export const linearBurnColor32: BlendColor32 = (src, dst) => {
103
+ const sa = (src >>> 24) & 0xFF
104
+ if (sa === 0) return dst
105
+
106
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
107
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
108
+
109
+ // Math: Base + Blend - 255 (clamped to 0)
110
+ const br = Math.max(0, dr + sr - 255)
111
+ const bg = Math.max(0, dg + sg - 255)
112
+ const bb = Math.max(0, db + sb - 255)
113
+
114
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
115
+
116
+ // Alpha Lerp inlined
117
+ const invA = 255 - sa
118
+ const r = (br * sa + dr * invA) >> 8
119
+ const g = (bg * sa + dg * invA) >> 8
120
+ const b = (bb * sa + db * invA) >> 8
121
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
122
+
123
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
124
+ }
125
+
126
+ export const darkerColor32: BlendColor32 = (src, dst) => {
127
+ const sa = (src >>> 24) & 0xFF
128
+ if (sa === 0) return dst
129
+
130
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
131
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
132
+
133
+ // 1. Calculate Luminosity (Photoshop Weights: R:0.3, G:0.59, B:0.11)
134
+ // Scaled by 256 for integer math: 77, 151, 28
135
+ const lumSrc = (sr * 77 + sg * 151 + sb * 28)
136
+ const lumDst = (dr * 77 + dg * 151 + db * 28)
137
+
138
+ // 2. Selection Logic
139
+ // Pick the perceptually darker pixel
140
+ let br, bg, bb
141
+ if (lumSrc < lumDst) {
142
+ br = sr
143
+ bg = sg
144
+ bb = sb
145
+ } else {
146
+ br = dr
147
+ bg = dg
148
+ bb = db
149
+ }
150
+
151
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
152
+
153
+ // 3. Alpha Lerp inlined
154
+ const invA = 255 - sa
155
+ const r = (br * sa + dr * invA) >> 8
156
+ const g = (bg * sa + dg * invA) >> 8
157
+ const b = (bb * sa + db * invA) >> 8
158
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
159
+
160
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
161
+ }
162
+
163
+ /** Math.max(src, dst) */
164
+ export const lightenColor32: BlendColor32 = (src, dst) => {
165
+ const sa = (src >>> 24) & 0xFF
166
+ if (sa === 0) return dst
167
+ const br = Math.max(src & 0xFF, dst & 0xFF)
168
+ const bg = Math.max((src >> 8) & 0xFF, (dst >> 8) & 0xFF)
169
+ const bb = Math.max((src >> 16) & 0xFF, (dst >> 16) & 0xFF)
170
+
171
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
172
+
173
+ const dr = dst & 0xFF
174
+ const dg = (dst >> 8) & 0xFF
175
+ const db = (dst >> 16) & 0xFF
176
+
177
+ // Alpha Lerp inlined
178
+ const invA = 255 - sa
179
+ const r = (br * sa + dr * invA) >> 8
180
+ const g = (bg * sa + dg * invA) >> 8
181
+ const b = (bb * sa + db * invA) >> 8
182
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
183
+
184
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
185
+ }
186
+
28
187
  /**
29
- * Screen: Lightens the destination (inverse of Multiply).
30
- * Result = 1 - ((1 - Src) * (1 - Dst))
188
+ * 255 - ((255 - src) * (255 - dst))
31
189
  */
32
190
  export const screenColor32: BlendColor32 = (src, dst) => {
33
191
  const sa = (src >>> 24) & 0xFF
@@ -35,14 +193,13 @@ export const screenColor32: BlendColor32 = (src, dst) => {
35
193
 
36
194
  const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
37
195
 
38
- // 1. Core Math
39
196
  const br = 255 - (((255 - (src & 0xFF)) * (255 - dr)) >> 8)
40
197
  const bg = 255 - (((255 - ((src >> 8) & 0xFF)) * (255 - dg)) >> 8)
41
198
  const bb = 255 - (((255 - ((src >> 16) & 0xFF)) * (255 - db)) >> 8)
42
199
 
43
200
  if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
44
201
 
45
- // 2. Alpha Lerp inlined
202
+ // Alpha Lerp inlined
46
203
  const invA = 255 - sa
47
204
  const r = (br * sa + dr * invA) >> 8
48
205
  const g = (bg * sa + dg * invA) >> 8
@@ -52,24 +209,44 @@ export const screenColor32: BlendColor32 = (src, dst) => {
52
209
  return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
53
210
  }
54
211
 
55
- /**
56
- * Linear Dodge (Additive): Simply adds the source to the destination.
57
- * Clamps at 255.
58
- */
212
+ /** src === 255 ? 255 : Math.min(255, (dst << 8) / (255 - src)) */
213
+ export const colorDodgeColor32: BlendColor32 = (src, dst) => {
214
+ const sa = (src >>> 24) & 0xFF
215
+ if (sa === 0) return dst
216
+
217
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
218
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
219
+
220
+ const br = sr === 255 ? 255 : Math.min(255, (dr << 8) / (255 - sr))
221
+ const bg = sg === 255 ? 255 : Math.min(255, (dg << 8) / (255 - sg))
222
+ const bb = sb === 255 ? 255 : Math.min(255, (db << 8) / (255 - sb))
223
+
224
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
225
+
226
+ // Alpha Lerp inlined
227
+ const invA = 255 - sa
228
+ const r = (br * sa + dr * invA) >> 8
229
+ const g = (bg * sa + dg * invA) >> 8
230
+ const b = (bb * sa + db * invA) >> 8
231
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
232
+
233
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
234
+ }
235
+
236
+ /** src + dst */
59
237
  export const linearDodgeColor32: BlendColor32 = (src, dst) => {
60
238
  const sa = (src >>> 24) & 0xFF
61
239
  if (sa === 0) return dst
62
240
 
63
241
  const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
64
242
 
65
- // 1. Core Math (Additive with clamping)
66
243
  const br = Math.min(255, (src & 0xFF) + dr)
67
244
  const bg = Math.min(255, ((src >> 8) & 0xFF) + dg)
68
245
  const bb = Math.min(255, ((src >> 16) & 0xFF) + db)
69
246
 
70
247
  if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
71
248
 
72
- // 2. Alpha Lerp inlined
249
+ // Alpha Lerp inlined
73
250
  const invA = 255 - sa
74
251
  const r = (br * sa + dr * invA) >> 8
75
252
  const g = (bg * sa + dg * invA) >> 8
@@ -79,24 +256,105 @@ export const linearDodgeColor32: BlendColor32 = (src, dst) => {
79
256
  return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
80
257
  }
81
258
 
82
- /**
83
- * Multiply: Darkens the destination based on the source color.
84
- * Result = (Src * Dst) / 255
85
- */
86
- export const multiplyColor32: BlendColor32 = (src, dst) => {
259
+ export const lighterColor32: BlendColor32 = (src, dst) => {
87
260
  const sa = (src >>> 24) & 0xFF
88
261
  if (sa === 0) return dst
89
262
 
90
263
  const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
264
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
91
265
 
92
- // 1. Core Math
93
- const br = ((src & 0xFF) * dr) >> 8
94
- const bg = (((src >> 8) & 0xFF) * dg) >> 8
95
- const bb = (((src >> 16) & 0xFF) * db) >> 8
266
+ // Calculate Luminosity (Photoshop uses Weights: R:0.3, G:0.59, B:0.11)
267
+ // We use integer math (scaled by 256) for speed.
268
+ const lumSrc = (sr * 77 + sg * 151 + sb * 28)
269
+ const lumDst = (dr * 77 + dg * 151 + db * 28)
270
+
271
+ // Selection Logic (Base result)
272
+ let br, bg, bb
273
+ if (lumSrc > lumDst) {
274
+ br = sr
275
+ bg = sg
276
+ bb = sb
277
+ } else {
278
+ br = dr
279
+ bg = dg
280
+ bb = db
281
+ }
282
+
283
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
284
+
285
+ // Alpha Lerp
286
+ const invA = 255 - sa
287
+ const r = (br * sa + dr * invA) >> 8
288
+ const g = (bg * sa + dg * invA) >> 8
289
+ const b = (bb * sa + db * invA) >> 8
290
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
291
+
292
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
293
+ }
294
+
295
+ /** src < 128 ? (2 * src * dst) : (255 - 2 * (255 - src) * (255 - dst)) */
296
+ export const overlayColor32: BlendColor32 = (src, dst) => {
297
+ const sa = (src >>> 24) & 0xFF
298
+ if (sa === 0) return dst
299
+
300
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
301
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
302
+
303
+ const br = dr < 128 ? (2 * sr * dr) >> 8 : 255 - (2 * (255 - sr) * (255 - dr) >> 8)
304
+ const bg = dg < 128 ? (2 * sg * dg) >> 8 : 255 - (2 * (255 - sg) * (255 - dg) >> 8)
305
+ const bb = db < 128 ? (2 * sb * db) >> 8 : 255 - (2 * (255 - sb) * (255 - db) >> 8)
306
+
307
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
308
+
309
+ // Alpha Lerp inlined
310
+ const invA = 255 - sa
311
+ const r = (br * sa + dr * invA) >> 8
312
+ const g = (bg * sa + dg * invA) >> 8
313
+ const b = (bb * sa + db * invA) >> 8
314
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
315
+
316
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
317
+ }
318
+
319
+ /** ((255 - dst) * ((src * dst) >> 8) + dst * (255 - (((255 - src) * (255 - dst)) >> 8))) >> 8 */
320
+ export const softLightColor32: BlendColor32 = (src, dst) => {
321
+ const sa = (src >>> 24) & 0xFF
322
+ if (sa === 0) return dst
323
+
324
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
325
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
326
+
327
+ const br = ((255 - dr) * ((sr * dr) >> 8) + dr * (255 - (((255 - sr) * (255 - dr)) >> 8))) >> 8
328
+ const bg = ((255 - dg) * ((sg * dg) >> 8) + dg * (255 - (((255 - sg) * (255 - dg)) >> 8))) >> 8
329
+ const bb = ((255 - db) * ((sb * db) >> 8) + db * (255 - (((255 - sb) * (255 - db)) >> 8))) >> 8
96
330
 
97
331
  if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
98
332
 
99
- // 2. Alpha Lerp inlined
333
+ // Alpha Lerp inlined
334
+ const invA = 255 - sa
335
+ const r = (br * sa + dr * invA) >> 8
336
+ const g = (bg * sa + dg * invA) >> 8
337
+ const b = (bb * sa + db * invA) >> 8
338
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
339
+
340
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
341
+ }
342
+
343
+ /** If src < 128 (50% gray), Multiply; otherwise, Screen */
344
+ export const hardLightColor32: BlendColor32 = (src, dst) => {
345
+ const sa = (src >>> 24) & 0xFF
346
+ if (sa === 0) return dst
347
+
348
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
349
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
350
+
351
+ const br = sr < 128 ? (2 * sr * dr) >> 8 : 255 - ((2 * (255 - sr) * (255 - dr)) >> 8)
352
+ const bg = sg < 128 ? (2 * sg * dg) >> 8 : 255 - ((2 * (255 - sg) * (255 - dg)) >> 8)
353
+ const bb = sb < 128 ? (2 * sb * db) >> 8 : 255 - ((2 * (255 - sb) * (255 - db)) >> 8)
354
+
355
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
356
+
357
+ // Alpha Lerp inlined
100
358
  const invA = 255 - sa
101
359
  const r = (br * sa + dr * invA) >> 8
102
360
  const g = (bg * sa + dg * invA) >> 8
@@ -107,23 +365,134 @@ export const multiplyColor32: BlendColor32 = (src, dst) => {
107
365
  }
108
366
 
109
367
  /**
110
- * Difference: Subtracts the darker color from the lighter color.
111
- * Result = |Src - Dst|
368
+ * If src < 128: Burn(dst, 2 * src)
369
+ * If src >= 128: Dodge(dst, 2 * (src - 128))
112
370
  */
371
+ export const vividLightColor32: BlendColor32 = (src, dst) => {
372
+ const sa = (src >>> 24) & 0xFF
373
+ if (sa === 0) return dst
374
+
375
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
376
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
377
+
378
+ const br = sr < 128
379
+ ? (sr === 0 ? 0 : Math.max(0, 255 - (((255 - dr) << 8) / (2 * sr))))
380
+ : (sr === 255 ? 255 : Math.min(255, (dr << 8) / (2 * (255 - sr))))
381
+
382
+ const bg = sg < 128
383
+ ? (sg === 0 ? 0 : Math.max(0, 255 - (((255 - dg) << 8) / (2 * sg))))
384
+ : (sg === 255 ? 255 : Math.min(255, (dg << 8) / (2 * (255 - sg))))
385
+
386
+ const bb = sb < 128
387
+ ? (sb === 0 ? 0 : Math.max(0, 255 - (((255 - db) << 8) / (2 * sb))))
388
+ : (sb === 255 ? 255 : Math.min(255, (db << 8) / (2 * (255 - sb))))
389
+
390
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
391
+
392
+ // Alpha Lerp inlined
393
+ const invA = 255 - sa
394
+ const r = (br * sa + dr * invA) >> 8
395
+ const g = (bg * sa + dg * invA) >> 8
396
+ const b = (bb * sa + db * invA) >> 8
397
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
398
+
399
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
400
+ }
401
+
402
+ /** dst + 2 * src - 255 (Clamped to 0-255) */
403
+ export const linearLightColor32: BlendColor32 = (src, dst) => {
404
+ const sa = (src >>> 24) & 0xFF
405
+ if (sa === 0) return dst
406
+
407
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
408
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
409
+
410
+ const br = Math.max(0, Math.min(255, dr + 2 * sr - 255))
411
+ const bg = Math.max(0, Math.min(255, dg + 2 * sg - 255))
412
+ const bb = Math.max(0, Math.min(255, db + 2 * sb - 255))
413
+
414
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
415
+
416
+ // Alpha Lerp inlined
417
+ const invA = 255 - sa
418
+ const r = (br * sa + dr * invA) >> 8
419
+ const g = (bg * sa + dg * invA) >> 8
420
+ const b = (bb * sa + db * invA) >> 8
421
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
422
+
423
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
424
+ }
425
+
426
+ /** src < 128 ? min(dst, 2 * src) : max(dst, 2 * (src - 128)) */
427
+ export const pinLightColor32: BlendColor32 = (src, dst) => {
428
+ const sa = (src >>> 24) & 0xFF
429
+ if (sa === 0) return dst
430
+
431
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
432
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
433
+
434
+ const br = sr < 128 ? Math.min(dr, 2 * sr) : Math.max(dr, 2 * (sr - 128))
435
+ const bg = sg < 128 ? Math.min(dg, 2 * sg) : Math.max(dg, 2 * (sg - 128))
436
+ const bb = sb < 128 ? Math.min(db, 2 * sb) : Math.max(db, 2 * (sb - 128))
437
+
438
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
439
+
440
+ // Alpha Lerp inlined
441
+ const invA = 255 - sa
442
+ const r = (br * sa + dr * invA) >> 8
443
+ const g = (bg * sa + dg * invA) >> 8
444
+ const b = (bb * sa + db * invA) >> 8
445
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
446
+
447
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
448
+ }
449
+
450
+ /** (Vivid Light logic forced to 0 or 255) */
451
+ export const hardMixColor32: BlendColor32 = (src, dst) => {
452
+ const sa = (src >>> 24) & 0xFF
453
+ if (sa === 0) return dst
454
+
455
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
456
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
457
+
458
+ const br = (sr < 128
459
+ ? (sr === 0 ? 0 : Math.max(0, 255 - (((255 - dr) << 8) / (2 * sr))))
460
+ : (sr === 255 ? 255 : Math.min(255, (dr << 8) / (2 * (255 - sr))))) < 128 ? 0 : 255
461
+
462
+ const bg = (sg < 128
463
+ ? (sg === 0 ? 0 : Math.max(0, 255 - (((255 - dg) << 8) / (2 * sg))))
464
+ : (sg === 255 ? 255 : Math.min(255, (dg << 8) / (2 * (255 - sg))))) < 128 ? 0 : 255
465
+
466
+ const bb = (sb < 128
467
+ ? (sb === 0 ? 0 : Math.max(0, 255 - (((255 - db) << 8) / (2 * sb))))
468
+ : (sb === 255 ? 255 : Math.min(255, (db << 8) / (2 * (255 - sb))))) < 128 ? 0 : 255
469
+
470
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
471
+
472
+ // Alpha Lerp inlined
473
+ const invA = 255 - sa
474
+ const r = (br * sa + dr * invA) >> 8
475
+ const g = (bg * sa + dg * invA) >> 8
476
+ const b = (bb * sa + db * invA) >> 8
477
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
478
+
479
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
480
+ }
481
+
482
+ /** Math.abs(src - dst) */
113
483
  export const differenceColor32: BlendColor32 = (src, dst) => {
114
484
  const sa = (src >>> 24) & 0xFF
115
485
  if (sa === 0) return dst
116
486
 
117
487
  const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
118
488
 
119
- // 1. Core Math
120
489
  const br = Math.abs((src & 0xFF) - dr)
121
490
  const bg = Math.abs(((src >> 8) & 0xFF) - dg)
122
491
  const bb = Math.abs(((src >> 16) & 0xFF) - db)
123
492
 
124
493
  if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
125
494
 
126
- // 2. Alpha Lerp inlined
495
+ // Alpha Lerp inlined
127
496
  const invA = 255 - sa
128
497
  const r = (br * sa + dr * invA) >> 8
129
498
  const g = (bg * sa + dg * invA) >> 8
@@ -133,25 +502,21 @@ export const differenceColor32: BlendColor32 = (src, dst) => {
133
502
  return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
134
503
  }
135
504
 
136
- /**
137
- * Hard Light: Decides Multiply vs Screen based on SOURCE brightness.
138
- * Acts like a harsh spotlight.
139
- */
140
- export const hardLightColor32: BlendColor32 = (src, dst) => {
505
+ /** dst + src - ((dst * src) >> 7) */
506
+ export const exclusionColor32: BlendColor32 = (src, dst) => {
141
507
  const sa = (src >>> 24) & 0xFF
142
508
  if (sa === 0) return dst
143
509
 
144
- const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
145
510
  const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
511
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
146
512
 
147
- // 1. Core Math
148
- const br = sr < 128 ? (2 * sr * dr) >> 8 : 255 - (2 * (255 - sr) * (255 - dr) >> 8)
149
- const bg = sg < 128 ? (2 * sg * dg) >> 8 : 255 - (2 * (255 - sg) * (255 - dg) >> 8)
150
- const bb = sb < 128 ? (2 * sb * db) >> 8 : 255 - (2 * (255 - sb) * (255 - db) >> 8)
513
+ const br = dr + sr - ((dr * sr) >> 7)
514
+ const bg = dg + sg - ((dg * sg) >> 7)
515
+ const bb = db + sb - ((db * sb) >> 7)
151
516
 
152
517
  if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
153
518
 
154
- // 2. Alpha Lerp inlined
519
+ // Alpha Lerp inlined
155
520
  const invA = 255 - sa
156
521
  const r = (br * sa + dr * invA) >> 8
157
522
  const g = (bg * sa + dg * invA) >> 8
@@ -161,25 +526,21 @@ export const hardLightColor32: BlendColor32 = (src, dst) => {
161
526
  return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
162
527
  }
163
528
 
164
- /**
165
- * Color Burn: Darkens the destination to reflect the source color.
166
- * Intense saturation in the darks.
167
- */
168
- export const colorBurnColor32: BlendColor32 = (src, dst) => {
529
+ /** Math.max(0, dst - src) */
530
+ export const subtractColor32: BlendColor32 = (src, dst) => {
169
531
  const sa = (src >>> 24) & 0xFF
170
532
  if (sa === 0) return dst
171
533
 
172
- const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
173
534
  const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
535
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
174
536
 
175
- // 1. Core Math (Avoid division by zero)
176
- const br = dr === 255 ? 255 : Math.max(0, 255 - ((255 - dr) << 8) / (sr || 1))
177
- const bg = dg === 255 ? 255 : Math.max(0, 255 - ((255 - dg) << 8) / (sg || 1))
178
- const bb = db === 255 ? 255 : Math.max(0, 255 - ((255 - db) << 8) / (sb || 1))
537
+ const br = Math.max(0, dr - sr)
538
+ const bg = Math.max(0, dg - sg)
539
+ const bb = Math.max(0, db - sb)
179
540
 
180
541
  if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
181
542
 
182
- // 2. Alpha Lerp inlined
543
+ // Alpha Lerp inlined
183
544
  const invA = 255 - sa
184
545
  const r = (br * sa + dr * invA) >> 8
185
546
  const g = (bg * sa + dg * invA) >> 8
@@ -188,25 +549,22 @@ export const colorBurnColor32: BlendColor32 = (src, dst) => {
188
549
 
189
550
  return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
190
551
  }
191
- /**
192
- * Overlay: The classic "Contrast" mode.
193
- * Decides Multiply vs Screen based on DESTINATION brightness.
194
- */
195
- export const overlayColor32: BlendColor32 = (src, dst) => {
552
+
553
+ /** sr === 0 ? 255 : Math.min(255, (dr << 8) / sr) */
554
+ export const divideColor32: BlendColor32 = (src, dst) => {
196
555
  const sa = (src >>> 24) & 0xFF
197
556
  if (sa === 0) return dst
198
557
 
199
- const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
200
558
  const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
559
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
201
560
 
202
- // 1. Core Math
203
- const br = dr < 128 ? (2 * sr * dr) >> 8 : 255 - (2 * (255 - sr) * (255 - dr) >> 8)
204
- const bg = dg < 128 ? (2 * sg * dg) >> 8 : 255 - (2 * (255 - sg) * (255 - dg) >> 8)
205
- const bb = db < 128 ? (2 * sb * db) >> 8 : 255 - (2 * (255 - sb) * (255 - db) >> 8)
561
+ const br = sr === 0 ? 255 : Math.min(255, (dr << 8) / sr)
562
+ const bg = sg === 0 ? 255 : Math.min(255, (dg << 8) / sg)
563
+ const bb = sb === 0 ? 255 : Math.min(255, (db << 8) / sb)
206
564
 
207
565
  if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
208
566
 
209
- // 2. Alpha Lerp inlined
567
+ // Alpha Lerp inlined
210
568
  const invA = 255 - sa
211
569
  const r = (br * sa + dr * invA) >> 8
212
570
  const g = (bg * sa + dg * invA) >> 8
@@ -216,13 +574,95 @@ export const overlayColor32: BlendColor32 = (src, dst) => {
216
574
  return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
217
575
  }
218
576
 
219
- export const COLOR_32_BLEND_MODES = {
220
- sourceOver: sourceOverColor32,
221
- screen: screenColor32,
222
- linearDodge: linearDodgeColor32,
223
- multiply: multiplyColor32,
224
- difference: differenceColor32,
225
- overlay: overlayColor32,
226
- hardLight: hardLightColor32,
227
- colorBurn: colorBurnColor32,
577
+ // The enum index IS the permanent ID.
578
+ // do not change the order, Adding to it is ok.
579
+ export enum BlendMode {
580
+ overwrite,
581
+ sourceOver,
582
+
583
+ darken,
584
+ multiply,
585
+ colorBurn,
586
+ linearBurn,
587
+ darkerColor,
588
+
589
+ lighten,
590
+ screen,
591
+ colorDodge,
592
+ linearDodge,
593
+ lighterColor,
594
+
595
+ overlay,
596
+ softLight,
597
+ hardLight,
598
+ vividLight,
599
+ linearLight,
600
+ pinLight,
601
+ hardMix,
602
+
603
+ difference,
604
+ exclusion,
605
+ subtract,
606
+ divide,
607
+ }
608
+
609
+ const BLENDER_REGISTRY = [
610
+ [BlendMode.overwrite, overwriteColor32],
611
+ [BlendMode.sourceOver, sourceOverColor32],
612
+
613
+ [BlendMode.darken, darkenColor32],
614
+ [BlendMode.multiply, multiplyColor32],
615
+ [BlendMode.colorBurn, colorBurnColor32],
616
+ [BlendMode.linearBurn, linearBurnColor32],
617
+ [BlendMode.darkerColor, darkerColor32],
618
+
619
+ [BlendMode.lighten, lightenColor32],
620
+ [BlendMode.screen, screenColor32],
621
+ [BlendMode.colorDodge, colorDodgeColor32],
622
+ [BlendMode.linearDodge, linearDodgeColor32],
623
+ [BlendMode.lighterColor, lighterColor32],
624
+
625
+ [BlendMode.overlay, overlayColor32],
626
+ [BlendMode.softLight, softLightColor32],
627
+ [BlendMode.hardLight, hardLightColor32],
628
+ [BlendMode.vividLight, vividLightColor32],
629
+ [BlendMode.linearLight, linearLightColor32],
630
+ [BlendMode.pinLight, pinLightColor32],
631
+ [BlendMode.hardMix, hardMixColor32],
632
+
633
+ [BlendMode.difference, differenceColor32],
634
+ [BlendMode.exclusion, exclusionColor32],
635
+ [BlendMode.subtract, subtractColor32],
636
+ [BlendMode.divide, divideColor32],
637
+ ] as const
638
+
639
+ export type RegisteredBlender = typeof BLENDER_REGISTRY[number][1]
640
+ export type BlendModeIndex = number & { readonly __brandBlendModeIndex: unique symbol }
641
+
642
+ export const COLOR_32_BLEND_MODES: BlendColor32[] = []
643
+
644
+ for (const [index, blend] of BLENDER_REGISTRY) {
645
+ COLOR_32_BLEND_MODES[index as BlendModeIndex] = blend
646
+ }
647
+
648
+ export const COLOR_32_BLEND_TO_INDEX = new Map<RegisteredBlender, BlendModeIndex>(
649
+ BLENDER_REGISTRY.map((entry, index) => {
650
+ return [
651
+ entry[1],
652
+ index as BlendModeIndex,
653
+ ]
654
+ }),
655
+ ) as {
656
+ get: (blend: RegisteredBlender) => BlendModeIndex
657
+ }
658
+
659
+ export const INDEX_TO_COLOR_32_BLEND = new Map<BlendModeIndex, RegisteredBlender>(
660
+ BLENDER_REGISTRY.map((entry, index) => {
661
+ return [
662
+ index as BlendModeIndex,
663
+ entry[1],
664
+ ]
665
+ }),
666
+ ) as {
667
+ get: (index: BlendModeIndex) => RegisteredBlender
228
668
  }