pixel-data-js 0.17.1 → 0.19.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 (78) hide show
  1. package/README.md +6 -1
  2. package/dist/index.dev.cjs +2747 -1397
  3. package/dist/index.dev.cjs.map +1 -1
  4. package/dist/index.dev.js +2725 -1403
  5. package/dist/index.dev.js.map +1 -1
  6. package/dist/index.prod.cjs +2747 -1397
  7. package/dist/index.prod.cjs.map +1 -1
  8. package/dist/index.prod.d.ts +401 -241
  9. package/dist/index.prod.js +2725 -1403
  10. package/dist/index.prod.js.map +1 -1
  11. package/package.json +21 -6
  12. package/src/Algorithm/forEachLinePoint.ts +36 -0
  13. package/src/BlendModes/BlendModeRegistry.ts +2 -0
  14. package/src/BlendModes/blend-modes-fast.ts +2 -2
  15. package/src/BlendModes/blend-modes-perfect.ts +5 -4
  16. package/src/BlendModes/toBlendModeIndexAndName.ts +41 -0
  17. package/src/History/PixelAccumulator.ts +2 -2
  18. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +30 -0
  19. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +30 -0
  20. package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +59 -0
  21. package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +138 -0
  22. package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +59 -0
  23. package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +131 -0
  24. package/src/History/PixelMutator/mutatorApplyRectBrush.ts +61 -0
  25. package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +169 -0
  26. package/src/History/PixelMutator/mutatorApplyRectPencil.ts +62 -0
  27. package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +149 -0
  28. package/src/History/PixelMutator/mutatorBlendColor.ts +9 -4
  29. package/src/History/PixelMutator/mutatorBlendPixelData.ts +10 -5
  30. package/src/History/PixelMutator/mutatorClear.ts +27 -0
  31. package/src/History/PixelMutator/{mutatorFillPixelData.ts → mutatorFill.ts} +9 -3
  32. package/src/History/PixelMutator/mutatorInvert.ts +10 -3
  33. package/src/History/PixelMutator.ts +27 -3
  34. package/src/History/PixelPatchTiles.ts +2 -2
  35. package/src/History/PixelWriter.ts +7 -3
  36. package/src/ImageData/ImageDataLike.ts +13 -0
  37. package/src/ImageData/ReusableImageData.ts +1 -4
  38. package/src/ImageData/extractImageDataBuffer.ts +22 -15
  39. package/src/ImageData/serialization.ts +4 -4
  40. package/src/ImageData/uInt32ArrayToImageData.ts +29 -0
  41. package/src/ImageData/writeImageData.ts +26 -18
  42. package/src/ImageData/writeImageDataBuffer.ts +30 -18
  43. package/src/IndexedImage/indexedImageToAverageColor.ts +1 -1
  44. package/src/Internal/resolveClipping.ts +140 -0
  45. package/src/Mask/applyBinaryMaskToAlphaMask.ts +89 -0
  46. package/src/Mask/copyMask.ts +1 -3
  47. package/src/Mask/mergeAlphaMasks.ts +81 -0
  48. package/src/Mask/mergeBinaryMasks.ts +89 -0
  49. package/src/PixelData/PixelBuffer32.ts +28 -0
  50. package/src/PixelData/PixelData.ts +38 -33
  51. package/src/PixelData/applyAlphaMaskToPixelData.ts +119 -0
  52. package/src/PixelData/applyBinaryMaskToPixelData.ts +111 -0
  53. package/src/PixelData/applyCircleBrushToPixelData.ts +58 -28
  54. package/src/PixelData/applyRectBrushToPixelData.ts +56 -73
  55. package/src/PixelData/blendColorPixelData.ts +18 -111
  56. package/src/PixelData/blendColorPixelDataAlphaMask.ts +111 -0
  57. package/src/PixelData/blendColorPixelDataBinaryMask.ts +89 -0
  58. package/src/PixelData/blendPixelData.ts +19 -107
  59. package/src/PixelData/blendPixelDataAlphaMask.ts +149 -0
  60. package/src/PixelData/blendPixelDataBinaryMask.ts +133 -0
  61. package/src/PixelData/clearPixelData.ts +2 -3
  62. package/src/PixelData/extractPixelData.ts +4 -4
  63. package/src/PixelData/extractPixelDataBuffer.ts +38 -26
  64. package/src/PixelData/fillPixelData.ts +18 -20
  65. package/src/PixelData/invertPixelData.ts +13 -21
  66. package/src/PixelData/pixelDataToAlphaMask.ts +2 -3
  67. package/src/PixelData/reflectPixelData.ts +3 -3
  68. package/src/PixelData/resamplePixelData.ts +2 -6
  69. package/src/PixelData/writePixelDataBuffer.ts +34 -20
  70. package/src/Rect/getCircleBrushOrPencilBounds.ts +43 -0
  71. package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +24 -0
  72. package/src/Rect/getRectBrushOrPencilBounds.ts +38 -0
  73. package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +26 -0
  74. package/src/_types.ts +49 -33
  75. package/src/index.ts +47 -11
  76. package/src/History/PixelMutator/mutatorApplyMask.ts +0 -20
  77. package/src/Mask/mergeMasks.ts +0 -100
  78. package/src/PixelData/applyMaskToPixelData.ts +0 -129
@@ -0,0 +1,133 @@
1
+ import type { BinaryMask, Color32, IPixelData, PixelBlendMaskOptions } from '../_types'
2
+ import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
3
+
4
+ export function blendPixelDataBinaryMask(
5
+ dst: IPixelData,
6
+ src: IPixelData,
7
+ binaryMask: BinaryMask,
8
+ opts: PixelBlendMaskOptions,
9
+ ) {
10
+ const {
11
+ x: targetX = 0,
12
+ y: targetY = 0,
13
+ sx: sourceX = 0,
14
+ sy: sourceY = 0,
15
+ w: width = src.width,
16
+ h: height = src.height,
17
+ alpha: globalAlpha = 255,
18
+ blendFn = sourceOverPerfect,
19
+ mw = src.width,
20
+ mx = 0,
21
+ my = 0,
22
+ invertMask = false,
23
+ } = opts
24
+
25
+ if (globalAlpha === 0) return
26
+
27
+ let x = targetX
28
+ let y = targetY
29
+ let sx = sourceX
30
+ let sy = sourceY
31
+ let w = width
32
+ let h = height
33
+
34
+ // 1. Source Clipping
35
+ if (sx < 0) {
36
+ x -= sx
37
+ w += sx
38
+ sx = 0
39
+ }
40
+ if (sy < 0) {
41
+ y -= sy
42
+ h += sy
43
+ sy = 0
44
+ }
45
+ w = Math.min(w, src.width - sx)
46
+ h = Math.min(h, src.height - sy)
47
+
48
+ // 2. Destination Clipping
49
+ if (x < 0) {
50
+ sx -= x
51
+ w += x
52
+ x = 0
53
+ }
54
+ if (y < 0) {
55
+ sy -= y
56
+ h += y
57
+ y = 0
58
+ }
59
+
60
+ const actualW = Math.min(w, dst.width - x)
61
+ const actualH = Math.min(h, dst.height - y)
62
+
63
+ if (actualW <= 0 || actualH <= 0) return
64
+
65
+ // 3. Coordinate Displacement for Mask Sync
66
+ // dx/dy represents how far the clipped start is from the requested start.
67
+ // This is the stable way to align the mask across all clipping permutations.
68
+ const dx = (x - targetX) | 0
69
+ const dy = (y - targetY) | 0
70
+
71
+ const dst32 = dst.data32
72
+ const src32 = src.data32
73
+ const dw = dst.width
74
+ const sw = src.width
75
+ const mPitch = mw
76
+
77
+ let dIdx = (y * dw + x) | 0
78
+ let sIdx = (sy * sw + sx) | 0
79
+ let mIdx = ((my + dy) * mPitch + (mx + dx)) | 0
80
+
81
+ const dStride = (dw - actualW) | 0
82
+ const sStride = (sw - actualW) | 0
83
+ const mStride = (mPitch - actualW) | 0
84
+
85
+ const skipVal = invertMask ? 1 : 0
86
+ const isOpaque = globalAlpha === 255
87
+ const isOverwrite = blendFn.isOverwrite || false
88
+
89
+ for (let iy = 0; iy < actualH; iy++) {
90
+ for (let ix = 0; ix < actualW; ix++) {
91
+ // Binary Mask Check (Earliest exit)
92
+ if (binaryMask[mIdx] === skipVal) {
93
+ dIdx++
94
+ sIdx++
95
+ mIdx++
96
+ continue
97
+ }
98
+
99
+ const srcCol = src32[sIdx] as Color32
100
+ const srcAlpha = srcCol >>> 24
101
+
102
+ // Source Alpha Check
103
+ if (srcAlpha === 0 && !isOverwrite) {
104
+ dIdx++
105
+ sIdx++
106
+ mIdx++
107
+ continue
108
+ }
109
+
110
+ let finalCol = srcCol
111
+ if (!isOpaque) {
112
+ // Rounding-corrected global alpha application
113
+ const a = (srcAlpha * globalAlpha + 128) >> 8
114
+ if (a === 0 && !isOverwrite) {
115
+ dIdx++
116
+ sIdx++
117
+ mIdx++
118
+ continue
119
+ }
120
+ finalCol = ((srcCol & 0x00ffffff) | (a << 24)) >>> 0 as Color32
121
+ }
122
+
123
+ dst32[dIdx] = blendFn(finalCol, dst32[dIdx] as Color32)
124
+
125
+ dIdx++
126
+ sIdx++
127
+ mIdx++
128
+ }
129
+ dIdx += dStride
130
+ sIdx += sStride
131
+ mIdx += mStride
132
+ }
133
+ }
@@ -1,5 +1,4 @@
1
- import type { Color32, Rect } from '../_types'
2
- import type { PixelData } from './PixelData'
1
+ import type { Color32, IPixelData, Rect } from '../_types'
3
2
  import { fillPixelData } from './fillPixelData'
4
3
 
5
4
  /**
@@ -7,7 +6,7 @@ import { fillPixelData } from './fillPixelData'
7
6
  * Internally uses the optimized fillPixelData.
8
7
  */
9
8
  export function clearPixelData(
10
- dst: PixelData,
9
+ dst: IPixelData,
11
10
  rect?: Partial<Rect>,
12
11
  ): void {
13
12
  fillPixelData(dst, 0 as Color32, rect)
@@ -1,4 +1,4 @@
1
- import type { Rect } from '../_types'
1
+ import type { IPixelData, Rect } from '../_types'
2
2
  import { extractPixelDataBuffer } from './extractPixelDataBuffer'
3
3
  import { PixelData } from './PixelData'
4
4
 
@@ -6,10 +6,10 @@ import { PixelData } from './PixelData'
6
6
  * High-level extraction that returns a new PixelData instance.
7
7
  * Leverages extractPixelDataBuffer for optimized 32-bit memory moves.
8
8
  */
9
- export function extractPixelData(source: PixelData, rect: Rect): PixelData
10
- export function extractPixelData(source: PixelData, x: number, y: number, w: number, h: number): PixelData
9
+ export function extractPixelData(source: IPixelData, rect: Rect): PixelData
10
+ export function extractPixelData(source: IPixelData, x: number, y: number, w: number, h: number): PixelData
11
11
  export function extractPixelData(
12
- source: PixelData,
12
+ source: IPixelData,
13
13
  _x: Rect | number,
14
14
  _y?: number,
15
15
  _w?: number,
@@ -1,14 +1,16 @@
1
- import type { Rect } from '../_types'
2
- import type { PixelData } from './PixelData'
1
+ import type { IPixelData, Rect } from '../_types'
2
+ import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
3
+
4
+ const SCRATCH_BLIT = makeClippedBlit()
3
5
 
4
6
  /**
5
7
  * Extracts a rectangular region of pixels from PixelData.
6
8
  * Returns a new Uint32Array containing the extracted pixels.
7
9
  */
8
- export function extractPixelDataBuffer(source: PixelData, rect: Rect): Uint32Array
9
- export function extractPixelDataBuffer(source: PixelData, x: number, y: number, w: number, h: number): Uint32Array
10
+ export function extractPixelDataBuffer(source: IPixelData, rect: Rect): Uint32Array
11
+ export function extractPixelDataBuffer(source: IPixelData, x: number, y: number, w: number, h: number): Uint32Array
10
12
  export function extractPixelDataBuffer(
11
- source: PixelData,
13
+ source: IPixelData,
12
14
  _x: Rect | number,
13
15
  _y?: number,
14
16
  _w?: number,
@@ -27,33 +29,43 @@ export function extractPixelDataBuffer(
27
29
  return new Uint32Array(0)
28
30
  }
29
31
 
30
- // Create a new ImageData to get a clean, aligned buffer
31
- const dstImageData = new ImageData(w, h)
32
- const dstData = new Uint32Array(dstImageData.data.buffer)
33
-
34
- const x0 = Math.max(0, x)
35
- const y0 = Math.max(0, y)
36
- const x1 = Math.min(srcW, x + w)
37
- const y1 = Math.min(srcH, y + h)
32
+ const dstData = new Uint32Array(w * h)
38
33
 
39
- // Return empty buffer if no intersection
40
- if (x1 <= x0 || y1 <= y0) {
41
- return dstData
42
- }
34
+ // We map from Source (srcW, srcH) at (x,y)
35
+ // To Dest (w, h) at (0,0)
36
+ // Note: resolveBlitClipping usually takes (dstX, dstY, srcX, srcY...)
37
+ // Here we are "blitting" FROM x,y TO 0,0.
38
+ const clip = resolveBlitClipping(
39
+ 0,
40
+ 0,
41
+ x,
42
+ y,
43
+ w,
44
+ h,
45
+ w,
46
+ h,
47
+ srcW,
48
+ srcH,
49
+ SCRATCH_BLIT,
50
+ )
43
51
 
44
- const copyWidth = x1 - x0
45
- const copyHeight = y1 - y0
52
+ if (!clip.inBounds) return dstData
46
53
 
47
- for (let row = 0; row < copyHeight; row++) {
48
- const srcRow = y0 + row
49
- const srcStart = srcRow * srcW + x0
54
+ const {
55
+ x: dstX,
56
+ y: dstY,
57
+ sx: srcX,
58
+ sy: srcY,
59
+ w: copyW,
60
+ h: copyH,
61
+ } = clip
50
62
 
51
- const dstRow = (y0 - y) + row
52
- const dstCol = (x0 - x)
53
- const dstStart = dstRow * w + dstCol
63
+ for (let row = 0; row < copyH; row++) {
64
+ const srcStart = (srcY + row) * srcW + srcX
65
+ const dstStart = (dstY + row) * w + dstX
54
66
 
55
67
  // Perform the high-speed 32-bit bulk copy
56
- const chunk = srcData.subarray(srcStart, srcStart + copyWidth)
68
+ const chunk = srcData.subarray(srcStart, srcStart + copyW)
57
69
  dstData.set(chunk, dstStart)
58
70
  }
59
71
 
@@ -1,6 +1,9 @@
1
- import type { Color32, Rect } from '../_types'
1
+ import type { Color32, IPixelData, Rect } from '../_types'
2
+ import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
2
3
  import type { PixelData } from './PixelData'
3
4
 
5
+ const SCRATCH_RECT = makeClippedRect()
6
+
4
7
  /**
5
8
  * Fills a region or the {@link PixelData} buffer with a solid color.
6
9
  *
@@ -10,7 +13,7 @@ import type { PixelData } from './PixelData'
10
13
  * buffer is filled.
11
14
  */
12
15
  export function fillPixelData(
13
- dst: PixelData,
16
+ dst: IPixelData,
14
17
  color: Color32,
15
18
  rect?: Partial<Rect>,
16
19
  ): void
@@ -23,7 +26,7 @@ export function fillPixelData(
23
26
  * @param h - Height of the fill area.
24
27
  */
25
28
  export function fillPixelData(
26
- dst: PixelData,
29
+ dst: IPixelData,
27
30
  color: Color32,
28
31
  x: number,
29
32
  y: number,
@@ -31,7 +34,7 @@ export function fillPixelData(
31
34
  h: number,
32
35
  ): void
33
36
  export function fillPixelData(
34
- dst: PixelData,
37
+ dst: IPixelData,
35
38
  color: Color32,
36
39
  _x?: Partial<Rect> | number,
37
40
  _y?: number,
@@ -60,35 +63,30 @@ export function fillPixelData(
60
63
  h = dst.height
61
64
  }
62
65
 
63
- // Destination Clipping
64
- if (x < 0) {
65
- w += x
66
- x = 0
67
- }
68
- if (y < 0) {
69
- h += y
70
- y = 0
71
- }
66
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT)
72
67
 
73
- const actualW = Math.min(w, dst.width - x)
74
- const actualH = Math.min(h, dst.height - y)
68
+ if (!clip.inBounds) return
75
69
 
76
- if (actualW <= 0 || actualH <= 0) {
77
- return
78
- }
70
+ // Use the clipped values
71
+ const {
72
+ x: finalX,
73
+ y: finalY,
74
+ w: actualW,
75
+ h: actualH,
76
+ } = clip
79
77
 
80
78
  const dst32 = dst.data32
81
79
  const dw = dst.width
82
80
 
83
81
  // Optimization: If filling the entire buffer, use the native .fill()
84
- if (actualW === dw && actualH === dst.height && x === 0 && y === 0) {
82
+ if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
85
83
  dst32.fill(color)
86
84
  return
87
85
  }
88
86
 
89
87
  // Row-by-row fill for partial rectangles
90
88
  for (let iy = 0; iy < actualH; iy++) {
91
- const start = (y + iy) * dw + x
89
+ const start = (finalY + iy) * dw + finalX
92
90
  const end = start + actualW
93
91
  dst32.fill(color, start, end)
94
92
  }
@@ -1,15 +1,16 @@
1
- import { type PixelMutateOptions } from '../_types'
2
- import type { PixelData } from './PixelData'
1
+ import { type IPixelData, type PixelMutateOptions } from '../_types'
2
+ import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
3
+
4
+ const SCRATCH_RECT = makeClippedRect()
3
5
 
4
6
  export function invertPixelData(
5
- pixelData: PixelData,
7
+ pixelData: IPixelData,
6
8
  opts: PixelMutateOptions = {},
7
9
  ): void {
8
10
  const dst = pixelData
9
11
  const {
10
12
  x: targetX = 0,
11
13
  y: targetY = 0,
12
-
13
14
  w: width = pixelData.width,
14
15
  h: height = pixelData.height,
15
16
  mask,
@@ -19,25 +20,16 @@ export function invertPixelData(
19
20
  invertMask = false,
20
21
  } = opts
21
22
 
22
- let x = targetX
23
- let y = targetY
24
- let w = width
25
- let h = height
26
-
27
- // Destination Clipping
28
- if (x < 0) {
29
- w += x
30
- x = 0
31
- }
32
- if (y < 0) {
33
- h += y
34
- y = 0
35
- }
23
+ const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT)
36
24
 
37
- const actualW = Math.min(w, dst.width - x)
38
- const actualH = Math.min(h, dst.height - y)
25
+ if (!clip.inBounds) return
39
26
 
40
- if (actualW <= 0 || actualH <= 0) return
27
+ const {
28
+ x,
29
+ y,
30
+ w: actualW,
31
+ h: actualH,
32
+ } = clip
41
33
 
42
34
  const dst32 = dst.data32
43
35
  const dw = dst.width
@@ -1,12 +1,11 @@
1
- import type { AlphaMask } from '../_types'
2
- import type { PixelData } from './PixelData'
1
+ import type { AlphaMask, IPixelData } from '../_types'
3
2
 
4
3
  /**
5
4
  * Extracts the alpha channel from PixelData into a single-channel mask.
6
5
  * Returns a Uint8Array branded as AlphaMask.
7
6
  */
8
7
  export function pixelDataToAlphaMask(
9
- pixelData: PixelData,
8
+ pixelData: IPixelData,
10
9
  ): AlphaMask {
11
10
  const {
12
11
  data32,
@@ -1,6 +1,6 @@
1
- import type { PixelData } from './PixelData'
1
+ import type { IPixelData } from '../_types'
2
2
 
3
- export function reflectPixelDataHorizontal(pixelData: PixelData): void {
3
+ export function reflectPixelDataHorizontal(pixelData: IPixelData): void {
4
4
  const width = pixelData.width
5
5
  const height = pixelData.height
6
6
  const data = pixelData.data32
@@ -20,7 +20,7 @@ export function reflectPixelDataHorizontal(pixelData: PixelData): void {
20
20
  }
21
21
  }
22
22
 
23
- export function reflectPixelDataVertical(pixelData: PixelData): void {
23
+ export function reflectPixelDataVertical(pixelData: IPixelData): void {
24
24
  const width = pixelData.width
25
25
  const height = pixelData.height
26
26
  const data = pixelData.data32
@@ -1,8 +1,4 @@
1
- /**
2
- * Resamples an PixelData by a specific factor using nearest neighbor
3
- * Factor > 1 upscales, Factor < 1 downscales.
4
- */
5
- import { PixelData } from '../index'
1
+ import { type IPixelData, PixelData } from '../index'
6
2
  import { resample32 } from '../Internal/resample32'
7
3
 
8
4
  /**
@@ -10,7 +6,7 @@ import { resample32 } from '../Internal/resample32'
10
6
  * Factor > 1 upscales, Factor < 1 downscales.
11
7
  */
12
8
  export function resamplePixelData(
13
- pixelData: PixelData,
9
+ pixelData: IPixelData,
14
10
  factor: number,
15
11
  ): PixelData {
16
12
  const { data, width, height } = resample32(pixelData.data32, pixelData.width, pixelData.height, factor)
@@ -1,5 +1,8 @@
1
+ import { type IPixelData, type Rect } from '../_types'
2
+ import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
1
3
  import type { PixelData } from './PixelData'
2
- import { type Rect } from '../_types'
4
+
5
+ const SCRATCH_BLIT = makeClippedBlit()
3
6
 
4
7
  /**
5
8
  * Copies a pixel buffer into a specific region of a {@link PixelData} object.
@@ -8,12 +11,12 @@ import { type Rect } from '../_types'
8
11
  * into the target {@link PixelData} buffer.
9
12
  */
10
13
  export function writePixelDataBuffer(
11
- target: PixelData,
14
+ target: IPixelData,
12
15
  data: Uint32Array,
13
16
  rect: Rect,
14
17
  ): void
15
18
  export function writePixelDataBuffer(
16
- target: PixelData,
19
+ target: IPixelData,
17
20
  data: Uint32Array,
18
21
  x: number,
19
22
  y: number,
@@ -21,7 +24,7 @@ export function writePixelDataBuffer(
21
24
  h: number,
22
25
  ): void
23
26
  export function writePixelDataBuffer(
24
- target: PixelData,
27
+ target: IPixelData,
25
28
  data: Uint32Array,
26
29
  _x: Rect | number,
27
30
  _y?: number,
@@ -41,25 +44,36 @@ export function writePixelDataBuffer(
41
44
  const dstH = target.height
42
45
  const dstData = target.data32
43
46
 
44
- const x0 = Math.max(0, x)
45
- const y0 = Math.max(0, y)
46
- const x1 = Math.min(dstW, x + w)
47
- const y1 = Math.min(dstH, y + h)
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
+ )
48
61
 
49
- if (x1 <= x0 || y1 <= y0) {
50
- return
51
- }
62
+ if (!clip.inBounds) return
52
63
 
53
- const rowLen = x1 - x0
54
- const srcCol = x0 - x
55
- const srcYOffset = y0 - y
56
- const actualH = y1 - y0
64
+ const {
65
+ x: dstX,
66
+ y: dstY,
67
+ sx: srcX,
68
+ sy: srcY,
69
+ w: copyW,
70
+ h: copyH,
71
+ } = clip
57
72
 
58
- for (let row = 0; row < actualH; row++) {
59
- const dstStart = (y0 + row) * dstW + x0
60
- const srcRow = srcYOffset + row
61
- const srcStart = srcRow * w + srcCol
73
+ for (let row = 0; row < copyH; row++) {
74
+ const dstStart = (dstY + row) * dstW + dstX
75
+ const srcStart = (srcY + row) * w + srcX
62
76
 
63
- dstData.set(data.subarray(srcStart, srcStart + rowLen), dstStart)
77
+ dstData.set(data.subarray(srcStart, srcStart + copyW), dstStart)
64
78
  }
65
79
  }
@@ -0,0 +1,43 @@
1
+ import type { Rect } from '../_types'
2
+
3
+ export function getCircleBrushOrPencilBounds(
4
+ centerX: number,
5
+ centerY: number,
6
+ brushSize: number,
7
+ targetWidth: number,
8
+ targetHeight: number,
9
+ out?: Rect,
10
+ ): Rect {
11
+ const r = brushSize / 2
12
+
13
+ const minOffset = -Math.ceil(r - 0.5)
14
+ const maxOffset = Math.floor(r - 0.5)
15
+
16
+ // start is inclusive, end is exclusive
17
+ const startX = Math.floor(centerX + minOffset)
18
+ const startY = Math.floor(centerY + minOffset)
19
+ const endX = Math.floor(centerX + maxOffset) + 1
20
+ const endY = Math.floor(centerY + maxOffset) + 1
21
+
22
+ const res = out ?? {
23
+ x: 0,
24
+ y: 0,
25
+ w: 0,
26
+ h: 0,
27
+ }
28
+
29
+ const cStartX = Math.max(0, startX)
30
+ const cStartY = Math.max(0, startY)
31
+ const cEndX = Math.min(targetWidth, endX)
32
+ const cEndY = Math.min(targetHeight, endY)
33
+
34
+ const w = cEndX - cStartX
35
+ const h = cEndY - cStartY
36
+
37
+ res.x = cStartX
38
+ res.y = cStartY
39
+ res.w = w < 0 ? 0 : w
40
+ res.h = h < 0 ? 0 : h
41
+
42
+ return res
43
+ }
@@ -0,0 +1,24 @@
1
+ import type { Rect } from '../_types'
2
+
3
+ export function getCircleBrushOrPencilStrokeBounds(
4
+ x0: number,
5
+ y0: number,
6
+ x1: number,
7
+ y1: number,
8
+ brushSize: number,
9
+ result: Rect,
10
+ ): Rect {
11
+ const r = Math.ceil(brushSize / 2)
12
+
13
+ const minX = Math.min(x0, x1) - r
14
+ const minY = Math.min(y0, y1) - r
15
+ const maxX = Math.max(x0, x1) + r
16
+ const maxY = Math.max(x0, y1) + r
17
+
18
+ result.x = Math.floor(minX)
19
+ result.y = Math.floor(minY)
20
+ result.w = Math.ceil(maxX - minX)
21
+ result.h = Math.ceil(maxY - minY)
22
+
23
+ return result
24
+ }
@@ -0,0 +1,38 @@
1
+ import type { Rect } from '../_types'
2
+
3
+ export function getRectBrushOrPencilBounds(
4
+ centerX: number,
5
+ centerY: number,
6
+ brushWidth: number,
7
+ brushHeight: number,
8
+ targetWidth: number,
9
+ targetHeight: number,
10
+ out?: Rect,
11
+ ): Rect {
12
+ const startX = Math.floor(centerX - brushWidth / 2)
13
+ const startY = Math.floor(centerY - brushHeight / 2)
14
+ const endX = startX + brushWidth
15
+ const endY = startY + brushHeight
16
+
17
+ const res = out ?? {
18
+ x: 0,
19
+ y: 0,
20
+ w: 0,
21
+ h: 0,
22
+ }
23
+
24
+ const cStartX = Math.max(0, startX)
25
+ const cStartY = Math.max(0, startY)
26
+ const cEndX = Math.min(targetWidth, endX)
27
+ const cEndY = Math.min(targetHeight, endY)
28
+
29
+ const w = cEndX - cStartX
30
+ const h = cEndY - cStartY
31
+
32
+ res.x = cStartX
33
+ res.y = cStartY
34
+ res.w = w < 0 ? 0 : w
35
+ res.h = h < 0 ? 0 : h
36
+
37
+ return res
38
+ }
@@ -0,0 +1,26 @@
1
+ import type { Rect } from '../_types'
2
+
3
+ export function getRectBrushOrPencilStrokeBounds(
4
+ x0: number,
5
+ y0: number,
6
+ x1: number,
7
+ y1: number,
8
+ brushWidth: number,
9
+ brushHeight: number,
10
+ result: Rect,
11
+ ): Rect {
12
+ const halfW = brushWidth / 2
13
+ const halfH = brushHeight / 2
14
+
15
+ const minX = Math.min(x0, x1) - halfW
16
+ const minY = Math.min(y0, y1) - halfH
17
+ const maxX = Math.max(x0, x1) + halfW
18
+ const maxY = Math.max(y0, y1) + halfH
19
+
20
+ result.x = Math.floor(minX)
21
+ result.y = Math.floor(minY)
22
+ result.w = Math.ceil(maxX - minX)
23
+ result.h = Math.ceil(maxY - minY)
24
+
25
+ return result
26
+ }