pixel-data-js 0.31.0 → 0.32.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pixel-data-js",
3
3
  "type": "module",
4
- "version": "0.31.0",
4
+ "version": "0.32.0",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "description": "JS Pixel and ImageData operations",
7
7
  "author": {
@@ -0,0 +1,55 @@
1
+ import type { Rect } from '../Rect/_rect-types'
2
+ import type { ImageDataLike } from './_ImageData-types'
3
+ import { extractImageDataBuffer } from './extractImageDataBuffer'
4
+
5
+ /**
6
+ * Extracts a specific rectangular region of pixels from a larger {@link ImageDataLike}
7
+ * source into a new {@link Uint8ClampedArray}.
8
+ *
9
+ * This is a "read-only" operation that returns a copy of the pixel data.
10
+ *
11
+ * @param imageData - The source image data to read from.
12
+ * @param rect - A rect defining the region to extract.
13
+ * @returns A buffer containing the RGBA pixel data of the region.
14
+ */
15
+ export function extractImageData(
16
+ imageData: ImageDataLike,
17
+ rect: Rect,
18
+ ): ImageData | null
19
+ /**
20
+ * @param imageData - The source image data to read from.
21
+ * @param x - The starting horizontal coordinate.
22
+ * @param y - The starting vertical coordinate.
23
+ * @param w - The width of the region to extract.
24
+ * @param h - The height of the region to extract.
25
+ * @returns A buffer containing the RGBA pixel data of the region.
26
+ */
27
+ export function extractImageData(
28
+ imageData: ImageDataLike,
29
+ x: number,
30
+ y: number,
31
+ w: number,
32
+ h: number,
33
+ ): ImageData | null
34
+ export function extractImageData(
35
+ imageData: ImageDataLike,
36
+ _x: Rect | number,
37
+ _y?: number,
38
+ _w?: number,
39
+ _h?: number,
40
+ ): ImageData | null {
41
+ const { x, y, w, h } = typeof _x === 'object'
42
+ ? _x
43
+ : { x: _x, y: _y!, w: _w!, h: _h! }
44
+
45
+ if (w <= 0) return null
46
+ if (h <= 0) return null
47
+
48
+ const result = new ImageData(w, h)
49
+
50
+ const buffer = extractImageDataBuffer(imageData, x, y, w, h)
51
+ result.data.set(buffer)
52
+
53
+ return result
54
+
55
+ }
@@ -1,9 +1,6 @@
1
1
  import type { Rect } from '../Rect/_rect-types'
2
- import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
3
2
  import type { ImageDataLike } from './_ImageData-types'
4
3
 
5
- const SCRATCH_BLIT = makeClippedBlit()
6
-
7
4
  /**
8
5
  * Extracts a specific rectangular region of pixels from a larger {@link ImageDataLike}
9
6
  * source into a new {@link Uint8ClampedArray}.
@@ -43,37 +40,69 @@ export function extractImageDataBuffer(
43
40
  const { x, y, w, h } = typeof _x === 'object'
44
41
  ? _x
45
42
  : { x: _x, y: _y!, w: _w!, h: _h! }
43
+ if (w <= 0) return new Uint8ClampedArray(0)
44
+ if (h <= 0) return new Uint8ClampedArray(0)
45
+
46
+ const srcW = imageData.width
47
+ const srcH = imageData.height
48
+ const src = imageData.data
49
+
50
+ const outLen = w * h * 4
51
+ const out = new Uint8ClampedArray(outLen)
52
+
53
+ let srcX = x
54
+ let srcY = y
55
+ let dstX = 0
56
+ let dstY = 0
57
+ let copyW = w
58
+ let copyH = h
59
+
60
+ if (srcX < 0) {
61
+ dstX = -srcX
62
+ copyW += srcX
63
+ srcX = 0
64
+ }
65
+
66
+ if (srcY < 0) {
67
+ dstY = -srcY
68
+ copyH += srcY
69
+ srcY = 0
70
+ }
71
+
72
+ copyW = Math.min(copyW, srcW - srcX)
73
+ copyH = Math.min(copyH, srcH - srcY)
74
+
75
+ if (copyW <= 0) return out
76
+ if (copyH <= 0) return out
46
77
 
47
- const { width: srcW, height: srcH, data: src } = imageData
48
- // Safety check for invalid dimensions
49
- if (w <= 0 || h <= 0) return new Uint8ClampedArray(0)
50
- const out = new Uint8ClampedArray(w * h * 4)
78
+ // 2. Perform high-speed block copy
79
+ // Attempt to use a 32-bit view if the buffer is memory-aligned.
80
+ // This reduces loop iterations and arithmetic by 4x.
81
+ const isAligned = src.byteOffset % 4 === 0
51
82
 
52
- const clip = resolveBlitClipping(
53
- 0,
54
- 0,
55
- x,
56
- y,
57
- w,
58
- h,
59
- w,
60
- h,
61
- srcW,
62
- srcH,
63
- SCRATCH_BLIT,
64
- )
83
+ if (isAligned) {
84
+ const srcLen32 = src.byteLength / 4
85
+ const src32 = new Uint32Array(src.buffer, src.byteOffset, srcLen32)
86
+ const out32 = new Uint32Array(out.buffer)
65
87
 
66
- if (!clip.inBounds) return out
88
+ for (let row = 0; row < copyH; row++) {
89
+ const srcStart = (srcY + row) * srcW + srcX
90
+ const dstStart = (dstY + row) * w + dstX
91
+ const chunk = src32.subarray(srcStart, srcStart + copyW)
67
92
 
68
- const { x: dstX, y: dstY, sx: srcX, sy: srcY, w: copyW, h: copyH } = clip
69
- const rowLen = copyW * 4
93
+ out32.set(chunk, dstStart)
94
+ }
95
+ } else {
96
+ // Fallback for unaligned data
97
+ const rowLen = copyW * 4
70
98
 
71
- for (let row = 0; row < copyH; row++) {
72
- const srcStart = ((srcY + row) * srcW + srcX) * 4
73
- const dstStart = ((dstY + row) * w + dstX) * 4
99
+ for (let row = 0; row < copyH; row++) {
100
+ const srcStart = ((srcY + row) * srcW + srcX) * 4
101
+ const dstStart = ((dstY + row) * w + dstX) * 4
102
+ const chunk = src.subarray(srcStart, srcStart + rowLen)
74
103
 
75
- // Perform the high-speed bulk copy
76
- out.set(src.subarray(srcStart, srcStart + rowLen), dstStart)
104
+ out.set(chunk, dstStart)
105
+ }
77
106
  }
78
107
 
79
108
  return out
@@ -1,8 +1,3 @@
1
- import { MaskType } from '../Mask/_mask-types'
2
- import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
3
-
4
- const SCRATCH_BLIT = makeClippedBlit()
5
-
6
1
  /**
7
2
  * Writes image data from a source to a target with support for clipping and alpha masking.
8
3
  *
@@ -10,88 +5,72 @@ const SCRATCH_BLIT = makeClippedBlit()
10
5
  * @param source - The source ImageData to read from.
11
6
  * @param x - The x-coordinate in the target where drawing starts.
12
7
  * @param y - The y-coordinate in the target where drawing starts.
13
- * @param sx - The x-coordinate in the source to start copying from.
14
- * @param sy - The y-coordinate in the source to start copying from.
15
- * @param sw - The width of the rectangle to copy.
16
- * @param sh - The height of the rectangle to copy.
17
- * @param mask - An optional Uint8Array mask (0-255). 0 is transparent, 255 is opaque.
18
- * @param maskType - type of mask
19
8
  */
20
9
  export function writeImageData(
21
10
  target: ImageData,
22
11
  source: ImageData,
23
12
  x: number,
24
13
  y: number,
25
- sx: number = 0,
26
- sy: number = 0,
27
- sw: number = source.width,
28
- sh: number = source.height,
29
- mask: Uint8Array | null = null,
30
- maskType: MaskType = MaskType.BINARY,
31
14
  ): void {
32
15
  const dstW = target.width
33
16
  const dstH = target.height
34
- const dstData = target.data
17
+ const dst = target.data
18
+
35
19
  const srcW = source.width
36
- const srcData = source.data
20
+ const srcH = source.height
21
+ const src = source.data
37
22
 
38
- const clip = resolveBlitClipping(
39
- x, y, sx, sy, sw, sh,
40
- dstW, dstH, srcW, source.height,
41
- SCRATCH_BLIT,
42
- )
23
+ let dstX = x
24
+ let dstY = y
25
+ let srcX = 0
26
+ let srcY = 0
27
+ let copyW = srcW
28
+ let copyH = srcH
43
29
 
44
- if (!clip.inBounds) return
30
+ if (dstX < 0) {
31
+ srcX = -dstX
32
+ copyW += dstX
33
+ dstX = 0
34
+ }
45
35
 
46
- const {
47
- x: dstX,
48
- y: dstY,
49
- sx: srcX,
50
- sy: srcY,
51
- w: copyW,
52
- h: copyH,
53
- } = clip
36
+ if (dstY < 0) {
37
+ srcY = -dstY
38
+ copyH += dstY
39
+ dstY = 0
40
+ }
41
+
42
+ copyW = Math.min(copyW, dstW - dstX)
43
+ copyH = Math.min(copyH, dstH - dstY)
54
44
 
55
- const useMask = !!mask
45
+ if (copyW <= 0) return
46
+ if (copyH <= 0) return
56
47
 
57
- for (let row = 0; row < copyH; row++) {
58
- const currentDstY = dstY + row
59
- const currentSrcY = srcY + row
48
+ const isDstAligned = dst.byteOffset % 4 === 0
49
+ const isSrcAligned = src.byteOffset % 4 === 0
60
50
 
61
- const dstStart = (currentDstY * dstW + dstX) * 4
62
- const srcStart = (currentSrcY * srcW + srcX) * 4
51
+ if (isDstAligned && isSrcAligned) {
52
+ const dstLen32 = dst.byteLength / 4
53
+ const dst32 = new Uint32Array(dst.buffer, dst.byteOffset, dstLen32)
63
54
 
64
- if (useMask && mask) {
65
- for (let ix = 0; ix < copyW; ix++) {
66
- const mi = currentSrcY * srcW + (srcX + ix)
67
- const alpha = mask[mi]
55
+ const srcLen32 = src.byteLength / 4
56
+ const src32 = new Uint32Array(src.buffer, src.byteOffset, srcLen32)
68
57
 
69
- if (alpha === 0) {
70
- continue
71
- }
58
+ for (let row = 0; row < copyH; row++) {
59
+ const dstStart = (dstY + row) * dstW + dstX
60
+ const srcStart = (srcY + row) * srcW + srcX
61
+ const chunk = src32.subarray(srcStart, srcStart + copyW)
72
62
 
73
- const di = dstStart + (ix * 4)
74
- const si = srcStart + (ix * 4)
63
+ dst32.set(chunk, dstStart)
64
+ }
65
+ } else {
66
+ const rowLen = copyW * 4
75
67
 
76
- if (maskType === MaskType.BINARY || alpha === 255) {
77
- dstData[di] = srcData[si]
78
- dstData[di + 1] = srcData[si + 1]
79
- dstData[di + 2] = srcData[si + 2]
80
- dstData[di + 3] = srcData[si + 3]
81
- } else {
82
- const a = alpha / 255
83
- const invA = 1 - a
68
+ for (let row = 0; row < copyH; row++) {
69
+ const dstStart = ((dstY + row) * dstW + dstX) * 4
70
+ const srcStart = ((srcY + row) * srcW + srcX) * 4
71
+ const chunk = src.subarray(srcStart, srcStart + rowLen)
84
72
 
85
- dstData[di] = srcData[si] * a + dstData[di] * invA
86
- dstData[di + 1] = srcData[si + 1] * a + dstData[di + 1] * invA
87
- dstData[di + 2] = srcData[si + 2] * a + dstData[di + 2] * invA
88
- dstData[di + 3] = srcData[si + 3] * a + dstData[di + 3] * invA
89
- }
90
- }
91
- } else {
92
- const byteLen = copyW * 4
93
- const sub = srcData.subarray(srcStart, srcStart + byteLen)
94
- dstData.set(sub, dstStart)
73
+ dst.set(chunk, dstStart)
95
74
  }
96
75
  }
97
76
  }
@@ -1,7 +1,4 @@
1
1
  import type { Rect } from '../Rect/_rect-types'
2
- import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
3
-
4
- const SCRATCH_BLIT = makeClippedBlit()
5
2
 
6
3
  /**
7
4
  * Copies a pixel buffer into a specific region of an {@link ImageData} object.
@@ -43,43 +40,84 @@ export function writeImageDataBuffer(
43
40
  _w?: number,
44
41
  _h?: number,
45
42
  ): void {
46
- const { x, y, w, h } = typeof _x === 'object'
47
- ? _x
48
- : { x: _x, y: _y!, w: _w!, h: _h! }
49
-
50
- const { width: dstW, height: dstH, data: dst } = target
51
-
52
- const clip = resolveBlitClipping(
53
- x,
54
- y,
55
- 0,
56
- 0,
57
- w,
58
- h,
59
- dstW,
60
- dstH,
61
- w,
62
- h,
63
- SCRATCH_BLIT,
64
- )
65
-
66
- if (!clip.inBounds) return
67
-
68
- const {
69
- x: dstX,
70
- y: dstY,
71
- sx: srcX,
72
- sy: srcY,
73
- w: copyW,
74
- h: copyH,
75
- } = clip
76
-
77
- const rowLen = copyW * 4
78
-
79
- for (let row = 0; row < copyH; row++) {
80
- const dstStart = ((dstY + row) * dstW + dstX) * 4
81
- const srcStart = ((srcY + row) * w + srcX) * 4
82
-
83
- dst.set(data.subarray(srcStart, srcStart + rowLen), dstStart)
43
+ let x: number
44
+ let y: number
45
+ let w: number
46
+ let h: number
47
+
48
+ if (typeof _x === 'object') {
49
+ x = _x.x
50
+ y = _x.y
51
+ w = _x.w
52
+ h = _x.h
53
+ } else {
54
+ x = _x
55
+ y = _y!
56
+ w = _w!
57
+ h = _h!
58
+ }
59
+
60
+ if (w <= 0) return
61
+ if (h <= 0) return
62
+
63
+ const dstW = target.width
64
+ const dstH = target.height
65
+ const dst = target.data
66
+
67
+ // Inline clipping logic for destination boundaries
68
+ let dstX = x
69
+ let dstY = y
70
+ let srcX = 0
71
+ let srcY = 0
72
+ let copyW = w
73
+ let copyH = h
74
+
75
+ if (dstX < 0) {
76
+ srcX = -dstX
77
+ copyW += dstX
78
+ dstX = 0
79
+ }
80
+
81
+ if (dstY < 0) {
82
+ srcY = -dstY
83
+ copyH += dstY
84
+ dstY = 0
85
+ }
86
+
87
+ copyW = Math.min(copyW, dstW - dstX)
88
+ copyH = Math.min(copyH, dstH - dstY)
89
+
90
+ if (copyW <= 0) return
91
+ if (copyH <= 0) return
92
+
93
+ // Fast-path: Both arrays must be 4-byte aligned to use Uint32Array safely
94
+ const isDstAligned = dst.byteOffset % 4 === 0
95
+ const isSrcAligned = data.byteOffset % 4 === 0
96
+
97
+ if (isDstAligned && isSrcAligned) {
98
+ const dstLen32 = dst.byteLength / 4
99
+ const dst32 = new Uint32Array(dst.buffer, dst.byteOffset, dstLen32)
100
+
101
+ const srcLen32 = data.byteLength / 4
102
+ const src32 = new Uint32Array(data.buffer, data.byteOffset, srcLen32)
103
+
104
+ for (let row = 0; row < copyH; row++) {
105
+ const dstStart = (dstY + row) * dstW + dstX
106
+ const srcStart = (srcY + row) * w + srcX
107
+ const chunk = src32.subarray(srcStart, srcStart + copyW)
108
+
109
+ dst32.set(chunk, dstStart)
110
+ }
111
+ } else {
112
+ // Fallback for unaligned data arrays
113
+ const rowLen = copyW * 4
114
+
115
+ for (let row = 0; row < copyH; row++) {
116
+ const dstStart = ((dstY + row) * dstW + dstX) * 4
117
+ const srcStart = ((srcY + row) * w + srcX) * 4
118
+ const chunk = data.subarray(srcStart, srcStart + rowLen)
119
+
120
+ dst.set(chunk, dstStart)
121
+ }
84
122
  }
85
123
  }
@@ -1,15 +1,21 @@
1
1
  import type { Rect } from '../Rect/_rect-types'
2
- import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
3
2
  import type { PixelData32 } from './_pixelData-types'
4
3
 
5
- const SCRATCH_BLIT = makeClippedBlit()
6
-
7
4
  /**
8
5
  * Extracts a rectangular region of pixels from PixelData.
9
6
  * Returns a new Uint32Array containing the extracted pixels.
10
7
  */
11
- export function extractPixelDataBuffer(source: PixelData32, rect: Rect): Uint32Array
12
- export function extractPixelDataBuffer(source: PixelData32, x: number, y: number, w: number, h: number): Uint32Array
8
+ export function extractPixelDataBuffer(
9
+ source: PixelData32,
10
+ rect: Rect,
11
+ ): Uint32Array
12
+ export function extractPixelDataBuffer(
13
+ source: PixelData32,
14
+ x: number,
15
+ y: number,
16
+ w: number,
17
+ h: number,
18
+ ): Uint32Array
13
19
  export function extractPixelDataBuffer(
14
20
  source: PixelData32,
15
21
  _x: Rect | number,
@@ -17,56 +23,63 @@ export function extractPixelDataBuffer(
17
23
  _w?: number,
18
24
  _h?: number,
19
25
  ): Uint32Array {
20
- const { x, y, w, h } = typeof _x === 'object'
21
- ? _x
22
- : { x: _x, y: _y!, w: _w!, h: _h! }
26
+ let x: number
27
+ let y: number
28
+ let w: number
29
+ let h: number
30
+
31
+ if (typeof _x === 'object') {
32
+ x = _x.x
33
+ y = _x.y
34
+ w = _x.w
35
+ h = _x.h
36
+ } else {
37
+ x = _x
38
+ y = _y!
39
+ w = _w!
40
+ h = _h!
41
+ }
23
42
 
24
43
  const srcW = source.w
25
44
  const srcH = source.h
26
45
  const srcData = source.data
27
46
 
28
- // Safety check for empty or invalid dimensions
29
- if (w <= 0 || h <= 0) {
30
- return new Uint32Array(0)
31
- }
47
+ if (w <= 0) return new Uint32Array(0)
48
+ if (h <= 0) return new Uint32Array(0)
32
49
 
33
50
  const dstData = new Uint32Array(w * h)
34
51
 
35
- // We map from Source (srcW, srcH) at (x,y)
36
- // To Dest (w, h) at (0,0)
37
- // Note: resolveBlitClipping usually takes (dstX, dstY, srcX, srcY...)
38
- // Here we are "blitting" FROM x,y TO 0,0.
39
- const clip = resolveBlitClipping(
40
- 0,
41
- 0,
42
- x,
43
- y,
44
- w,
45
- h,
46
- w,
47
- h,
48
- srcW,
49
- srcH,
50
- SCRATCH_BLIT,
51
- )
52
+ // Inline clipping logic to avoid object allocations
53
+ let srcX = x
54
+ let srcY = y
55
+ let dstX = 0
56
+ let dstY = 0
57
+ let copyW = w
58
+ let copyH = h
59
+
60
+ if (srcX < 0) {
61
+ dstX = -srcX
62
+ copyW += srcX
63
+ srcX = 0
64
+ }
52
65
 
53
- if (!clip.inBounds) return dstData
66
+ if (srcY < 0) {
67
+ dstY = -srcY
68
+ copyH += srcY
69
+ srcY = 0
70
+ }
54
71
 
55
- const {
56
- x: dstX,
57
- y: dstY,
58
- sx: srcX,
59
- sy: srcY,
60
- w: copyW,
61
- h: copyH,
62
- } = clip
72
+ copyW = Math.min(copyW, srcW - srcX)
73
+ copyH = Math.min(copyH, srcH - srcY)
74
+
75
+ if (copyW <= 0) return dstData
76
+ if (copyH <= 0) return dstData
63
77
 
64
78
  for (let row = 0; row < copyH; row++) {
65
79
  const srcStart = (srcY + row) * srcW + srcX
66
80
  const dstStart = (dstY + row) * w + dstX
67
-
68
- // Perform the high-speed 32-bit bulk copy
69
81
  const chunk = srcData.subarray(srcStart, srcStart + copyW)
82
+
70
83
  dstData.set(chunk, dstStart)
71
84
  }
72
85
 
@@ -1,10 +1,7 @@
1
1
  import type { Color32 } from '../_types'
2
2
  import type { Rect } from '../Rect/_rect-types'
3
- import { makeClippedRect, resolveRectClipping } from '../Rect/resolveClipping'
4
3
  import type { PixelData32 } from './_pixelData-types'
5
4
 
6
- const SCRATCH_RECT = makeClippedRect()
7
-
8
5
  /**
9
6
  * Fills a region or the {@link PixelData32} buffer with a solid color.
10
7
  *
@@ -42,6 +39,9 @@ export function fillPixelData(
42
39
  _w?: number,
43
40
  _h?: number,
44
41
  ): boolean {
42
+ const dstW = dst.w
43
+ const dstH = dst.h
44
+
45
45
  let x: number
46
46
  let y: number
47
47
  let w: number
@@ -50,8 +50,8 @@ export function fillPixelData(
50
50
  if (typeof _x === 'object') {
51
51
  x = _x.x ?? 0
52
52
  y = _x.y ?? 0
53
- w = _x.w ?? dst.w
54
- h = _x.h ?? dst.h
53
+ w = _x.w ?? dstW
54
+ h = _x.h ?? dstH
55
55
  } else if (typeof _x === 'number') {
56
56
  x = _x
57
57
  y = _y!
@@ -60,37 +60,55 @@ export function fillPixelData(
60
60
  } else {
61
61
  x = 0
62
62
  y = 0
63
- w = dst.w
64
- h = dst.h
63
+ w = dstW
64
+ h = dstH
65
+ }
66
+
67
+ // Inline bounds clipping
68
+ let dstX = x
69
+ let dstY = y
70
+ let fillW = w
71
+ let fillH = h
72
+
73
+ if (dstX < 0) {
74
+ fillW += dstX
75
+ dstX = 0
65
76
  }
66
77
 
67
- const clip = resolveRectClipping(
68
- x,
69
- y,
70
- w,
71
- h,
72
- dst.w,
73
- dst.h,
74
- SCRATCH_RECT,
75
- )
78
+ if (dstY < 0) {
79
+ fillH += dstY
80
+ dstY = 0
81
+ }
76
82
 
77
- if (!clip.inBounds) return false
83
+ fillW = Math.min(fillW, dstW - dstX)
84
+ fillH = Math.min(fillH, dstH - dstY)
78
85
 
79
- const {
80
- x: finalX,
81
- y: finalY,
82
- w: actualW,
83
- h: actualH,
84
- } = clip
86
+ if (fillW <= 0) return false
87
+ if (fillH <= 0) return false
85
88
 
86
89
  const dst32 = dst.data
87
- const dw = dst.w
88
90
  let hasChanged = false
89
91
 
90
- for (let iy = 0; iy < actualH; iy++) {
91
- const rowOffset = (finalY + iy) * dw
92
- const start = rowOffset + finalX
93
- const end = start + actualW
92
+ // Fast-path: If the area spans the full width, we can treat it as a contiguous 1D array
93
+ if (dstX === 0 && fillW === dstW) {
94
+ const start = dstY * dstW
95
+ const end = start + fillW * fillH
96
+
97
+ for (let i = start; i < end; i++) {
98
+ if (dst32[i] !== color) {
99
+ dst32[i] = color
100
+ hasChanged = true
101
+ }
102
+ }
103
+
104
+ return hasChanged
105
+ }
106
+
107
+ // Standard path: row-by-row
108
+ for (let iy = 0; iy < fillH; iy++) {
109
+ const rowOffset = (dstY + iy) * dstW
110
+ const start = rowOffset + dstX
111
+ const end = start + fillW
94
112
 
95
113
  for (let i = start; i < end; i++) {
96
114
  if (dst32[i] !== color) {