pixel-data-js 0.15.1 → 0.17.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 (36) hide show
  1. package/dist/index.dev.cjs +1884 -1203
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +1858 -1190
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +1884 -1203
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +320 -66
  8. package/dist/index.prod.js +1858 -1190
  9. package/dist/index.prod.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/BlendModes/BlendModeRegistry.ts +62 -0
  12. package/src/BlendModes/blend-modes-fast.ts +31 -84
  13. package/src/BlendModes/blend-modes-perfect.ts +343 -215
  14. package/src/BlendModes/blend-modes.ts +28 -30
  15. package/src/History/HistoryManager.ts +83 -0
  16. package/src/History/PixelAccumulator.ts +191 -0
  17. package/src/History/PixelEngineConfig.ts +18 -0
  18. package/src/History/PixelMutator/mutatorApplyMask.ts +20 -0
  19. package/src/History/PixelMutator/mutatorBlendColor.ts +22 -0
  20. package/src/History/PixelMutator/mutatorBlendPixel.ts +37 -0
  21. package/src/History/PixelMutator/mutatorBlendPixelData.ts +24 -0
  22. package/src/History/PixelMutator/mutatorFillPixelData.ts +21 -0
  23. package/src/History/PixelMutator/mutatorInvert.ts +18 -0
  24. package/src/History/PixelMutator.ts +18 -0
  25. package/src/History/PixelPatchTiles.ts +52 -0
  26. package/src/History/PixelWriter.ts +79 -0
  27. package/src/ImageData/{writeImageDataPixels.ts → writeImageDataBuffer.ts} +3 -3
  28. package/src/PixelData/applyCircleBrushToPixelData.ts +69 -0
  29. package/src/PixelData/applyMaskToPixelData.ts +1 -1
  30. package/src/PixelData/applyRectBrushToPixelData.ts +102 -0
  31. package/src/PixelData/blendPixelData.ts +2 -3
  32. package/src/PixelData/invertPixelData.ts +74 -7
  33. package/src/PixelData/writePixelDataBuffer.ts +65 -0
  34. package/src/_types.ts +31 -11
  35. package/src/index.ts +20 -10
  36. package/src/BlendModes/blend-mode-getters.ts +0 -14
@@ -0,0 +1,69 @@
1
+ import type { BlendColor32, Color32 } from '../_types'
2
+ import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
3
+ import type { PixelData } from './PixelData'
4
+
5
+ /**
6
+ * Applies a circular brush to pixel data, blending a color with optional falloff.
7
+ *
8
+ * @param target The PixelData to modify.
9
+ * @param color The brush color.
10
+ * @param centerX The center x-coordinate of the brush.
11
+ * @param centerY The center y-coordinate of the brush.
12
+ * @param brushSize The diameter of the brush.
13
+ * @param alpha The overall opacity of the brush (0-255).
14
+ * @default 255
15
+ * @param fallOff A function that returns an alpha multiplier (0-1) based on the normalized distance (0-1) from the circle's center.
16
+ * @param blendFn
17
+ * @default sourceOverPerfect
18
+ */
19
+ export function applyCircleBrushToPixelData(
20
+ target: PixelData,
21
+ color: Color32,
22
+ centerX: number,
23
+ centerY: number,
24
+ brushSize: number,
25
+ alpha = 255,
26
+ fallOff?: (dist: number) => number,
27
+ blendFn: BlendColor32 = sourceOverPerfect,
28
+ ): void {
29
+ const r = brushSize / 2
30
+ const rSqr = r * r
31
+ const centerOffset = (brushSize % 2 === 0) ? 0.5 : 0
32
+
33
+ const xStart = Math.max(0, Math.ceil(centerX - r))
34
+ const xEnd = Math.min(target.width - 1, Math.floor(centerX + r))
35
+ const yStart = Math.max(0, Math.ceil(centerY - r))
36
+ const yEnd = Math.min(target.height - 1, Math.floor(centerY + r))
37
+
38
+ const data32 = target.data32
39
+ const targetWidth = target.width
40
+ const baseColor = color & 0x00ffffff
41
+ const invR = 1 / r
42
+
43
+ // Pre-calculate the constant source for cases where fallOff is null
44
+ const constantSrc = ((alpha << 24) | baseColor) >>> 0 as Color32
45
+
46
+ for (let cy = yStart; cy <= yEnd; cy++) {
47
+ const dy = cy - centerY + centerOffset
48
+ const dySqr = dy * dy
49
+ const rowOffset = cy * targetWidth
50
+
51
+ for (let cx = xStart; cx <= xEnd; cx++) {
52
+ const dx = cx - centerX + centerOffset
53
+ const dSqr = dx * dx + dySqr
54
+
55
+ if (dSqr <= rSqr) {
56
+ const idx = rowOffset + cx
57
+
58
+ if (fallOff) {
59
+ const strength = fallOff(Math.sqrt(dSqr) * invR)
60
+ const fAlpha = (alpha * strength) & 0xFF
61
+ const src = ((fAlpha << 24) | baseColor) >>> 0 as Color32
62
+ data32[idx] = blendFn(src, data32[idx] as Color32)
63
+ } else {
64
+ data32[idx] = blendFn(constantSrc, data32[idx] as Color32)
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
@@ -8,7 +8,7 @@ import type { PixelData } from './PixelData'
8
8
  export function applyMaskToPixelData(
9
9
  dst: PixelData,
10
10
  mask: AnyMask,
11
- opts: ApplyMaskOptions,
11
+ opts: ApplyMaskOptions = {},
12
12
  ): void {
13
13
  const {
14
14
  x: targetX = 0,
@@ -0,0 +1,102 @@
1
+ import type { BlendColor32, Color32, Rect } from '../_types'
2
+ import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
3
+ import { PixelData } from './PixelData'
4
+
5
+ /**
6
+ * Applies a rectangular brush to pixel data, blending a color with optional falloff.
7
+ *
8
+ * @param target The PixelData to modify.
9
+ * @param color The brush color.
10
+ * @param centerX The center x-coordinate of the brush.
11
+ * @param centerY The center y-coordinate of the brush.
12
+ * @param brushWidth
13
+ * @param brushHeight
14
+ * @param alpha The overall opacity of the brush (0-255).
15
+ * @default 255
16
+ * @param fallOff A function that returns an alpha multiplier (0-1) based on the normalized distance (0-1) from the circle's center.
17
+ * @param blendFn
18
+ * @default sourceOverPerfect
19
+ */
20
+ export function applyRectBrushToPixelData(
21
+ target: PixelData,
22
+ color: Color32,
23
+ centerX: number,
24
+ centerY: number,
25
+ brushWidth: number,
26
+ brushHeight: number,
27
+ alpha = 255,
28
+ fallOff?: (dist: number) => number,
29
+ blendFn: BlendColor32 = sourceOverPerfect,
30
+ ): void {
31
+ const targetWidth = target.width
32
+ const targetHeight = target.height
33
+ const data32 = target.data32
34
+
35
+ const rawStartX = Math.floor(centerX - brushWidth / 2)
36
+ const rawStartY = Math.floor(centerY - brushHeight / 2)
37
+ const endX = Math.min(targetWidth, rawStartX + brushWidth)
38
+ const endY = Math.min(targetHeight, rawStartY + brushHeight)
39
+ const startX = Math.max(0, rawStartX)
40
+ const startY = Math.max(0, rawStartY)
41
+
42
+ const baseColor = color & 0x00ffffff
43
+ const constantSrc = ((alpha << 24) | baseColor) >>> 0 as Color32
44
+ const invHalfW = 1 / (brushWidth / 2)
45
+ const invHalfH = 1 / (brushHeight / 2)
46
+
47
+ for (let py = startY; py < endY; py++) {
48
+ const rowOffset = py * targetWidth
49
+ const dy = Math.abs(py + 0.5 - centerY) * invHalfH
50
+
51
+ for (let px = startX; px < endX; px++) {
52
+ if (fallOff) {
53
+ const dx = Math.abs(px + 0.5 - centerX) * invHalfW
54
+ const dist = dx > dy ? dx : dy
55
+ const fAlpha = (alpha * fallOff(dist)) | 0
56
+ const src = ((fAlpha << 24) | baseColor) >>> 0 as Color32
57
+ data32[rowOffset + px] = blendFn(src, data32[rowOffset + px] as Color32)
58
+ } else {
59
+ data32[rowOffset + px] = blendFn(constantSrc, data32[rowOffset + px] as Color32)
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ export function getRectBrushBounds(
66
+ centerX: number,
67
+ centerY: number,
68
+ brushWidth: number,
69
+ brushHeight: number,
70
+ targetWidth?: number,
71
+ targetHeight?: number,
72
+ ): Rect {
73
+
74
+ const rawStartX = Math.floor(centerX - brushWidth / 2)
75
+ const rawStartY = Math.floor(centerY - brushHeight / 2)
76
+
77
+ const rawEndX = rawStartX + brushWidth
78
+ const rawEndY = rawStartY + brushHeight
79
+
80
+ const startX = targetWidth !== undefined
81
+ ? Math.max(0, rawStartX)
82
+ : rawStartX
83
+
84
+ const startY = targetHeight !== undefined
85
+ ? Math.max(0, rawStartY)
86
+ : rawStartY
87
+
88
+ const endX = targetWidth !== undefined
89
+ ? Math.min(targetWidth, rawEndX)
90
+ : rawEndX
91
+
92
+ const endY = targetHeight !== undefined
93
+ ? Math.min(targetHeight, rawEndY)
94
+ : rawEndY
95
+
96
+ return {
97
+ x: startX,
98
+ y: startY,
99
+ w: endX - startX,
100
+ h: endY - startY,
101
+ }
102
+ }
@@ -1,6 +1,5 @@
1
1
  import { type Color32, MaskType, type PixelBlendOptions } from '../_types'
2
- import { BlendMode } from '../BlendModes/blend-modes'
3
- import { FAST_BLEND_MODES } from '../BlendModes/blend-modes-fast'
2
+ import { sourceOverFast } from '../BlendModes/blend-modes-fast'
4
3
  import type { PixelData } from './PixelData'
5
4
 
6
5
  /**
@@ -30,7 +29,7 @@ export function blendPixelData(
30
29
  w: width = src.width,
31
30
  h: height = src.height,
32
31
  alpha: globalAlpha = 255,
33
- blendFn = FAST_BLEND_MODES[BlendMode.sourceOver],
32
+ blendFn = sourceOverFast,
34
33
  mask,
35
34
  maskType = MaskType.ALPHA,
36
35
  mw,
@@ -1,16 +1,83 @@
1
+ import { type PixelMutateOptions } from '../_types'
1
2
  import type { PixelData } from './PixelData'
2
3
 
3
4
  export function invertPixelData(
4
5
  pixelData: PixelData,
5
- ): PixelData {
6
+ opts: PixelMutateOptions = {},
7
+ ): void {
8
+ const dst = pixelData
9
+ const {
10
+ x: targetX = 0,
11
+ y: targetY = 0,
6
12
 
7
- const data32 = pixelData.data32
8
- const len = data32.length
13
+ w: width = pixelData.width,
14
+ h: height = pixelData.height,
15
+ mask,
16
+ mw,
17
+ mx = 0,
18
+ my = 0,
19
+ invertMask = false,
20
+ } = opts
9
21
 
10
- for (let i = 0; i < len; i++) {
11
- // XOR with 0x00FFFFFF flips RGB bits and ignores Alpha (the top 8 bits)
12
- data32[i] = data32[i] ^ 0x00FFFFFF
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
13
35
  }
14
36
 
15
- return pixelData
37
+ const actualW = Math.min(w, dst.width - x)
38
+ const actualH = Math.min(h, dst.height - y)
39
+
40
+ if (actualW <= 0 || actualH <= 0) return
41
+
42
+ const dst32 = dst.data32
43
+ const dw = dst.width
44
+ const mPitch = mw ?? width
45
+
46
+ const dx = x - targetX
47
+ const dy = y - targetY
48
+
49
+ let dIdx = y * dw + x
50
+ let mIdx = (my + dy) * mPitch + (mx + dx)
51
+
52
+ const dStride = dw - actualW
53
+ const mStride = mPitch - actualW
54
+
55
+ // Optimization: Split loops to avoid checking `if (mask)` for every pixel.
56
+ if (mask) {
57
+ for (let iy = 0; iy < actualH; iy++) {
58
+ for (let ix = 0; ix < actualW; ix++) {
59
+ const mVal = mask[mIdx]
60
+ const isHit = invertMask
61
+ ? mVal === 0
62
+ : mVal === 1
63
+
64
+ if (isHit) {
65
+ // XOR with 0x00FFFFFF flips RGB bits and ignores Alpha (the top 8 bits)
66
+ dst32[dIdx] = dst32[dIdx] ^ 0x00FFFFFF
67
+ }
68
+ dIdx++
69
+ mIdx++
70
+ }
71
+ dIdx += dStride
72
+ mIdx += mStride
73
+ }
74
+ } else {
75
+ for (let iy = 0; iy < actualH; iy++) {
76
+ for (let ix = 0; ix < actualW; ix++) {
77
+ dst32[dIdx] = dst32[dIdx] ^ 0x00FFFFFF
78
+ dIdx++
79
+ }
80
+ dIdx += dStride
81
+ }
82
+ }
16
83
  }
@@ -0,0 +1,65 @@
1
+ import type { PixelData } from './PixelData'
2
+ import { type Rect } from '../_types'
3
+
4
+ /**
5
+ * Copies a pixel buffer into a specific region of a {@link PixelData} object.
6
+ *
7
+ * This function performs a direct memory copy from a {@link Uint32Array}
8
+ * into the target {@link PixelData} buffer.
9
+ */
10
+ export function writePixelDataBuffer(
11
+ target: PixelData,
12
+ data: Uint32Array,
13
+ rect: Rect,
14
+ ): void
15
+ export function writePixelDataBuffer(
16
+ target: PixelData,
17
+ data: Uint32Array,
18
+ x: number,
19
+ y: number,
20
+ w: number,
21
+ h: number,
22
+ ): void
23
+ export function writePixelDataBuffer(
24
+ target: PixelData,
25
+ data: Uint32Array,
26
+ _x: Rect | number,
27
+ _y?: number,
28
+ _w?: number,
29
+ _h?: number,
30
+ ): void {
31
+ const { x, y, w, h } = typeof _x === 'object'
32
+ ? _x
33
+ : {
34
+ x: _x,
35
+ y: _y!,
36
+ w: _w!,
37
+ h: _h!,
38
+ }
39
+
40
+ const dstW = target.width
41
+ const dstH = target.height
42
+ const dstData = target.data32
43
+
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)
48
+
49
+ if (x1 <= x0 || y1 <= y0) {
50
+ return
51
+ }
52
+
53
+ const rowLen = x1 - x0
54
+ const srcCol = x0 - x
55
+ const srcYOffset = y0 - y
56
+ const actualH = y1 - y0
57
+
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
62
+
63
+ dstData.set(data.subarray(srcStart, srcStart + rowLen), dstStart)
64
+ }
65
+ }
package/src/_types.ts CHANGED
@@ -66,33 +66,33 @@ export type AnyMask = BinaryMask | AlphaMask
66
66
  * Configuration for pixel manipulation operations.
67
67
  * Designed to be used by spreading a Rect object ({x, y, w, h}) directly.
68
68
  */
69
- export interface PixelOptions {
69
+ export interface PixelRectOptions {
70
70
  /**
71
71
  * The starting X coordinate in the destination buffer.
72
72
  * @default 0
73
73
  */
74
74
  x?: number
75
+
75
76
  /**
76
77
  * The starting Y coordinate in the destination buffer.
77
78
  * @default 0
78
79
  */
79
80
  y?: number
81
+
80
82
  /**
81
83
  * The width of the region to process.
82
84
  * @default Source width.
83
85
  */
84
86
  w?: number
87
+
85
88
  /**
86
89
  * The height of the region to process.
87
90
  * @default Source height.
88
91
  */
89
92
  h?: number
93
+ }
90
94
 
91
- /**
92
- * Overall layer opacity 0-255.
93
- * @default 255
94
- */
95
- alpha?: number
95
+ export interface PixelMaskRectOptions {
96
96
 
97
97
  /**
98
98
  * Mask width.
@@ -111,6 +111,27 @@ export interface PixelOptions {
111
111
  * @default 0
112
112
  */
113
113
  my?: number
114
+ }
115
+
116
+ export interface PixelMaskInvertOptions {
117
+
118
+ /**
119
+ * If true the inverse of the mask will be applied
120
+ * @default false
121
+ */
122
+ invertMask?: boolean
123
+ }
124
+
125
+ /**
126
+ * Configuration for pixel manipulation operations.
127
+ * Designed to be used by spreading a Rect object ({x, y, w, h}) directly.
128
+ */
129
+ export interface PixelOptions extends PixelRectOptions, PixelMaskRectOptions, PixelMaskInvertOptions {
130
+ /**
131
+ * Overall layer opacity 0-255.
132
+ * @default 255
133
+ */
134
+ alpha?: number
114
135
 
115
136
  /** An optional mask to restrict where pixels are written. */
116
137
  mask?: AnyMask | null
@@ -119,12 +140,11 @@ export interface PixelOptions {
119
140
  * @default {@link MaskType.ALPHA}
120
141
  */
121
142
  maskType?: MaskType
143
+ }
122
144
 
123
- /**
124
- * If true the inverse of the mask will be applied
125
- * @default false
126
- */
127
- invertMask?: boolean
145
+ export interface PixelMutateOptions extends PixelRectOptions, PixelMaskRectOptions, PixelMaskInvertOptions {
146
+ /** An optional mask to restrict where pixels are mutated. */
147
+ mask?: BinaryMask | null
128
148
  }
129
149
 
130
150
  /**
package/src/index.ts CHANGED
@@ -3,15 +3,8 @@ export * from './color'
3
3
 
4
4
  export * from './Algorithm/floodFillSelection'
5
5
 
6
- export {
7
- BlendMode,
8
- type BlendModeIndex,
9
- } from './BlendModes/blend-modes'
10
- export {
11
- type BlendToIndexGetter,
12
- type IndexToBlendGetter,
13
- } from './BlendModes/blend-mode-getters'
14
-
6
+ export * from './BlendModes/BlendModeRegistry'
7
+ export * from './BlendModes/blend-modes'
15
8
  export * from './BlendModes/blend-modes-fast'
16
9
  export * from './BlendModes/blend-modes-perfect'
17
10
 
@@ -22,6 +15,20 @@ export * from './Clipboard/getImageDataFromClipboard'
22
15
  export * from './Clipboard/writeImageDataToClipboard'
23
16
  export * from './Clipboard/writeImgBlobToClipboard'
24
17
 
18
+ export * from './History/PixelAccumulator'
19
+ export * from './History/HistoryManager'
20
+ export * from './History/PixelEngineConfig'
21
+ export * from './History/PixelMutator'
22
+ export * from './History/PixelPatchTiles'
23
+ export * from './History/PixelWriter'
24
+
25
+ export * from './History/PixelMutator/mutatorApplyMask'
26
+ export * from './History/PixelMutator/mutatorBlendColor'
27
+ export * from './History/PixelMutator/mutatorBlendPixel'
28
+ export * from './History/PixelMutator/mutatorBlendPixelData'
29
+ export * from './History/PixelMutator/mutatorFillPixelData'
30
+ export * from './History/PixelMutator/mutatorInvert'
31
+
25
32
  export * from './ImageData/ReusableImageData'
26
33
  export * from './ImageData/copyImageData'
27
34
  export * from './ImageData/extractImageDataBuffer'
@@ -35,7 +42,7 @@ export * from './ImageData/resampleImageData'
35
42
  export * from './ImageData/resizeImageData'
36
43
  export * from './ImageData/serialization'
37
44
  export * from './ImageData/writeImageData'
38
- export * from './ImageData/writeImageDataPixels'
45
+ export * from './ImageData/writeImageDataBuffer'
39
46
 
40
47
  export * from './IndexedImage/IndexedImage'
41
48
  export * from './IndexedImage/getIndexedImageColorCounts'
@@ -53,7 +60,9 @@ export * from './Mask/invertMask'
53
60
  export * from './Mask/mergeMasks'
54
61
 
55
62
  export * from './PixelData/PixelData'
63
+ export * from './PixelData/applyCircleBrushToPixelData'
56
64
  export * from './PixelData/applyMaskToPixelData'
65
+ export * from './PixelData/applyRectBrushToPixelData'
57
66
  export * from './PixelData/blendColorPixelData'
58
67
  export * from './PixelData/blendPixelData'
59
68
  export * from './PixelData/clearPixelData'
@@ -65,5 +74,6 @@ export * from './PixelData/pixelDataToAlphaMask'
65
74
  export * from './PixelData/reflectPixelData'
66
75
  export * from './PixelData/resamplePixelData'
67
76
  export * from './PixelData/rotatePixelData'
77
+ export * from './PixelData/writePixelDataBuffer'
68
78
 
69
79
  export * from './Rect/trimRectBounds'
@@ -1,14 +0,0 @@
1
- import type { BlendColor32 } from '../_types'
2
- import type { BlendModeIndex } from './blend-modes'
3
- import { FAST_BLEND_TO_INDEX, INDEX_TO_FAST_BLEND } from './blend-modes-fast'
4
- import { type INDEX_TO_PERFECT_BLEND, PERFECT_BLEND_TO_INDEX } from './blend-modes-perfect'
5
-
6
- export type BaseIndexToBlendGetter<B extends BlendColor32> = {
7
- get: (index: BlendModeIndex) => B
8
- }
9
- export type IndexToBlendGetter = typeof INDEX_TO_FAST_BLEND | typeof INDEX_TO_PERFECT_BLEND
10
-
11
- export type BaseBlendToIndexGetter<B extends BlendColor32> = {
12
- get: (blend: B) => BlendModeIndex
13
- }
14
- export type BlendToIndexGetter = typeof FAST_BLEND_TO_INDEX | typeof PERFECT_BLEND_TO_INDEX