pixel-data-js 0.26.0 → 0.28.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 (139) hide show
  1. package/README.md +12 -2
  2. package/dist/index.prod.cjs +2227 -1050
  3. package/dist/index.prod.cjs.map +1 -1
  4. package/dist/index.prod.d.ts +549 -424
  5. package/dist/index.prod.js +2171 -1028
  6. package/dist/index.prod.js.map +1 -1
  7. package/package.json +11 -11
  8. package/src/Algorithm/floodFillSelection.ts +8 -6
  9. package/src/Algorithm/forEachLinePoint.ts +6 -6
  10. package/src/{Internal/resample32.ts → Algorithm/resampleUint32Array.ts} +11 -21
  11. package/src/BlendModes/blend-modes-fast.ts +169 -0
  12. package/src/BlendModes/blend-modes-perfect.ts +207 -0
  13. package/src/BlendModes/blend-modes.ts +9 -0
  14. package/src/Canvas/CanvasFrameRenderer.ts +20 -28
  15. package/src/Canvas/CanvasPixelDataRenderer.ts +23 -0
  16. package/src/Canvas/PixelCanvas.ts +2 -7
  17. package/src/Canvas/ReusableCanvas.ts +4 -12
  18. package/src/Canvas/_canvas-types.ts +26 -0
  19. package/src/History/PixelAccumulator.ts +17 -17
  20. package/src/History/PixelEngineConfig.ts +3 -3
  21. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +4 -3
  22. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +4 -3
  23. package/src/History/PixelMutator/mutatorApplyMask.ts +4 -3
  24. package/src/History/PixelMutator/mutatorBlendAlphaMask.ts +6 -4
  25. package/src/History/PixelMutator/mutatorBlendBinaryMask.ts +6 -4
  26. package/src/History/PixelMutator/mutatorBlendColor.ts +2 -2
  27. package/src/History/PixelMutator/mutatorBlendColorPaintAlphaMask.ts +2 -1
  28. package/src/History/PixelMutator/mutatorBlendColorPaintBinaryMask.ts +2 -1
  29. package/src/History/PixelMutator/mutatorBlendColorPaintMask.ts +3 -1
  30. package/src/History/PixelMutator/{mutatorBlendPaintRect.ts → mutatorBlendColorPaintRect.ts} +5 -5
  31. package/src/History/PixelMutator/mutatorBlendMask.ts +6 -4
  32. package/src/History/PixelMutator/mutatorBlendPixelData.ts +5 -4
  33. package/src/History/PixelMutator/mutatorClear.ts +4 -3
  34. package/src/History/PixelMutator/mutatorFill.ts +5 -4
  35. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +2 -1
  36. package/src/History/PixelMutator/mutatorInvert.ts +2 -2
  37. package/src/History/PixelMutator.ts +2 -2
  38. package/src/History/PixelPatchTiles.ts +7 -7
  39. package/src/History/PixelWriter.ts +12 -63
  40. package/src/ImageData/ImageDataLike.ts +1 -1
  41. package/src/ImageData/_ImageData-types.ts +13 -0
  42. package/src/ImageData/copyImageData.ts +1 -1
  43. package/src/ImageData/extractImageDataBuffer.ts +3 -2
  44. package/src/ImageData/imageDataToUint32Array.ts +18 -0
  45. package/src/ImageData/resampleImageData.ts +3 -3
  46. package/src/ImageData/resizeImageData.ts +1 -1
  47. package/src/ImageData/serialization.ts +1 -1
  48. package/src/ImageData/uInt32ArrayToImageData.ts +1 -1
  49. package/src/ImageData/writeImageData.ts +2 -2
  50. package/src/ImageData/writeImageDataBuffer.ts +2 -2
  51. package/src/IndexedImage/IndexedImage.ts +56 -98
  52. package/src/IndexedImage/_indexedImage-types.ts +18 -0
  53. package/src/IndexedImage/getIndexedImageColorCounts.ts +3 -3
  54. package/src/IndexedImage/indexedImageToAverageColor.ts +1 -1
  55. package/src/IndexedImage/indexedImageToImageData.ts +4 -6
  56. package/src/IndexedImage/resampleIndexedImage.ts +7 -15
  57. package/src/Input/fileToImageData.ts +1 -1
  58. package/src/Internal/_errors.ts +2 -0
  59. package/src/Internal/macros.ts +14 -0
  60. package/src/Mask/AlphaMask.ts +1 -1
  61. package/src/Mask/BinaryMask/makeBinaryMaskFromAlphaMask.ts +23 -0
  62. package/src/Mask/BinaryMask/makeBinaryMaskOutline.ts +88 -0
  63. package/src/Mask/BinaryMask/makeCircleBinaryMaskOutline.ts +104 -0
  64. package/src/Mask/BinaryMask/makeRectBinaryMaskOutline.ts +34 -0
  65. package/src/Mask/BinaryMask.ts +1 -1
  66. package/src/Mask/_mask-types.ts +73 -0
  67. package/src/Mask/applyBinaryMaskToAlphaMask.ts +2 -1
  68. package/src/Mask/copyMask.ts +1 -1
  69. package/src/Mask/extractMask.ts +2 -1
  70. package/src/Mask/extractMaskBuffer.ts +1 -1
  71. package/src/Mask/mergeAlphaMasks.ts +6 -3
  72. package/src/Mask/mergeBinaryMasks.ts +2 -1
  73. package/src/Mask/setMaskData.ts +1 -1
  74. package/src/MaskRect/merge2BinaryMaskRects.ts +2 -2
  75. package/src/MaskRect/mergeBinaryMaskRects.ts +1 -1
  76. package/src/MaskRect/subtractBinaryMaskRects.ts +1 -1
  77. package/src/Paint/AlphaMaskPaintBuffer.ts +339 -0
  78. package/src/Paint/AlphaMaskPaintBufferCanvasRenderer.ts +78 -0
  79. package/src/Paint/BinaryMaskPaintBuffer.ts +254 -0
  80. package/src/Paint/BinaryMaskPaintBufferCanvasRenderer.ts +67 -0
  81. package/src/Paint/{PaintBuffer.ts → ColorPaintBuffer.ts} +148 -77
  82. package/src/Paint/{PaintBufferCanvasRenderer.ts → ColorPaintBufferCanvasRenderer.ts} +6 -5
  83. package/src/Paint/PaintCursorRenderer.ts +117 -0
  84. package/src/Paint/_paint-types.ts +22 -0
  85. package/src/Paint/eachTileInBounds.ts +45 -0
  86. package/src/Paint/makeCirclePaintMask.ts +74 -0
  87. package/src/Paint/makePaintMask.ts +5 -2
  88. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +4 -2
  89. package/src/PixelData/PixelData.ts +15 -19
  90. package/src/PixelData/ReusablePixelData.ts +36 -0
  91. package/src/PixelData/_pixelData-types.ts +17 -0
  92. package/src/PixelData/applyAlphaMaskToPixelData.ts +80 -43
  93. package/src/PixelData/applyBinaryMaskToPixelData.ts +10 -8
  94. package/src/PixelData/applyMaskToPixelData.ts +4 -9
  95. package/src/PixelData/blendColorPixelData.ts +9 -8
  96. package/src/PixelData/blendColorPixelDataAlphaMask.ts +9 -7
  97. package/src/PixelData/blendColorPixelDataBinaryMask.ts +9 -7
  98. package/src/PixelData/blendColorPixelDataMask.ts +4 -2
  99. package/src/PixelData/blendColorPixelDataPaintAlphaMask.ts +4 -2
  100. package/src/PixelData/blendColorPixelDataPaintBinaryMask.ts +4 -2
  101. package/src/PixelData/blendColorPixelDataPaintMask.ts +5 -2
  102. package/src/PixelData/blendPixel.ts +6 -5
  103. package/src/PixelData/blendPixelData.ts +14 -13
  104. package/src/PixelData/blendPixelDataAlphaMask.ts +15 -13
  105. package/src/PixelData/blendPixelDataBinaryMask.ts +15 -13
  106. package/src/PixelData/blendPixelDataMask.ts +5 -3
  107. package/src/PixelData/blendPixelDataPaintBuffer.ts +5 -4
  108. package/src/PixelData/clearPixelDataFast.ts +4 -2
  109. package/src/PixelData/copyPixelData.ts +14 -0
  110. package/src/PixelData/extractPixelData.ts +8 -7
  111. package/src/PixelData/extractPixelDataBuffer.ts +9 -8
  112. package/src/PixelData/fillPixelData.ts +16 -14
  113. package/src/PixelData/fillPixelDataBinaryMask.ts +10 -8
  114. package/src/PixelData/fillPixelDataFast.ts +16 -14
  115. package/src/PixelData/invertPixelData.ts +9 -8
  116. package/src/PixelData/pixelDataToAlphaMask.ts +9 -8
  117. package/src/PixelData/reflectPixelData.ts +9 -9
  118. package/src/PixelData/resamplePixelData.ts +20 -9
  119. package/src/PixelData/rotatePixelData.ts +8 -7
  120. package/src/PixelData/uInt32ArrayToPixelData.ts +15 -0
  121. package/src/PixelData/writePaintBufferToPixelData.ts +5 -5
  122. package/src/PixelData/writePixelDataBuffer.ts +10 -9
  123. package/src/Rect/_rect-types.ts +7 -0
  124. package/src/Rect/getRectsBounds.ts +1 -1
  125. package/src/Rect/trimMaskRectBounds.ts +2 -1
  126. package/src/Rect/trimRectBounds.ts +1 -1
  127. package/src/Tile/MaskTile.ts +40 -0
  128. package/src/Tile/PixelTile.ts +23 -0
  129. package/src/{PixelTile/PixelTilePool.ts → Tile/TilePool.ts} +9 -9
  130. package/src/Tile/_tile-types.ts +33 -0
  131. package/src/_errors.ts +1 -0
  132. package/src/_types.ts +2 -118
  133. package/src/index.ts +47 -22
  134. package/src/ImageData/imageDataToUInt32Array.ts +0 -13
  135. package/src/Internal/helpers.ts +0 -5
  136. package/src/Paint/makeCirclePaintAlphaMask.ts +0 -41
  137. package/src/Paint/makeCirclePaintBinaryMask.ts +0 -29
  138. package/src/PixelTile/PixelTile.ts +0 -21
  139. /package/src/{Internal → Rect}/resolveClipping.ts +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pixel-data-js",
3
3
  "type": "module",
4
- "version": "0.26.0",
4
+ "version": "0.28.0",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "description": "JS Pixel and ImageData operations",
7
7
  "author": {
@@ -17,7 +17,9 @@
17
17
  "color",
18
18
  "blend modes",
19
19
  "color blend",
20
- "canvas"
20
+ "canvas",
21
+ "draw",
22
+ "paint"
21
23
  ],
22
24
  "main": "./dist/index.prod.cjs",
23
25
  "module": "./dist/index.prod.js",
@@ -45,18 +47,17 @@
45
47
  "scripts": {
46
48
  "build": "tsup",
47
49
  "test": "vitest --coverage --project unit",
48
- "re-index": "tsx _scripts/re-index.ts",
49
- "check": "npm run check-circ && npm run re-index && npm run sort && npm run typecheck",
50
50
  "test:build": "pnpm build && vitest run --project dist",
51
- "check-circ": "tsx _scripts/check-circular.ts",
52
51
  "test:mutation": "stryker run",
53
- "docs": "npx typedoc",
54
- "typecheck": "tsc --noEmit",
55
- "check-exports": "tsx _scripts/check-exports.ts",
52
+ "re-index": "tsx _scripts/re-index.ts",
53
+ "check-circ": "tsx _scripts/check-circular.ts",
56
54
  "sort": "tsx _scripts/apply-sorting.ts",
55
+ "check": "pnpm run check-circ && pnpm run re-index && pnpm run sort && pnpm run typecheck && pnpm typedoc",
56
+ "typecheck": "tsc --noEmit",
57
57
  "bench": "tsx ./benchmark/run.ts",
58
58
  "bench:all": "tsx ./benchmark/run-all.ts",
59
- "bench:compare": "tsx ./benchmark/compare.ts"
59
+ "bench:compare": "tsx ./benchmark/compare.ts",
60
+ "docs": "pnpm typedoc"
60
61
  },
61
62
  "devDependencies": {
62
63
  "@clack/prompts": "^1.1.0",
@@ -67,7 +68,6 @@
67
68
  "@stryker-mutator/vitest-runner": "^9.5.1",
68
69
  "@types/cli-progress": "^3.11.6",
69
70
  "@types/node": "^25.2.3",
70
- "@vitest/browser": "3.2.4",
71
71
  "@vitest/coverage-v8": "3.2.4",
72
72
  "cli-progress": "^3.12.0",
73
73
  "cmd-ts": "^0.15.0",
@@ -88,7 +88,7 @@
88
88
  "typedoc-plugin-mdn-links": "^5.1.1",
89
89
  "typedoc-rhineai-theme": "^1.2.0",
90
90
  "typescript": "^5.9.3",
91
- "unplugin-inline": "^1.14.0",
91
+ "unplugin-inline": "^1.15.0",
92
92
  "vite-tsconfig-paths": "^6.1.1",
93
93
  "vitest": "3.2.4"
94
94
  },
@@ -1,7 +1,9 @@
1
- import { type BinaryMaskRect, type Color32, MaskType, type Rect } from '../_types'
1
+ import { type Color32 } from '../_types'
2
2
  import { colorDistance } from '../color'
3
3
  import { extractImageDataBuffer } from '../ImageData/extractImageDataBuffer'
4
- import type { PixelData } from '../PixelData/PixelData'
4
+ import { type BinaryMaskRect, MaskType } from '../Mask/_mask-types'
5
+ import type { PixelData } from '../PixelData/_pixelData-types'
6
+ import type { Rect } from '../Rect/_rect-types'
5
7
  import { trimMaskRectBounds } from '../Rect/trimMaskRectBounds'
6
8
 
7
9
  export type FloodFillResult = BinaryMaskRect & {
@@ -11,7 +13,7 @@ export type FloodFillResult = BinaryMaskRect & {
11
13
  }
12
14
 
13
15
  /**
14
- * Performs a color-based flood fill selection on {@link ImageData} or {@link PixelData}.
16
+ * Performs a color-based flood fill selection {@link PixelData}.
15
17
  * This utility identifies pixels starting from a specific coordinate that fall within a
16
18
  * color tolerance. It can operate in "contiguous" mode (classic bucket fill) or
17
19
  * "non-contiguous" mode (selects all matching pixels in the buffer).
@@ -51,9 +53,9 @@ export function floodFillSelection(
51
53
  out?: FloodFillResult,
52
54
  ): FloodFillResult | null {
53
55
 
54
- const data32 = target.data32
55
- const width = target.width
56
- const height = target.height
56
+ const data32 = target.data
57
+ const width = target.w
58
+ const height = target.h
57
59
 
58
60
  const lx = bounds?.x ?? 0
59
61
  const ly = bounds?.y ?? 0
@@ -9,18 +9,18 @@ export function forEachLinePoint(
9
9
  y1: number,
10
10
  callback: (x: number, y: number) => void,
11
11
  ): void {
12
+
13
+ if (x0 === x1 && y0 === y1) {
14
+ callback(x0, y0)
15
+ return
16
+ }
17
+
12
18
  const dx = x1 - x0
13
19
  const dy = y1 - y0
14
20
 
15
21
  // Determine the number of steps based on the longest axis
16
22
  const steps = Math.max(Math.abs(dx), Math.abs(dy))
17
23
 
18
- // Handle the zero-length line (Single Stamp Case)
19
- if (steps === 0) {
20
- callback(x0, y0)
21
- return
22
- }
23
-
24
24
  const xInc = dx / steps
25
25
  const yInc = dy / steps
26
26
 
@@ -1,26 +1,15 @@
1
- const resample32Scratch = {
2
- data: null as null | Int32Array,
3
- width: 0,
4
- height: 0,
5
- }
6
-
7
- /**
8
- * @internal
9
- */
10
- type Resample32Result = { data: Int32Array; width: number; height: number }
1
+ import type { MutablePixelData32, PixelData32 } from '../PixelData/_pixelData-types'
11
2
 
12
- /**
13
- * @internal
14
- */
15
- export function resample32(
16
- srcData32: Uint32Array | Int32Array,
3
+ export function resampleUint32Array<T extends PixelData32, M extends MutablePixelData32>(
4
+ srcData32: Uint32Array,
17
5
  srcW: number,
18
6
  srcH: number,
19
7
  factor: number,
20
- ): Resample32Result {
8
+ out?: M,
9
+ ): T {
21
10
  const dstW = Math.max(1, (srcW * factor) | 0)
22
11
  const dstH = Math.max(1, (srcH * factor) | 0)
23
- const dstData = new Int32Array(dstW * dstH)
12
+ const dstData = new Uint32Array(dstW * dstH)
24
13
 
25
14
  // Use the reciprocal to map back precisely
26
15
  const scaleX = srcW / dstW
@@ -38,9 +27,10 @@ export function resample32(
38
27
  }
39
28
  }
40
29
 
41
- resample32Scratch.data = dstData
42
- resample32Scratch.width = dstW
43
- resample32Scratch.height = dstH
30
+ out = out ?? {} as M
31
+ out.data = dstData
32
+ out.w = dstW
33
+ out.h = dstH
44
34
 
45
- return resample32Scratch as Resample32Result
35
+ return out as unknown as T
46
36
  }
@@ -4,6 +4,165 @@ import { makeBlendModeRegistry } from './BlendModeRegistry'
4
4
 
5
5
  export const overwriteFast = overwriteBase
6
6
 
7
+ export const sourceInFast: BlendColor32 = (src, dst) => {
8
+ const da = (dst >>> 24) & 0xFF
9
+ if (da === 0) return 0 as Color32
10
+ if (da === 255) return src
11
+
12
+ const sa = (src >>> 24) & 0xFF
13
+ const sr = src & 0xFF
14
+ const sg = (src >>> 8) & 0xFF
15
+ const sb = (src >>> 16) & 0xFF
16
+
17
+ const r = (sr * da) >> 8
18
+ const g = (sg * da) >> 8
19
+ const b = (sb * da) >> 8
20
+ const a = (sa * da) >> 8
21
+
22
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
23
+ }
24
+
25
+ export const sourceOutFast: BlendColor32 = (src, dst) => {
26
+ const da = (dst >>> 24) & 0xFF
27
+ if (da === 255) return 0 as Color32
28
+ if (da === 0) return src
29
+
30
+ const sa = (src >>> 24) & 0xFF
31
+ const sr = src & 0xFF
32
+ const sg = (src >>> 8) & 0xFF
33
+ const sb = (src >>> 16) & 0xFF
34
+
35
+ const invDa = 255 - da
36
+ const r = (sr * invDa) >> 8
37
+ const g = (sg * invDa) >> 8
38
+ const b = (sb * invDa) >> 8
39
+ const a = (sa * invDa) >> 8
40
+
41
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
42
+ }
43
+
44
+ export const sourceAtopFast: BlendColor32 = (src, dst) => {
45
+ const sa = (src >>> 24) & 0xFF
46
+ const da = (dst >>> 24) & 0xFF
47
+ if (da === 0) return 0 as Color32
48
+
49
+ const sr = src & 0xFF
50
+ const sg = (src >>> 8) & 0xFF
51
+ const sb = (src >>> 16) & 0xFF
52
+ const dr = dst & 0xFF
53
+ const dg = (dst >>> 8) & 0xFF
54
+ const db = (dst >>> 16) & 0xFF
55
+
56
+ const invSa = 255 - sa
57
+ const r = (sr * da + dr * invSa) >> 8
58
+ const g = (sg * da + dg * invSa) >> 8
59
+ const b = (sb * da + db * invSa) >> 8
60
+
61
+ return ((da << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
62
+ }
63
+
64
+ export const destinationOverFast: BlendColor32 = (src, dst) => {
65
+ const da = (dst >>> 24) & 0xFF
66
+ if (da === 255) return dst
67
+ if (da === 0) return src
68
+
69
+ const sa = (src >>> 24) & 0xFF
70
+ const sr = src & 0xFF
71
+ const sg = (src >>> 8) & 0xFF
72
+ const sb = (src >>> 16) & 0xFF
73
+ const dr = dst & 0xFF
74
+ const dg = (dst >>> 8) & 0xFF
75
+ const db = (dst >>> 16) & 0xFF
76
+
77
+ const invDa = 255 - da
78
+ const r = (dr * 255 + sr * invDa) >> 8
79
+ const g = (dg * 255 + sg * invDa) >> 8
80
+ const b = (db * 255 + sb * invDa) >> 8
81
+ const a = (da * 255 + sa * invDa) >> 8
82
+
83
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
84
+ }
85
+
86
+ export const destinationInFast: BlendColor32 = (src, dst) => {
87
+ const sa = (src >>> 24) & 0xFF
88
+ if (sa === 0) return 0 as Color32
89
+ if (sa === 255) return dst
90
+
91
+ const da = (dst >>> 24) & 0xFF
92
+ const dr = dst & 0xFF
93
+ const dg = (dst >>> 8) & 0xFF
94
+ const db = (dst >>> 16) & 0xFF
95
+
96
+ const r = (dr * sa) >> 8
97
+ const g = (dg * sa) >> 8
98
+ const b = (db * sa) >> 8
99
+ const a = (da * sa) >> 8
100
+
101
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
102
+ }
103
+
104
+ export const destinationOutFast: BlendColor32 = (src, dst) => {
105
+ const sa = (src >>> 24) & 0xFF
106
+ if (sa === 255) return 0 as Color32
107
+ if (sa === 0) return dst
108
+
109
+ const da = (dst >>> 24) & 0xFF
110
+ const dr = dst & 0xFF
111
+ const dg = (dst >>> 8) & 0xFF
112
+ const db = (dst >>> 16) & 0xFF
113
+
114
+ const invSa = 255 - sa
115
+ const r = (dr * invSa) >> 8
116
+ const g = (dg * invSa) >> 8
117
+ const b = (db * invSa) >> 8
118
+ const a = (da * invSa) >> 8
119
+
120
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
121
+ }
122
+
123
+ export const destinationAtopFast: BlendColor32 = (src, dst) => {
124
+ const sa = (src >>> 24) & 0xFF
125
+ if (sa === 0) return 0 as Color32 // Rule: Final Alpha = Sa
126
+ const da = (dst >>> 24) & 0xFF
127
+ if (da === 0) return 0 as Color32
128
+
129
+ const sr = src & 0xFF
130
+ const sg = (src >>> 8) & 0xFF
131
+ const sb = (src >>> 16) & 0xFF
132
+ const dr = dst & 0xFF
133
+ const dg = (dst >>> 8) & 0xFF
134
+ const db = (dst >>> 16) & 0xFF
135
+
136
+ const invDa = 255 - da
137
+ const r = (dr * sa + sr * invDa) >> 8
138
+ const g = (dg * sa + sg * invDa) >> 8
139
+ const b = (db * sa + sb * invDa) >> 8
140
+
141
+ return ((sa << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
142
+ }
143
+
144
+ export const xorFast: BlendColor32 = (src, dst) => {
145
+ const sa = (src >>> 24) & 0xFF
146
+ const da = (dst >>> 24) & 0xFF
147
+
148
+ const sr = src & 0xFF
149
+ const sg = (src >>> 8) & 0xFF
150
+ const sb = (src >>> 16) & 0xFF
151
+ const dr = dst & 0xFF
152
+ const dg = (dst >>> 8) & 0xFF
153
+ const db = (dst >>> 16) & 0xFF
154
+
155
+ const invDa = 255 - da
156
+ const invSa = 255 - sa
157
+
158
+ const r = (sr * invDa + dr * invSa) >> 8
159
+ const g = (sg * invDa + dg * invSa) >> 8
160
+ const b = (sb * invDa + db * invSa) >> 8
161
+ const a = (sa * invDa + da * invSa) >> 8
162
+
163
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
164
+ }
165
+
7
166
  export const sourceOverFast: BlendColor32 = (src, dst) => {
8
167
  const sa = (src >>> 24) & 0xFF
9
168
  if (sa === 255) return src
@@ -577,6 +736,16 @@ export const divideFast: BlendColor32 = (src, dst) => {
577
736
 
578
737
  export const BASE_FAST_BLEND_MODE_FUNCTIONS: Record<number, BlendColor32> = {
579
738
  [BaseBlendMode.overwrite]: overwriteFast,
739
+
740
+ [BaseBlendMode.sourceIn]: sourceInFast,
741
+ [BaseBlendMode.sourceOut]: sourceOutFast,
742
+ [BaseBlendMode.sourceAtop]: sourceAtopFast,
743
+ [BaseBlendMode.destinationOver]: destinationOverFast,
744
+ [BaseBlendMode.destinationIn]: destinationInFast,
745
+ [BaseBlendMode.destinationOut]: destinationOutFast,
746
+ [BaseBlendMode.destinationAtop]: destinationAtopFast,
747
+ [BaseBlendMode.xor]: xorFast,
748
+
580
749
  [BaseBlendMode.sourceOver]: sourceOverFast,
581
750
  [BaseBlendMode.darken]: darkenFast,
582
751
  [BaseBlendMode.multiply]: multiplyFast,
@@ -4,6 +4,203 @@ import { makeBlendModeRegistry } from './BlendModeRegistry'
4
4
 
5
5
  export const overwritePerfect = overwriteBase
6
6
 
7
+ export const sourceInPerfect: BlendColor32 = (src, dst) => {
8
+ const da = (dst >>> 24) & 0xFF
9
+ if (da === 0) return 0 as Color32
10
+ if (da === 255) return src
11
+
12
+ const sa = (src >>> 24) & 0xFF
13
+ const sr = src & 0xFF
14
+ const sg = (src >>> 8) & 0xFF
15
+ const sb = (src >>> 16) & 0xFF
16
+
17
+ // Result: [Sa * Da, Sc * Da]
18
+ const tR = sr * da
19
+ const r = (tR + 1 + (tR >> 8)) >> 8
20
+ const tG = sg * da
21
+ const g = (tG + 1 + (tG >> 8)) >> 8
22
+ const tB = sb * da
23
+ const b = (tB + 1 + (tB >> 8)) >> 8
24
+ const tA = sa * da
25
+ const a = (tA + 1 + (tA >> 8)) >> 8
26
+
27
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
28
+ }
29
+
30
+ export const sourceOutPerfect: BlendColor32 = (src, dst) => {
31
+ const da = (dst >>> 24) & 0xFF
32
+ if (da === 255) return 0 as Color32
33
+ if (da === 0) return src
34
+
35
+ const sa = (src >>> 24) & 0xFF
36
+ const sr = src & 0xFF
37
+ const sg = (src >>> 8) & 0xFF
38
+ const sb = (src >>> 16) & 0xFF
39
+
40
+ const invDa = 255 - da
41
+ // Result: [Sa * (1 - Da), Sc * (1 - Da)]
42
+ const tR = sr * invDa
43
+ const r = (tR + 1 + (tR >> 8)) >> 8
44
+ const tG = sg * invDa
45
+ const g = (tG + 1 + (tG >> 8)) >> 8
46
+ const tB = sb * invDa
47
+ const b = (tB + 1 + (tB >> 8)) >> 8
48
+ const tA = sa * invDa
49
+ const a = (tA + 1 + (tA >> 8)) >> 8
50
+
51
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
52
+ }
53
+
54
+ export const sourceAtopPerfect: BlendColor32 = (src, dst) => {
55
+ const sa = (src >>> 24) & 0xFF
56
+ const da = (dst >>> 24) & 0xFF
57
+ if (da === 0) return 0 as Color32
58
+
59
+ const sr = src & 0xFF
60
+ const sg = (src >>> 8) & 0xFF
61
+ const sb = (src >>> 16) & 0xFF
62
+ const dr = dst & 0xFF
63
+ const dg = (dst >>> 8) & 0xFF
64
+ const db = (dst >>> 16) & 0xFF
65
+
66
+ const invSa = 255 - sa
67
+ // Result: [Da, Sc * Da + Dc * (1 - Sa)]
68
+ const tR = sr * da + dr * invSa
69
+ const r = (tR + 1 + (tR >> 8)) >> 8
70
+ const tG = sg * da + dg * invSa
71
+ const g = (tG + 1 + (tG >> 8)) >> 8
72
+ const tB = sb * da + db * invSa
73
+ const b = (tB + 1 + (tB >> 8)) >> 8
74
+
75
+ return ((da << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
76
+ }
77
+
78
+ export const destinationOverPerfect: BlendColor32 = (src, dst) => {
79
+ const da = (dst >>> 24) & 0xFF
80
+ if (da === 255) return dst
81
+ if (da === 0) return src
82
+
83
+ const sa = (src >>> 24) & 0xFF
84
+ const sr = src & 0xFF
85
+ const sg = (src >>> 8) & 0xFF
86
+ const sb = (src >>> 16) & 0xFF
87
+ const dr = dst & 0xFF
88
+ const dg = (dst >>> 8) & 0xFF
89
+ const db = (dst >>> 16) & 0xFF
90
+
91
+ const invDa = 255 - da
92
+ // Result: [Da + Sa * (1 - Da), Dc + Sc * (1 - Da)]
93
+ const tR = dr * 255 + sr * invDa
94
+ const r = (tR + 1 + (tR >> 8)) >> 8
95
+ const tG = dg * 255 + sg * invDa
96
+ const g = (tG + 1 + (tG >> 8)) >> 8
97
+ const tB = db * 255 + sb * invDa
98
+ const b = (tB + 1 + (tB >> 8)) >> 8
99
+ const tA = da * 255 + sa * invDa
100
+ const a = (tA + 1 + (tA >> 8)) >> 8
101
+
102
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
103
+ }
104
+
105
+ export const destinationInPerfect: BlendColor32 = (src, dst) => {
106
+ const sa = (src >>> 24) & 0xFF
107
+ if (sa === 0) return 0 as Color32
108
+ if (sa === 255) return dst
109
+
110
+ const da = (dst >>> 24) & 0xFF
111
+ const dr = dst & 0xFF
112
+ const dg = (dst >>> 8) & 0xFF
113
+ const db = (dst >>> 16) & 0xFF
114
+
115
+ // Result: [Da * Sa, Dc * Sa]
116
+ const tR = dr * sa
117
+ const r = (tR + 1 + (tR >> 8)) >> 8
118
+ const tG = dg * sa
119
+ const g = (tG + 1 + (tG >> 8)) >> 8
120
+ const tB = db * sa
121
+ const b = (tB + 1 + (tB >> 8)) >> 8
122
+ const tA = da * sa
123
+ const a = (tA + 1 + (tA >> 8)) >> 8
124
+
125
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
126
+ }
127
+
128
+ export const destinationOutPerfect: BlendColor32 = (src, dst) => {
129
+ const sa = (src >>> 24) & 0xFF
130
+ if (sa === 255) return 0 as Color32
131
+ if (sa === 0) return dst
132
+
133
+ const da = (dst >>> 24) & 0xFF
134
+ const dr = dst & 0xFF
135
+ const dg = (dst >>> 8) & 0xFF
136
+ const db = (dst >>> 16) & 0xFF
137
+
138
+ const invSa = 255 - sa
139
+ // Result: [Da * (1 - Sa), Dc * (1 - Sa)]
140
+ const tR = dr * invSa
141
+ const r = (tR + 1 + (tR >> 8)) >> 8
142
+ const tG = dg * invSa
143
+ const g = (tG + 1 + (tG >> 8)) >> 8
144
+ const tB = db * invSa
145
+ const b = (tB + 1 + (tB >> 8)) >> 8
146
+ const tA = da * invSa
147
+ const a = (tA + 1 + (tA >> 8)) >> 8
148
+
149
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
150
+ }
151
+
152
+ export const destinationAtopPerfect: BlendColor32 = (src, dst) => {
153
+ const sa = (src >>> 24) & 0xFF
154
+ if (sa === 0) return 0 as Color32 // Rule: Final Alpha = Sa
155
+ const da = (dst >>> 24) & 0xFF
156
+ if (da === 0) return 0 as Color32
157
+
158
+ const sr = src & 0xFF
159
+ const sg = (src >>> 8) & 0xFF
160
+ const sb = (src >>> 16) & 0xFF
161
+ const dr = dst & 0xFF
162
+ const dg = (dst >>> 8) & 0xFF
163
+ const db = (dst >>> 16) & 0xFF
164
+
165
+ const invDa = 255 - da
166
+ // Result: [Sa, Dc * Sa + Sc * (1 - Da)]
167
+ const tR = dr * sa + sr * invDa
168
+ const r = (tR + 1 + (tR >> 8)) >> 8
169
+ const tG = dg * sa + sg * invDa
170
+ const g = (tG + 1 + (tG >> 8)) >> 8
171
+ const tB = db * sa + sb * invDa
172
+ const b = (tB + 1 + (tB >> 8)) >> 8
173
+
174
+ return ((sa << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
175
+ }
176
+
177
+ export const xorPerfect: BlendColor32 = (src, dst) => {
178
+ const sa = (src >>> 24) & 0xFF
179
+ const da = (dst >>> 24) & 0xFF
180
+
181
+ const sr = src & 0xFF
182
+ const sg = (src >>> 8) & 0xFF
183
+ const sb = (src >>> 16) & 0xFF
184
+ const dr = dst & 0xFF
185
+ const dg = (dst >>> 8) & 0xFF
186
+ const db = (dst >>> 16) & 0xFF
187
+
188
+ const invDa = 255 - da
189
+ const invSa = 255 - sa
190
+
191
+ // Result: [Sa * (1 - Da) + Da * (1 - Sa), Sc * (1 - Da) + Dc * (1 - Sa)]
192
+ const tR = sr * invDa + dr * invSa
193
+ const r = (tR + 1 + (tR >> 8)) >> 8
194
+ const tG = sg * invDa + dg * invSa
195
+ const g = (tG + 1 + (tG >> 8)) >> 8
196
+ const tB = sb * invDa + db * invSa
197
+ const b = (tB + 1 + (tB >> 8)) >> 8
198
+ const tA = sa * invDa + da * invSa
199
+ const a = (tA + 1 + (tA >> 8)) >> 8
200
+
201
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
202
+ }
203
+
7
204
  export const sourceOverPerfect: BlendColor32 = (src, dst) => {
8
205
  const sa = (src >>> 24) & 0xFF
9
206
  if (sa === 255) return src
@@ -748,6 +945,16 @@ export const dividePerfect: BlendColor32 = (src, dst) => {
748
945
 
749
946
  export const BASE_PERFECT_BLEND_MODE_FUNCTIONS: Record<number, BlendColor32> = {
750
947
  [BaseBlendMode.overwrite]: overwritePerfect,
948
+
949
+ [BaseBlendMode.sourceIn]: sourceInPerfect,
950
+ [BaseBlendMode.sourceOut]: sourceOutPerfect,
951
+ [BaseBlendMode.sourceAtop]: sourceAtopPerfect,
952
+ [BaseBlendMode.destinationOver]: destinationOverPerfect,
953
+ [BaseBlendMode.destinationIn]: destinationInPerfect,
954
+ [BaseBlendMode.destinationOut]: destinationOutPerfect,
955
+ [BaseBlendMode.destinationAtop]: destinationAtopPerfect,
956
+ [BaseBlendMode.xor]: xorPerfect,
957
+
751
958
  [BaseBlendMode.sourceOver]: sourceOverPerfect,
752
959
  [BaseBlendMode.darken]: darkenPerfect,
753
960
  [BaseBlendMode.multiply]: multiplyPerfect,
@@ -24,6 +24,15 @@ export const BaseBlendMode = {
24
24
  exclusion: 20,
25
25
  subtract: 21,
26
26
  divide: 22,
27
+
28
+ sourceIn: 23,
29
+ sourceOut: 24,
30
+ sourceAtop: 25,
31
+ destinationOver: 26,
32
+ destinationIn: 27,
33
+ destinationOut: 28,
34
+ destinationAtop: 29,
35
+ xor: 30,
27
36
  } as const
28
37
 
29
38
  export interface RequiredBlendModes {
@@ -1,54 +1,46 @@
1
- import type { PixelCanvas } from './PixelCanvas'
2
- import { makeReusableCanvas } from './ReusableCanvas'
1
+ import type { DrawPixelLayer, DrawScreenLayer, PixelCanvas, ReusableCanvasFactory } from './_canvas-types'
2
+ import { makeReusableOffscreenCanvas } from './ReusableCanvas'
3
3
 
4
- export type DrawPixelLayer = (ctx: CanvasRenderingContext2D) => void
5
- export type DrawScreenLayer = (ctx: CanvasRenderingContext2D, scale: number) => void
6
- export type CanvasFrameRenderer = ReturnType<typeof makeCanvasFrameRenderer>
4
+ export type CanvasFrameRenderer<T extends HTMLCanvasElement | OffscreenCanvas = OffscreenCanvas> =
5
+ ReturnType<typeof makeCanvasFrameRenderer<T>>
7
6
 
8
- const defaults = {
9
- makeReusableCanvas,
10
- }
11
-
12
- type Deps = Partial<typeof defaults>
13
-
14
- /**
15
- * @param deps - @hidden
16
- */
17
- export function makeCanvasFrameRenderer(deps: Deps = defaults) {
18
- const {
19
- makeReusableCanvas = defaults.makeReusableCanvas,
20
- } = deps
21
-
22
- const bufferCanvas = makeReusableCanvas()
7
+ export function makeCanvasFrameRenderer<T extends HTMLCanvasElement | OffscreenCanvas = OffscreenCanvas>(
8
+ reusableCanvasFactory: () => ReusableCanvasFactory<T> = makeReusableOffscreenCanvas as unknown as () => ReusableCanvasFactory<T>,
9
+ ) {
10
+ const bufferCanvas = reusableCanvasFactory()
23
11
 
24
12
  return function renderCanvasFrame(
25
13
  pixelCanvas: PixelCanvas,
26
14
  scale: number,
27
15
  getImageData: () => ImageData | undefined | null,
28
- drawPixelLayer?: DrawPixelLayer,
16
+ drawPixelLayer?: DrawPixelLayer<T>,
29
17
  drawScreenLayer?: DrawScreenLayer,
30
18
  ) {
31
- const { canvas, ctx } = pixelCanvas
32
19
 
33
- // 1. Clear pixel buffer (unscaled)
34
- const { ctx: pxCtx, canvas: pxCanvas } = bufferCanvas(canvas.width, canvas.height)
20
+ const canvas = pixelCanvas.canvas
21
+ const ctx = pixelCanvas.ctx
22
+ const w = canvas.width
23
+ const h = canvas.height
24
+
25
+ // 1. Clear pixel buffer
26
+ const buffer = bufferCanvas(w, h)
35
27
 
36
28
  // 2. Draw pixel data into pixel buffer
37
29
  const img = getImageData()
38
30
  if (img) {
39
- pxCtx.putImageData(img, 0, 0)
31
+ buffer.ctx.putImageData(img, 0, 0)
40
32
  }
41
33
 
42
34
  // draw transient pixel data
43
- drawPixelLayer?.(pxCtx)
35
+ drawPixelLayer?.(buffer.ctx)
44
36
 
45
37
  // clear target canvas
46
38
  ctx.setTransform(1, 0, 0, 1, 0, 0)
47
- ctx.clearRect(0, 0, canvas.width, canvas.height)
39
+ ctx.clearRect(0, 0, w, h)
48
40
 
49
41
  // Draw pixel buffer scaled onto screen
50
42
  ctx.setTransform(scale, 0, 0, scale, 0, 0)
51
- ctx.drawImage(pxCanvas, 0, 0)
43
+ ctx.drawImage(buffer.canvas, 0, 0)
52
44
 
53
45
  // Draw overlays in screen space
54
46
  ctx.setTransform(1, 0, 0, 1, 0, 0)
@@ -0,0 +1,23 @@
1
+ import type { PixelData } from '../PixelData/_pixelData-types'
2
+ import type { ReusableCanvasFactory } from './_canvas-types'
3
+ import { makeReusableOffscreenCanvas } from './ReusableCanvas'
4
+
5
+ export type CanvasPixelDataRenderer = ReturnType<typeof makeCanvasPixelDataRenderer>
6
+
7
+ export function makeCanvasPixelDataRenderer<T extends HTMLCanvasElement | OffscreenCanvas = OffscreenCanvas>(
8
+ reusableCanvasFactory: () => ReusableCanvasFactory<T> = makeReusableOffscreenCanvas as unknown as () => ReusableCanvasFactory<T>,
9
+ ) {
10
+ const bufferCanvas = reusableCanvasFactory()
11
+
12
+ return function drawPixelData(
13
+ targetCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
14
+ pixelData: PixelData,
15
+ x = 0,
16
+ y = 0,
17
+ ): void {
18
+ const buffer = bufferCanvas(pixelData.w, pixelData.h)
19
+
20
+ buffer.ctx.putImageData(pixelData.imageData, 0, 0)
21
+ targetCtx.drawImage(buffer.canvas, x, y)
22
+ }
23
+ }