pixel-data-js 0.31.0 → 0.33.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.
@@ -1,17 +1,16 @@
1
1
  import type { Color32 } from '../_types'
2
2
  import type { BinaryMask } from '../Mask/_mask-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
- * Fills a region of the {@link PixelData32} buffer with a solid color using a mask.
6
+ * Fills the target PixelData with a color based on a binary mask.
7
+ *
10
8
  * @param target - The target to modify.
11
9
  * @param color - The color to apply.
12
- * @param mask - The mask defining the area to fill.
13
- * @param x - Starting horizontal coordinate for the mask placement.
14
- * @param y - Starting vertical coordinate for the mask placement.
10
+ * @param mask - The binary mask determining where to fill.
11
+ * @param x - Horizontal offset to place the mask.
12
+ * @param y - Vertical offset to place the mask.
13
+ * @returns true if any pixels were actually modified.
15
14
  */
16
15
  export function fillPixelDataBinaryMask(
17
16
  target: PixelData32,
@@ -20,55 +19,61 @@ export function fillPixelDataBinaryMask(
20
19
  x = 0,
21
20
  y = 0,
22
21
  ): boolean {
23
-
22
+ const targetW = target.w
23
+ const targetH = target.h
24
24
  const maskW = mask.w
25
25
  const maskH = mask.h
26
26
 
27
- const clip = resolveRectClipping(
28
- x,
29
- y,
30
- maskW,
31
- maskH,
32
- target.w,
33
- target.h,
34
- SCRATCH_RECT,
35
- )
27
+ // Inline clipping logic
28
+ let dstX = x
29
+ let dstY = y
30
+ let actualW = maskW
31
+ let actualH = maskH
32
+
33
+ if (dstX < 0) {
34
+ actualW += dstX
35
+ dstX = 0
36
+ }
37
+
38
+ if (dstY < 0) {
39
+ actualH += dstY
40
+ dstY = 0
41
+ }
36
42
 
37
- if (!clip.inBounds) return false
43
+ actualW = Math.min(actualW, targetW - dstX)
44
+ actualH = Math.min(actualH, targetH - dstY)
38
45
 
39
- const {
40
- x: finalX,
41
- y: finalY,
42
- w: actualW,
43
- h: actualH,
44
- } = clip
46
+ if (actualW <= 0 || actualH <= 0) return false
45
47
 
46
48
  const maskData = mask.data
47
49
  const dst32 = target.data
48
- const dw = target.w
50
+
51
+ // Calculate offsets for the mask based on clipping
52
+ const mx = dstX - x
53
+ const my = dstY - y
49
54
 
50
55
  let hasChanged = false
51
56
 
52
- for (let iy = 0; iy < actualH; iy++) {
53
- const currentY = finalY + iy
54
- const maskY = currentY - y
55
- const maskOffset = maskY * maskW
57
+ // Stride-based loop for performance
58
+ let dIdx = dstY * targetW + dstX
59
+ let mIdx = my * maskW + mx
56
60
 
57
- const dstRowOffset = currentY * dw
61
+ const dStride = targetW - actualW
62
+ const mStride = maskW - actualW
58
63
 
64
+ for (let iy = 0; iy < actualH; iy++) {
59
65
  for (let ix = 0; ix < actualW; ix++) {
60
- const currentX = finalX + ix
61
- const maskX = currentX - x
62
- const maskIndex = maskOffset + maskX
63
-
64
- if (maskData[maskIndex]) {
65
- const current = dst32[dstRowOffset + currentX]
66
- if (current !== color) {
67
- dst32[dstRowOffset + currentX] = color
66
+ if (maskData[mIdx]) {
67
+ if (dst32[dIdx] !== color) {
68
+ dst32[dIdx] = color
68
69
  hasChanged = true
69
70
  }
70
71
  }
72
+ dIdx++
73
+ mIdx++
71
74
  }
75
+ dIdx += dStride
76
+ mIdx += mStride
72
77
  }
73
78
 
74
79
  return hasChanged
@@ -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
  * This function is faster than {@link fillPixelData} but does not
@@ -44,6 +41,9 @@ export function fillPixelDataFast(
44
41
  _w?: number,
45
42
  _h?: number,
46
43
  ): void {
44
+ const dstW = dst.w
45
+ const dstH = dst.h
46
+
47
47
  let x: number
48
48
  let y: number
49
49
  let w: number
@@ -66,31 +66,40 @@ export function fillPixelDataFast(
66
66
  h = dst.h
67
67
  }
68
68
 
69
- const clip = resolveRectClipping(x, y, w, h, dst.w, dst.h, SCRATCH_RECT)
69
+ // Inline bounds clipping
70
+ let dstX = x
71
+ let dstY = y
72
+ let fillW = w
73
+ let fillH = h
74
+
75
+ if (dstX < 0) {
76
+ fillW += dstX
77
+ dstX = 0
78
+ }
79
+
80
+ if (dstY < 0) {
81
+ fillH += dstY
82
+ dstY = 0
83
+ }
70
84
 
71
- if (!clip.inBounds) return
85
+ fillW = Math.min(fillW, dstW - dstX)
86
+ fillH = Math.min(fillH, dstH - dstY)
72
87
 
73
- // Use the clipped values
74
- const {
75
- x: finalX,
76
- y: finalY,
77
- w: actualW,
78
- h: actualH,
79
- } = clip
88
+ if (fillW <= 0 || fillH <= 0) return
80
89
 
81
90
  const dst32 = dst.data
82
91
  const dw = dst.w
83
92
 
84
93
  // Optimization: If filling the entire buffer, use the native .fill()
85
- if (actualW === dw && actualH === dst.h && finalX === 0 && finalY === 0) {
94
+ if (fillW === dw && fillH === dst.h && dstX === 0 && dstY === 0) {
86
95
  dst32.fill(color)
87
96
  return
88
97
  }
89
98
 
90
99
  // Row-by-row fill for partial rectangles
91
- for (let iy = 0; iy < actualH; iy++) {
92
- const start = (finalY + iy) * dw + finalX
93
- const end = start + actualW
100
+ for (let iy = 0; iy < fillH; iy++) {
101
+ const start = (dstY + iy) * dw + dstX
102
+ const end = start + fillW
94
103
  dst32.fill(color, start, end)
95
104
  }
96
105
  }
@@ -1,49 +1,66 @@
1
1
  import { type PixelMutateOptions } from '../_types'
2
- import { makeClippedRect, resolveRectClipping } from '../Rect/resolveClipping'
3
2
  import type { PixelData32 } from './_pixelData-types'
4
3
 
5
- const SCRATCH_RECT = makeClippedRect()
6
-
4
+ /**
5
+ * Inverts the RGB color data of the target PixelData, optionally controlled by a mask.
6
+ * @param target - The target to modify.
7
+ * @param opts - Options defining the area, mask, and offsets.
8
+ * @returns true if the operation was performed within bounds.
9
+ */
7
10
  export function invertPixelData(
8
11
  target: PixelData32,
9
12
  opts?: PixelMutateOptions,
10
13
  ): boolean {
14
+ const targetW = target.w
15
+ const targetH = target.h
16
+
11
17
  const mask = opts?.mask
18
+ const invertMask = opts?.invertMask ?? false
19
+
12
20
  const targetX = opts?.x ?? 0
13
21
  const targetY = opts?.y ?? 0
14
22
  const mx = opts?.mx ?? 0
15
23
  const my = opts?.my ?? 0
16
- const width = opts?.w ?? target.w
17
- const height = opts?.h ?? target.h
18
- const invertMask = opts?.invertMask ?? false
24
+ const w = opts?.w ?? targetW
25
+ const h = opts?.h ?? targetH
19
26
 
20
- const clip = resolveRectClipping(targetX, targetY, width, height, target.w, target.h, SCRATCH_RECT)
27
+ // Inline clipping logic
28
+ let x = targetX
29
+ let y = targetY
30
+ let actualW = w
31
+ let actualH = h
21
32
 
22
- if (!clip.inBounds) return false
33
+ if (x < 0) {
34
+ actualW += x
35
+ x = 0
36
+ }
23
37
 
24
- const {
25
- x,
26
- y,
27
- w: actualW,
28
- h: actualH,
29
- } = clip
38
+ if (y < 0) {
39
+ actualH += y
40
+ y = 0
41
+ }
42
+
43
+ actualW = Math.min(actualW, targetW - x)
44
+ actualH = Math.min(actualH, targetH - y)
45
+
46
+ if (actualW <= 0 || actualH <= 0) return false
30
47
 
31
48
  const dst32 = target.data
32
- const dw = target.w
33
- const mPitch = mask?.w ?? width
49
+ const dw = targetW
34
50
 
51
+ // Calculate relative movement for the mask coordinate
35
52
  const dx = x - targetX
36
53
  const dy = y - targetY
37
54
 
38
55
  let dIdx = y * dw + x
39
- let mIdx = (my + dy) * mPitch + (mx + dx)
40
-
41
56
  const dStride = dw - actualW
42
- const mStride = mPitch - actualW
43
57
 
44
- // Optimization: Split loops to avoid checking `if (mask)` for every pixel.
45
58
  if (mask) {
46
59
  const maskData = mask.data
60
+ const mPitch = mask.w
61
+ let mIdx = (my + dy) * mPitch + (mx + dx)
62
+ const mStride = mPitch - actualW
63
+
47
64
  for (let iy = 0; iy < actualH; iy++) {
48
65
  for (let ix = 0; ix < actualW; ix++) {
49
66
  const mVal = maskData[mIdx]
@@ -52,7 +69,7 @@ export function invertPixelData(
52
69
  : mVal === 1
53
70
 
54
71
  if (isHit) {
55
- // XOR with 0x00FFFFFF flips RGB bits and ignores Alpha (the top 8 bits)
72
+ // XOR with 0x00FFFFFF flips RGB bits and ignores Alpha
56
73
  dst32[dIdx] = dst32[dIdx] ^ 0x00FFFFFF
57
74
  }
58
75
  dIdx++
@@ -0,0 +1,75 @@
1
+ import type { MutablePixelData32, PixelData32 } from './_pixelData-types'
2
+
3
+ /**
4
+ * Non-destructively resizes the {@link PixelData32} buffer to new dimensions, optionally
5
+ * offsetting the original content.
6
+ * This operation creates a new buffer. It does not scale or stretch pixels;
7
+ * instead, it crops or pads the image based on the new dimensions.
8
+ *
9
+ * @param target - The source pixel data to resize.
10
+ * @param newWidth - The target width in pixels.
11
+ * @param newHeight - The target height in pixels.
12
+ * @param offsetX - The horizontal offset for placing the original image.
13
+ * @param offsetY - The vertical offset for placing the original image.
14
+ * @param out - output object
15
+ * @returns A new {@link PixelData32} object with the specified dimensions.
16
+ */
17
+ export function resizePixelData(
18
+ target: PixelData32,
19
+ newWidth: number,
20
+ newHeight: number,
21
+ offsetX = 0,
22
+ offsetY = 0,
23
+ out?: MutablePixelData32,
24
+ ): PixelData32 {
25
+ const newData = new Uint32Array(newWidth * newHeight)
26
+ const {
27
+ w: oldW,
28
+ h: oldH,
29
+ data: oldData,
30
+ } = target
31
+
32
+ const result = out ?? {} as MutablePixelData32
33
+ result.w = newWidth
34
+ result.h = newHeight
35
+ result.data = newData
36
+
37
+ // Determine intersection of the old image (at offset) and new canvas bounds
38
+ const x0 = Math.max(0, offsetX)
39
+ const y0 = Math.max(0, offsetY)
40
+ const x1 = Math.min(newWidth, offsetX + oldW)
41
+ const y1 = Math.min(newHeight, offsetY + oldH)
42
+
43
+ if (x1 <= x0 || y1 <= y0) {
44
+ return result
45
+ }
46
+
47
+ const copyW = x1 - x0
48
+ const copyH = y1 - y0
49
+
50
+ // Optimization: If we are copying the full width of both buffers,
51
+ // we can perform a single bulk 1D copy.
52
+ if (copyW === oldW && copyW === newWidth && offsetX === 0) {
53
+ const srcStart = (y0 - offsetY) * oldW
54
+ const dstStart = y0 * newWidth
55
+ const len = copyW * copyH
56
+
57
+ newData.set(oldData.subarray(srcStart, srcStart + len), dstStart)
58
+ return result
59
+ }
60
+
61
+ // Standard row-by-row copy
62
+ for (let row = 0; row < copyH; row++) {
63
+ const dstY = y0 + row
64
+ const srcY = dstY - offsetY
65
+ const srcX = x0 - offsetX
66
+
67
+ const dstStart = dstY * newWidth + x0
68
+ const srcStart = srcY * oldW + srcX
69
+ const chunk = oldData.subarray(srcStart, srcStart + copyW)
70
+
71
+ newData.set(chunk, dstStart)
72
+ }
73
+
74
+ return result
75
+ }
@@ -0,0 +1,55 @@
1
+ import type { PixelData32 } from './_pixelData-types'
2
+
3
+ /**
4
+ * Writes PixelData from a source to a target.
5
+ * @param target - The destination to write to.
6
+ * @param source - The source to read from.
7
+ * @param x - The x-coordinate in the target where drawing starts.
8
+ * @param y - The y-coordinate in the target where drawing starts.
9
+ */
10
+ export function writePixelData(
11
+ target: PixelData32,
12
+ source: PixelData32,
13
+ x = 0,
14
+ y = 0,
15
+ ): void {
16
+ const dstW = target.w
17
+ const dstH = target.h
18
+ const dst = target.data
19
+
20
+ const srcW = source.w
21
+ const srcH = source.h
22
+ const src = source.data
23
+
24
+ let dstX = x
25
+ let dstY = y
26
+ let srcX = 0
27
+ let srcY = 0
28
+ let copyW = srcW
29
+ let copyH = srcH
30
+
31
+ if (dstX < 0) {
32
+ srcX = -dstX
33
+ copyW += dstX
34
+ dstX = 0
35
+ }
36
+
37
+ if (dstY < 0) {
38
+ srcY = -dstY
39
+ copyH += dstY
40
+ dstY = 0
41
+ }
42
+
43
+ copyW = Math.min(copyW, dstW - dstX)
44
+ copyH = Math.min(copyH, dstH - dstY)
45
+
46
+ if (copyW <= 0 || copyH <= 0) return
47
+
48
+ for (let row = 0; row < copyH; row++) {
49
+ const dstStart = (dstY + row) * dstW + dstX
50
+ const srcStart = (srcY + row) * srcW + srcX
51
+ const chunk = src.subarray(srcStart, srcStart + copyW)
52
+
53
+ dst.set(chunk, dstStart)
54
+ }
55
+ }
@@ -1,20 +1,25 @@
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
  * Copies a pixel buffer into a specific region of a {@link PixelData32} object.
9
- *
10
- * This function performs a direct memory copy from a {@link Uint32Array}
11
- * into the target buffer.
6
+ * @param target - The target to write into.
7
+ * @param data - The source pixel data (Uint32Array).
8
+ * @param rect - A rect defining the destination region.
12
9
  */
13
10
  export function writePixelDataBuffer(
14
11
  target: PixelData32,
15
12
  data: Uint32Array,
16
13
  rect: Rect,
17
14
  ): void
15
+ /**
16
+ * @param target - The target to write into.
17
+ * @param data - The source pixel data (Uint32Array).
18
+ * @param x - The starting horizontal coordinate in the target.
19
+ * @param y - The starting vertical coordinate in the target.
20
+ * @param w - The width of the region to write.
21
+ * @param h - The height of the region to write.
22
+ */
18
23
  export function writePixelDataBuffer(
19
24
  target: PixelData32,
20
25
  data: Uint32Array,
@@ -31,49 +36,59 @@ export function writePixelDataBuffer(
31
36
  _w?: number,
32
37
  _h?: number,
33
38
  ): void {
34
- const { x, y, w, h } = typeof _x === 'object'
35
- ? _x
36
- : {
37
- x: _x,
38
- y: _y!,
39
- w: _w!,
40
- h: _h!,
41
- }
39
+ let x: number
40
+ let y: number
41
+ let w: number
42
+ let h: number
43
+
44
+ if (typeof _x === 'object') {
45
+ x = _x.x
46
+ y = _x.y
47
+ w = _x.w
48
+ h = _x.h
49
+ } else {
50
+ x = _x
51
+ y = _y!
52
+ w = _w!
53
+ h = _h!
54
+ }
55
+
56
+ if (w <= 0 || h <= 0) return
42
57
 
43
58
  const dstW = target.w
44
59
  const dstH = target.h
45
60
  const dstData = target.data
46
61
 
47
- // treat the source buffer as a Source Image starting at 0,0 with size w,h
48
- const clip = resolveBlitClipping(
49
- x,
50
- y,
51
- 0,
52
- 0,
53
- w,
54
- h,
55
- dstW,
56
- dstH,
57
- w,
58
- h,
59
- SCRATCH_BLIT,
60
- )
62
+ // Inline clipping logic
63
+ let dstX = x
64
+ let dstY = y
65
+ let srcX = 0
66
+ let srcY = 0
67
+ let copyW = w
68
+ let copyH = h
69
+
70
+ if (dstX < 0) {
71
+ srcX = -dstX
72
+ copyW += dstX
73
+ dstX = 0
74
+ }
75
+
76
+ if (dstY < 0) {
77
+ srcY = -dstY
78
+ copyH += dstY
79
+ dstY = 0
80
+ }
61
81
 
62
- if (!clip.inBounds) return
82
+ copyW = Math.min(copyW, dstW - dstX)
83
+ copyH = Math.min(copyH, dstH - dstY)
63
84
 
64
- const {
65
- x: dstX,
66
- y: dstY,
67
- sx: srcX,
68
- sy: srcY,
69
- w: copyW,
70
- h: copyH,
71
- } = clip
85
+ if (copyW <= 0 || copyH <= 0) return
72
86
 
73
87
  for (let row = 0; row < copyH; row++) {
74
88
  const dstStart = (dstY + row) * dstW + dstX
75
89
  const srcStart = (srcY + row) * w + srcX
90
+ const chunk = data.subarray(srcStart, srcStart + copyW)
76
91
 
77
- dstData.set(data.subarray(srcStart, srcStart + copyW), dstStart)
92
+ dstData.set(chunk, dstStart)
78
93
  }
79
94
  }
package/src/index.ts CHANGED
@@ -58,6 +58,7 @@ export * from './History/PixelWriter'
58
58
 
59
59
  export * from './ImageData/_ImageData-types'
60
60
  export * from './ImageData/copyImageData'
61
+ export * from './ImageData/extractImageData'
61
62
  export * from './ImageData/extractImageDataBuffer'
62
63
  export * from './ImageData/ImageDataLike'
63
64
  export * from './ImageData/imageDataToAlphaMaskBuffer'
@@ -160,15 +161,16 @@ export * from './PixelData/PixelData'
160
161
  export * from './PixelData/pixelDataToAlphaMask'
161
162
  export * from './PixelData/reflectPixelData'
162
163
  export * from './PixelData/resamplePixelData'
164
+ export * from './PixelData/resizePixelData'
163
165
  export * from './PixelData/ReusablePixelData'
164
166
  export * from './PixelData/rotatePixelData'
165
167
  export * from './PixelData/uInt32ArrayToPixelData'
166
168
  export * from './PixelData/writePaintBufferToPixelData'
169
+ export * from './PixelData/writePixelData'
167
170
  export * from './PixelData/writePixelDataBuffer'
168
171
 
169
172
  export * from './Rect/_rect-types'
170
173
  export * from './Rect/getRectsBounds'
171
- export * from './Rect/resolveClipping'
172
174
  export * from './Rect/trimMaskRectBounds'
173
175
  export * from './Rect/trimRectBounds'
174
176