pixel-data-js 0.36.0 → 0.38.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 (73) hide show
  1. package/dist/index.prod.cjs +376 -305
  2. package/dist/index.prod.cjs.map +1 -1
  3. package/dist/index.prod.d.ts +109 -67
  4. package/dist/index.prod.js +372 -304
  5. package/dist/index.prod.js.map +1 -1
  6. package/package.json +2 -2
  7. package/src/Algorithm/floodFillSelection.ts +3 -2
  8. package/src/BlendModes/blend-modes-fast.ts +2 -1
  9. package/src/BlendModes/blend-modes-perfect.ts +2 -1
  10. package/src/Canvas/ReusableCanvas.ts +0 -5
  11. package/src/Color/_color-types.ts +8 -0
  12. package/src/Color/colorDistance.ts +9 -0
  13. package/src/Color/convert-color.ts +43 -0
  14. package/src/Color/lerpColor32.ts +44 -0
  15. package/src/Color/pack-color.ts +38 -0
  16. package/src/History/HistoryAction.ts +2 -2
  17. package/src/History/PixelAccumulator.ts +13 -15
  18. package/src/History/PixelMutator/mutatorBlendColor.ts +2 -1
  19. package/src/History/PixelMutator/mutatorBlendColorPaintAlphaMask.ts +1 -1
  20. package/src/History/PixelMutator/mutatorBlendColorPaintBinaryMask.ts +1 -1
  21. package/src/History/PixelMutator/mutatorBlendColorPaintMask.ts +23 -8
  22. package/src/History/PixelMutator/mutatorBlendColorPaintRect.ts +2 -1
  23. package/src/History/PixelMutator/mutatorBlendPixel.ts +2 -1
  24. package/src/History/PixelMutator/mutatorClear.ts +1 -1
  25. package/src/History/PixelMutator/mutatorFill.ts +1 -1
  26. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +1 -1
  27. package/src/History/PixelWriter.ts +5 -5
  28. package/src/ImageData/serialization.ts +7 -2
  29. package/src/IndexedImage/IndexedImage.ts +1 -1
  30. package/src/IndexedImage/indexedImageToAverageColor.ts +3 -2
  31. package/src/Mask/_mask-types.ts +9 -0
  32. package/src/Paint/AlphaMaskPaintBuffer.ts +26 -26
  33. package/src/Paint/BinaryMaskPaintBuffer.ts +19 -19
  34. package/src/Paint/ColorPaintBuffer.ts +40 -42
  35. package/src/Paint/Commit/AlphaMaskPaintBufferCommitter.ts +1 -1
  36. package/src/Paint/Commit/AlphaMaskPaintBufferManager.ts +6 -7
  37. package/src/Paint/Commit/BinaryMaskPaintBufferCommitter.ts +1 -1
  38. package/src/Paint/Commit/BinaryMaskPaintBufferManager.ts +6 -7
  39. package/src/Paint/Commit/ColorPaintBufferManager.ts +6 -7
  40. package/src/Paint/Commit/commitColorPaintBuffer.ts +2 -6
  41. package/src/Paint/Commit/commitMaskPaintBuffer.ts +3 -7
  42. package/src/Paint/Render/AlphaMaskPaintBufferCanvasRenderer.ts +42 -25
  43. package/src/Paint/Render/BinaryMaskPaintBufferCanvasRenderer.ts +40 -24
  44. package/src/Paint/Render/ColorPaintBufferCanvasRenderer.ts +21 -21
  45. package/src/Paint/Render/PaintCursorRenderer.ts +3 -2
  46. package/src/Paint/eachTileInBounds.ts +9 -10
  47. package/src/PixelData/blendColorPixelData.ts +2 -1
  48. package/src/PixelData/blendColorPixelDataAlphaMask.ts +2 -1
  49. package/src/PixelData/blendColorPixelDataBinaryMask.ts +2 -1
  50. package/src/PixelData/blendColorPixelDataMask.ts +2 -1
  51. package/src/PixelData/blendColorPixelDataPaintAlphaMask.ts +1 -1
  52. package/src/PixelData/blendColorPixelDataPaintBinaryMask.ts +1 -1
  53. package/src/PixelData/blendColorPixelDataPaintMask.ts +19 -8
  54. package/src/PixelData/blendPixel.ts +2 -1
  55. package/src/PixelData/blendPixelData.ts +2 -1
  56. package/src/PixelData/blendPixelDataAlphaMask.ts +2 -1
  57. package/src/PixelData/blendPixelDataBinaryMask.ts +2 -1
  58. package/src/PixelData/blendPixelDataPaintBuffer.ts +2 -3
  59. package/src/PixelData/clearPixelDataFast.ts +1 -1
  60. package/src/PixelData/fillPixelData.ts +1 -1
  61. package/src/PixelData/fillPixelDataBinaryMask.ts +1 -1
  62. package/src/PixelData/fillPixelDataFast.ts +1 -1
  63. package/src/PixelData/writePaintBufferToPixelData.ts +1 -5
  64. package/src/Tile/MaskTile.ts +4 -0
  65. package/src/Tile/PixelTile.ts +2 -0
  66. package/src/Tile/TilePool.ts +9 -8
  67. package/src/Tile/TileTargetConfig.ts +27 -0
  68. package/src/Tile/_tile-types.ts +16 -0
  69. package/src/_types.ts +1 -6
  70. package/src/index.ts +7 -3
  71. package/src/History/PixelEngineConfig.ts +0 -28
  72. package/src/Internal/_constants.ts +0 -3
  73. package/src/color.ts +0 -112
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pixel-data-js",
3
3
  "type": "module",
4
- "version": "0.36.0",
4
+ "version": "0.38.0",
5
5
  "packageManager": "pnpm@10.33.0",
6
6
  "description": "JS Pixel and ImageData operations",
7
7
  "author": {
@@ -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.15.0",
91
+ "unplugin-inline": "^1.16.0",
92
92
  "vite-tsconfig-paths": "^6.1.1",
93
93
  "vitest": "3.2.4"
94
94
  },
@@ -1,5 +1,6 @@
1
- import { type Color32 } from '../_types'
2
- import { colorDistance } from '../color'
1
+ import type { Color32 } from '../Color/_color-types'
2
+
3
+ import { colorDistance } from '../Color/colorDistance'
3
4
  import { extractImageDataBuffer } from '../ImageData/extractImageDataBuffer'
4
5
  import { type BinaryMaskRect, MaskType } from '../Mask/_mask-types'
5
6
  import type { PixelData } from '../PixelData/_pixelData-types'
@@ -1,4 +1,5 @@
1
- import type { BlendColor32, Color32 } from '../_types'
1
+ import type { BlendColor32 } from '../_types'
2
+ import type { Color32 } from '../Color/_color-types'
2
3
  import { BaseBlendMode, overwriteBase } from './blend-modes'
3
4
  import { makeBlendModeRegistry } from './BlendModeRegistry'
4
5
 
@@ -1,4 +1,5 @@
1
- import type { BlendColor32, Color32 } from '../_types'
1
+ import type { BlendColor32 } from '../_types'
2
+ import type { Color32 } from '../Color/_color-types'
2
3
  import { BaseBlendMode, overwriteBase } from './blend-modes'
3
4
  import { makeBlendModeRegistry } from './BlendModeRegistry'
4
5
 
@@ -63,11 +63,6 @@ function makeReusableCanvasMeta<T extends HTMLCanvasElement | OffscreenCanvas>(
63
63
  canvas.width = width
64
64
  canvas.height = height
65
65
  ctx!.imageSmoothingEnabled = false
66
- } else {
67
- // Always reset transform before clearing to ensure the whole buffer is wiped
68
- ctx!.setTransform(1, 0, 0, 1, 0, 0)
69
- // Same size → manually clear
70
- ctx!.clearRect(0, 0, width, height)
71
66
  }
72
67
 
73
68
  return result
@@ -0,0 +1,8 @@
1
+ /** Represents a 32-bit color in 0xAABBGGRR (Little endian) */
2
+ export type Color32 = number & { readonly __brandColor32: unique symbol }
3
+
4
+ /** ALL values are 0-255 */
5
+ export type RGBA = { r: number, g: number, b: number, a: number }
6
+
7
+ /** r, g, b are 0-255. a is 0-1 for css use */
8
+ export type CssRGBA = { r: number, g: number, b: number, a: number } & { readonly __brandCssRGBA: unique symbol }
@@ -0,0 +1,9 @@
1
+ import type { Color32 } from './_color-types'
2
+
3
+ export function colorDistance(a: Color32, b: Color32): number {
4
+ const dr = (a & 0xFF) - (b & 0xFF)
5
+ const dg = ((a >>> 8) & 0xFF) - ((b >>> 8) & 0xFF)
6
+ const db = ((a >>> 16) & 0xFF) - ((b >>> 16) & 0xFF)
7
+ const da = ((a >>> 24) & 0xFF) - ((b >>> 24) & 0xFF)
8
+ return dr * dr + dg * dg + db * db + da * da
9
+ }
@@ -0,0 +1,43 @@
1
+ // Convert 0xAABBGGRR to #RRGGBBAA
2
+ import type { Color32, CssRGBA } from './_color-types'
3
+
4
+ export function color32ToHex(color: Color32): string {
5
+ const r = (color & 0xFF).toString(16).padStart(2, '0')
6
+ const g = ((color >>> 8) & 0xFF).toString(16).padStart(2, '0')
7
+ const b = ((color >>> 16) & 0xFF).toString(16).padStart(2, '0')
8
+ const a = ((color >>> 24) & 0xFF).toString(16).padStart(2, '0')
9
+ return `#${r}${g}${b}${a}`
10
+ }
11
+
12
+ /**
13
+ * Converts a 32-bit integer (0xAABBGGRR) to a CSS rgba() string.
14
+ * Example: 0xFF0000FF -> "rgba(255,0,0,1)"
15
+ */
16
+ export function color32ToCssRGBAString(color: Color32): string {
17
+ const r = color & 0xFF
18
+ const g = (color >>> 8) & 0xFF
19
+ const b = (color >>> 16) & 0xFF
20
+ const a = (color >>> 24) & 0xFF
21
+
22
+ const alpha = Number((a / 255).toFixed(3))
23
+
24
+ return `rgba(${r},${g},${b},${alpha})`
25
+ }
26
+
27
+ export function color32ToCssRGBA(color: Color32): CssRGBA {
28
+ const r = color & 0xFF
29
+ const g = (color >>> 8) & 0xFF
30
+ const b = (color >>> 16) & 0xFF
31
+ const a = (color >>> 24) & 0xFF
32
+
33
+ return {
34
+ r,
35
+ g,
36
+ b,
37
+ a: a / 255,
38
+ } as CssRGBA
39
+ }
40
+
41
+ export function cssRGBAToColor32({ r, g, b, a }: CssRGBA): Color32 {
42
+ return (((a * 255) << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
43
+ }
@@ -0,0 +1,44 @@
1
+ import type { Color32 } from './_color-types'
2
+
3
+ /**
4
+ * Linearly interpolates between two 32-bit colors using a floating-point weight.
5
+ * * This is the preferred method for UI animations or scenarios where high
6
+ * precision is required. It uses the standard `a + t * (b - a)` formula
7
+ * for each channel.
8
+ * @param a - The starting color as a 32-bit integer (AABBGGRR).
9
+ * @param b - The target color as a 32-bit integer (AABBGGRR).
10
+ * @param t - The interpolation factor between 0.0 and 1.0.
11
+ * @returns The interpolated 32-bit color.
12
+ */
13
+ export function lerpColor32(a: Color32, b: Color32, t: number): Color32 {
14
+ const r = (a & 0xFF) + t * ((b & 0xFF) - (a & 0xFF))
15
+ const g = ((a >>> 8) & 0xFF) + t * (((b >>> 8) & 0xFF) - ((a >>> 8) & 0xFF))
16
+ const b_ = ((a >>> 16) & 0xFF) + t * (((b >>> 16) & 0xFF) - ((a >>> 16) & 0xFF))
17
+ const a_ = ((a >>> 24) & 0xFF) + t * (((b >>> 24) & 0xFF) - ((a >>> 24) & 0xFF))
18
+
19
+ return ((a_ << 24) | (b_ << 16) | (g << 8) | r) >>> 0 as Color32
20
+ }
21
+
22
+ /**
23
+ * Linearly interpolates between two 32-bit colors using integer fixed-point math.
24
+ * Highly optimized for image processing and real-time blitting. It processes
25
+ * channels in parallel using bitmasks (RB and GA pairs).
26
+ * **Note:** Subject to a 1-bit drift (rounding down) due to fast bit-shift division.
27
+ * @param src - The source (foreground) color as a 32-bit integer.
28
+ * @param dst - The destination (background) color as a 32-bit integer.
29
+ * @param w - The blend weight as a byte value from 0 to 255. Where 0 is 100% dst and 255 is 100% src
30
+ * @returns The blended 32-bit color.
31
+ */
32
+ export function lerpColor32Fast(src: Color32, dst: Color32, w: number): Color32 {
33
+ const invA = 255 - w
34
+
35
+ // Masking Red and Blue: 0x00FF00FF
36
+ // We process R and B in one go, then shift back down
37
+ const rb = (((src & 0x00FF00FF) * w + (dst & 0x00FF00FF) * invA) >>> 8) & 0x00FF00FF
38
+
39
+ // Masking Green and Alpha: 0xFF00FF00
40
+ // We shift down first to avoid overflow, then shift back up
41
+ const ga = ((((src >>> 8) & 0x00FF00FF) * w + ((dst >>> 8) & 0x00FF00FF) * invA) >>> 8) & 0x00FF00FF
42
+
43
+ return (rb | (ga << 8)) >>> 0 as Color32
44
+ }
@@ -0,0 +1,38 @@
1
+ import type { Color32, RGBA } from './_color-types'
2
+
3
+ /**
4
+ * Packs RGBA into a 32-bit integer compatible with
5
+ * Little-Endian Uint32Array views on ImageData.
6
+ */
7
+ export function packColor(r: number, g: number, b: number, a: number): Color32 {
8
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
9
+ }
10
+
11
+ export function packRGBA({ r, g, b, a }: RGBA): Color32 {
12
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
13
+ }
14
+
15
+ export const unpackRed = (packed: Color32): number => (packed >>> 0) & 0xFF
16
+ export const unpackGreen = (packed: Color32): number => (packed >>> 8) & 0xFF
17
+ export const unpackBlue = (packed: Color32): number => (packed >>> 16) & 0xFF
18
+ export const unpackAlpha = (packed: Color32): number => (packed >>> 24) & 0xFF
19
+
20
+ export function unpackColor(packed: Color32): RGBA {
21
+ return {
22
+ r: (packed >>> 0) & 0xFF,
23
+ g: (packed >>> 8) & 0xFF,
24
+ b: (packed >>> 16) & 0xFF,
25
+ a: (packed >>> 24) & 0xFF,
26
+ }
27
+ }
28
+
29
+ const SCRATCH_RGBA: RGBA = { r: 0, g: 0, b: 0, a: 0 }
30
+
31
+ // uses a scratch arg for memory perf. be careful about re-use.
32
+ export function unpackColorTo(packed: Color32, scratch = SCRATCH_RGBA): RGBA {
33
+ scratch.r = (packed >>> 0) & 0xFF
34
+ scratch.g = (packed >>> 8) & 0xFF
35
+ scratch.b = (packed >>> 16) & 0xFF
36
+ scratch.a = (packed >>> 24) & 0xFF
37
+ return scratch
38
+ }
@@ -1,5 +1,5 @@
1
+ import type { TileTargetConfig } from '../Tile/_tile-types'
1
2
  import type { PixelAccumulator } from './PixelAccumulator'
2
- import type { PixelEngineConfig } from './PixelEngineConfig'
3
3
  import { applyPatchTiles, type PixelPatchTiles } from './PixelPatchTiles'
4
4
 
5
5
  export interface HistoryAction {
@@ -11,7 +11,7 @@ export interface HistoryAction {
11
11
  export type HistoryActionFactory = typeof makeHistoryAction
12
12
 
13
13
  export function makeHistoryAction(
14
- config: PixelEngineConfig,
14
+ config: TileTargetConfig,
15
15
  accumulator: PixelAccumulator,
16
16
  patch: PixelPatchTiles,
17
17
  afterUndo?: (patch: PixelPatchTiles) => void,
@@ -1,6 +1,5 @@
1
- import type { PixelTile } from '../Tile/_tile-types'
1
+ import type { PixelTile, TileTargetConfig } from '../Tile/_tile-types'
2
2
  import type { TilePool } from '../Tile/TilePool'
3
- import type { PixelEngineConfig } from './PixelEngineConfig'
4
3
  import { applyPatchTiles, type PixelPatchTiles } from './PixelPatchTiles'
5
4
 
6
5
  export type DidChangeFn = (didChange: boolean) => boolean
@@ -10,7 +9,7 @@ export class PixelAccumulator {
10
9
  public beforeTiles: PixelTile[]
11
10
 
12
11
  constructor(
13
- readonly config: PixelEngineConfig,
12
+ readonly config: TileTargetConfig,
14
13
  readonly pixelTilePool: TilePool<PixelTile>,
15
14
  ) {
16
15
  this.lookup = []
@@ -27,18 +26,17 @@ export class PixelAccumulator {
27
26
  * @param y pixel y coordinate
28
27
  */
29
28
  storePixelBeforeState(x: number, y: number): DidChangeFn | null {
30
- const shift = this.config.tileShift
31
29
  const columns = this.config.targetColumns
32
- const targetWidth = this.config.target.w
33
- const targetHeight = this.config.target.h
30
+ const targetWidth = this.config.targetWidth
31
+ const targetHeight = this.config.targetHeight
34
32
 
35
33
  // Return a no-op if the pixel is outside the target boundaries
36
34
  if (x < 0 || x >= targetWidth || y < 0 || y >= targetHeight) {
37
35
  return null
38
36
  }
39
37
 
40
- const tx = x >> shift
41
- const ty = y >> shift
38
+ const tx = (x * this.config.invTileSize) | 0
39
+ const ty = (y * this.config.invTileSize) | 0
42
40
  const id = ty * columns + tx
43
41
 
44
42
  let tile = this.lookup[id]
@@ -75,10 +73,10 @@ export class PixelAccumulator {
75
73
  w: number,
76
74
  h: number,
77
75
  ): DidChangeFn | null {
78
- const shift = this.config.tileShift
79
76
  const columns = this.config.targetColumns
80
- const targetWidth = this.config.target.w
81
- const targetHeight = this.config.target.h
77
+ const targetWidth = this.config.targetWidth
78
+ const targetHeight = this.config.targetHeight
79
+ const invTileSize = this.config.invTileSize
82
80
 
83
81
  // Clamp the bounding box to the actual canvas dimensions
84
82
  const clipX1 = Math.max(0, x)
@@ -91,10 +89,10 @@ export class PixelAccumulator {
91
89
  return null
92
90
  }
93
91
 
94
- const startX = clipX1 >> shift
95
- const startY = clipY1 >> shift
96
- const endX = clipX2 >> shift
97
- const endY = clipY2 >> shift
92
+ const startX = (clipX1 * invTileSize) | 0
93
+ const startY = (clipY1 * invTileSize) | 0
94
+ const endX = (clipX2 * invTileSize) | 0
95
+ const endY = (clipY2 * invTileSize) | 0
98
96
 
99
97
  const startIndex = this.beforeTiles.length
100
98
 
@@ -1,4 +1,5 @@
1
- import type { Color32, ColorBlendOptions } from '../../_types'
1
+ import type { ColorBlendOptions } from '../../_types'
2
+ import type { Color32 } from '../../Color/_color-types'
2
3
  import { blendColorPixelData } from '../../PixelData/blendColorPixelData'
3
4
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
4
5
 
@@ -1,5 +1,5 @@
1
- import { type Color32 } from '../../_types'
2
1
  import { sourceOverPerfect } from '../../BlendModes/blend-modes-perfect'
2
+ import type { Color32 } from '../../Color/_color-types'
3
3
  import type { PaintAlphaMask } from '../../Paint/_paint-types'
4
4
  import { blendColorPixelDataAlphaMask } from '../../PixelData/blendColorPixelDataAlphaMask'
5
5
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
@@ -1,5 +1,5 @@
1
- import { type Color32 } from '../../_types'
2
1
  import { sourceOverPerfect } from '../../BlendModes/blend-modes-perfect'
2
+ import type { Color32 } from '../../Color/_color-types'
3
3
  import type { PaintBinaryMask } from '../../Paint/_paint-types'
4
4
  import { blendColorPixelDataBinaryMask } from '../../PixelData/blendColorPixelDataBinaryMask'
5
5
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
@@ -1,7 +1,8 @@
1
- import { type Color32 } from '../../_types'
2
1
  import { sourceOverPerfect } from '../../BlendModes/blend-modes-perfect'
2
+ import type { Color32 } from '../../Color/_color-types'
3
3
  import { MaskType } from '../../Mask/_mask-types'
4
- import type { PaintMask } from '../../Paint/_paint-types'
4
+ import type { PaintMask, PaintRect } from '../../Paint/_paint-types'
5
+ import { blendColorPixelData } from '../../PixelData/blendColorPixelData'
5
6
  import { blendColorPixelDataAlphaMask } from '../../PixelData/blendColorPixelDataAlphaMask'
6
7
  import { blendColorPixelDataBinaryMask } from '../../PixelData/blendColorPixelDataBinaryMask'
7
8
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
@@ -9,6 +10,7 @@ import { type HistoryMutator, PixelWriter } from '../PixelWriter'
9
10
  const defaults = {
10
11
  blendColorPixelDataAlphaMask,
11
12
  blendColorPixelDataBinaryMask,
13
+ blendColorPixelData,
12
14
  }
13
15
  type Deps = Partial<typeof defaults>
14
16
 
@@ -19,6 +21,7 @@ export const mutatorBlendColorPaintMask = ((writer: PixelWriter<any>, deps: Part
19
21
  const {
20
22
  blendColorPixelDataBinaryMask = defaults.blendColorPixelDataBinaryMask,
21
23
  blendColorPixelDataAlphaMask = defaults.blendColorPixelDataAlphaMask,
24
+ blendColorPixelData = defaults.blendColorPixelData,
22
25
  } = deps
23
26
 
24
27
  const OPTS = {
@@ -26,12 +29,14 @@ export const mutatorBlendColorPaintMask = ((writer: PixelWriter<any>, deps: Part
26
29
  y: 0,
27
30
  blendFn: sourceOverPerfect,
28
31
  alpha: 255,
32
+ w: undefined as number | undefined,
33
+ h: undefined as number | undefined,
29
34
  }
30
35
 
31
36
  return {
32
37
  blendColorPaintMask(
33
38
  color: Color32,
34
- mask: PaintMask,
39
+ mask: PaintMask | PaintRect,
35
40
  x: number,
36
41
  y: number,
37
42
  alpha = 255,
@@ -47,16 +52,26 @@ export const mutatorBlendColorPaintMask = ((writer: PixelWriter<any>, deps: Part
47
52
  OPTS.y = ty
48
53
  OPTS.alpha = alpha
49
54
  OPTS.blendFn = blendFn
55
+ OPTS.w = undefined
56
+ OPTS.h = undefined
50
57
 
51
- if (mask.type === MaskType.BINARY) {
52
- return didChange(
53
- blendColorPixelDataBinaryMask(writer.config.target, color, mask, OPTS),
54
- )
55
- } else {
58
+ if (mask.data) {
59
+ if (mask.type === MaskType.BINARY) {
60
+ return didChange(
61
+ blendColorPixelDataBinaryMask(writer.config.target, color, mask, OPTS),
62
+ )
63
+ }
56
64
  return didChange(
57
65
  blendColorPixelDataAlphaMask(writer.config.target, color, mask, OPTS),
58
66
  )
59
67
  }
68
+
69
+ OPTS.w = mask.w
70
+ OPTS.h = mask.h
71
+
72
+ return didChange(
73
+ blendColorPixelData(writer.config.target, color, OPTS),
74
+ )
60
75
  },
61
76
  }
62
77
  }) satisfies HistoryMutator<any, Deps>
@@ -1,5 +1,6 @@
1
- import type { BlendColor32, Color32 } from '../../_types'
1
+ import type { BlendColor32 } from '../../_types'
2
2
  import { sourceOverPerfect } from '../../BlendModes/blend-modes-perfect'
3
+ import type { Color32 } from '../../Color/_color-types'
3
4
  import { _macro_paintRectCenterOffset } from '../../Internal/macros'
4
5
  import { blendColorPixelData } from '../../PixelData/blendColorPixelData'
5
6
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
@@ -1,4 +1,5 @@
1
- import type { BlendColor32, Color32 } from '../../_types'
1
+ import type { BlendColor32 } from '../../_types'
2
+ import type { Color32 } from '../../Color/_color-types'
2
3
  import { blendPixel } from '../../PixelData/blendPixel'
3
4
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
4
5
 
@@ -1,4 +1,4 @@
1
- import type { Color32 } from '../../_types'
1
+ import type { Color32 } from '../../Color/_color-types'
2
2
  import { fillPixelData } from '../../PixelData/fillPixelData'
3
3
  import type { Rect } from '../../Rect/_rect-types'
4
4
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
@@ -1,4 +1,4 @@
1
- import type { Color32 } from '../../_types'
1
+ import type { Color32 } from '../../Color/_color-types'
2
2
  import { fillPixelData } from '../../PixelData/fillPixelData'
3
3
  import type { Rect } from '../../Rect/_rect-types'
4
4
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
@@ -1,4 +1,4 @@
1
- import type { Color32 } from '../../_types'
1
+ import type { Color32 } from '../../Color/_color-types'
2
2
  import type { BinaryMask } from '../../Mask/_mask-types'
3
3
  import { fillPixelDataBinaryMask } from '../../PixelData/fillPixelDataBinaryMask'
4
4
  import { type HistoryMutator, PixelWriter } from '../PixelWriter'
@@ -1,13 +1,13 @@
1
1
  import { resizeImageData } from '../ImageData/resizeImageData'
2
2
  import type { PixelData } from '../PixelData/_pixelData-types'
3
3
  import { setPixelData } from '../PixelData/PixelData'
4
- import type { PixelTile } from '../Tile/_tile-types'
4
+ import type { PixelTile, TileTargetConfig } from '../Tile/_tile-types'
5
5
  import { makePixelTile } from '../Tile/PixelTile'
6
6
  import { TilePool } from '../Tile/TilePool'
7
+ import { makeTileTargetConfig } from '../Tile/TileTargetConfig'
7
8
  import { type HistoryActionFactory, makeHistoryAction } from './HistoryAction'
8
9
  import { HistoryManager } from './HistoryManager'
9
10
  import { PixelAccumulator } from './PixelAccumulator'
10
- import { PixelEngineConfig } from './PixelEngineConfig'
11
11
  import type { PixelPatchTiles } from './PixelPatchTiles'
12
12
 
13
13
  export interface PixelWriterOptions {
@@ -44,7 +44,7 @@ export class PixelWriter<M> {
44
44
  readonly historyManager: HistoryManager
45
45
  readonly accumulator: PixelAccumulator
46
46
  readonly historyActionFactory: HistoryActionFactory
47
- readonly config: PixelEngineConfig
47
+ readonly config: TileTargetConfig
48
48
  readonly pixelTilePool: TilePool<PixelTile>
49
49
  readonly mutator: M
50
50
 
@@ -54,10 +54,10 @@ export class PixelWriter<M> {
54
54
  const tileSize = options?.tileSize ?? 256
55
55
  const maxHistorySteps = options?.maxHistorySteps ?? 50
56
56
 
57
- this.config = new PixelEngineConfig(tileSize, target)
57
+ this.config = makeTileTargetConfig(tileSize, target)
58
58
  this.historyManager = options?.historyManager ?? new HistoryManager(maxHistorySteps)
59
59
  this.historyActionFactory = options?.historyActionFactory ?? makeHistoryAction
60
- this.pixelTilePool = options?.pixelTilePool ?? new TilePool(this.config, makePixelTile)
60
+ this.pixelTilePool = options?.pixelTilePool ?? new TilePool(this.config.tileSize, makePixelTile)
61
61
  this.accumulator = options?.accumulator ?? new PixelAccumulator(this.config, this.pixelTilePool)
62
62
  this.mutator = mutatorFactory(this)
63
63
  }
@@ -2,8 +2,11 @@ import type { Base64EncodedUInt8Array, ImageDataLike, SerializedImageData } from
2
2
 
3
3
  export function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array {
4
4
  const uint8 = new Uint8Array(buffer)
5
- const decoder = new TextDecoder('latin1')
6
- const binary = decoder.decode(uint8)
5
+ let binary = ''
6
+
7
+ for (let i = 0; i < uint8.length; i++) {
8
+ binary += String.fromCharCode(uint8[i])
9
+ }
7
10
 
8
11
  return btoa(binary) as Base64EncodedUInt8Array
9
12
  }
@@ -11,9 +14,11 @@ export function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedU
11
14
  export function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray<ArrayBuffer> {
12
15
  const binary = atob(encoded)
13
16
  const bytes = new Uint8ClampedArray(binary.length)
17
+
14
18
  for (let i = 0; i < binary.length; i++) {
15
19
  bytes[i] = binary.charCodeAt(i)
16
20
  }
21
+
17
22
  return bytes
18
23
  }
19
24
 
@@ -1,4 +1,4 @@
1
- import type { Color32 } from '../_types'
1
+ import type { Color32 } from '../Color/_color-types'
2
2
  import type { IndexedImage } from './_indexedImage-types'
3
3
 
4
4
  export function makeIndexedImage(
@@ -1,5 +1,6 @@
1
- import type { Color32 } from '../_types'
2
- import { packColor } from '../color'
1
+ import type { Color32 } from '../Color/_color-types'
2
+
3
+ import { packColor } from '../Color/pack-color'
3
4
  import type { IndexedImage } from './_indexedImage-types'
4
5
 
5
6
  /**
@@ -64,6 +64,15 @@ export type NullableBinaryMaskRect = Rect & ({
64
64
  type?: null
65
65
  data?: null
66
66
  })
67
+
68
+ export type NullableAlphaMaskRect = Rect & ({
69
+ type: MaskType.ALPHA
70
+ data: Uint8Array
71
+ } | {
72
+ type?: null
73
+ data?: null
74
+ })
75
+
67
76
  export type NullableMaskRect = Rect & ({
68
77
  type: MaskType
69
78
  data: Uint8Array
@@ -1,9 +1,9 @@
1
1
  import { forEachLinePoint } from '../Algorithm/forEachLinePoint'
2
- import type { PixelEngineConfig } from '../History/PixelEngineConfig'
3
2
  import type { Rect } from '../Rect/_rect-types'
4
3
  import { trimRectBounds } from '../Rect/trimRectBounds'
5
- import type { AlphaMaskTile } from '../Tile/_tile-types'
6
- import type { TilePool } from '../Tile/TilePool'
4
+ import type { AlphaMaskTile, TileTargetMeta } from '../Tile/_tile-types'
5
+ import { makeAlphaMaskTile } from '../Tile/MaskTile'
6
+ import { TilePool } from '../Tile/TilePool'
7
7
  import type { PaintAlphaMask, PaintBinaryMask, PaintRect } from './_paint-types'
8
8
  import { eachTileInBounds } from './eachTileInBounds'
9
9
 
@@ -16,8 +16,8 @@ export class AlphaMaskPaintBuffer {
16
16
  private eachTileInBoundsFn = eachTileInBounds
17
17
 
18
18
  constructor(
19
- readonly config: PixelEngineConfig,
20
- readonly tilePool: TilePool<AlphaMaskTile>,
19
+ readonly config: TileTargetMeta,
20
+ readonly tilePool: TilePool<AlphaMaskTile> = new TilePool(config.tileSize, makeAlphaMaskTile),
21
21
  ) {
22
22
  this.lookup = []
23
23
  }
@@ -46,9 +46,9 @@ export class AlphaMaskPaintBuffer {
46
46
  const lookup = this.lookup
47
47
  const tilePool = this.tilePool
48
48
  const config = this.config
49
- const tileShift = config.tileShift
50
- const tileMask = config.tileMask
51
- const target = config.target
49
+ const targetW = config.targetWidth
50
+ const targetH = config.targetHeight
51
+ const tileSize = config.tileSize
52
52
 
53
53
  const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
54
54
 
@@ -66,8 +66,8 @@ export class AlphaMaskPaintBuffer {
66
66
  topLeftY,
67
67
  bW,
68
68
  bH,
69
- target.w,
70
- target.h,
69
+ targetW,
70
+ targetH,
71
71
  scratch,
72
72
  )
73
73
 
@@ -80,8 +80,8 @@ export class AlphaMaskPaintBuffer {
80
80
  for (let i = 0; i < bH_t; i++) {
81
81
  const canvasY = bY + i
82
82
  const bOff = (canvasY - topLeftY) * bW
83
- const tOff = (canvasY & tileMask) << tileShift
84
- const dS = tOff + (bX & tileMask)
83
+ const tOff = (canvasY - tile.y) * tileSize
84
+ const dS = tOff + (bX - tile.x)
85
85
 
86
86
  for (let j = 0; j < bW_t; j++) {
87
87
  const canvasX = bX + j
@@ -130,9 +130,9 @@ export class AlphaMaskPaintBuffer {
130
130
  const lookup = this.lookup
131
131
  const tilePool = this.tilePool
132
132
  const config = this.config
133
- const tileShift = config.tileShift
134
- const tileMask = config.tileMask
135
- const target = config.target
133
+ const targetW = config.targetWidth
134
+ const targetH = config.targetHeight
135
+ const tileSize = config.tileSize
136
136
 
137
137
  const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
138
138
  let changed = false
@@ -149,8 +149,8 @@ export class AlphaMaskPaintBuffer {
149
149
  topLeftY,
150
150
  bW,
151
151
  bH,
152
- target.w,
153
- target.h,
152
+ targetW,
153
+ targetH,
154
154
  scratch,
155
155
  )
156
156
 
@@ -163,8 +163,8 @@ export class AlphaMaskPaintBuffer {
163
163
  for (let i = 0; i < bH_t; i++) {
164
164
  const canvasY = bY + i
165
165
  const bOff = (canvasY - topLeftY) * bW
166
- const tOff = (canvasY & tileMask) << tileShift
167
- const dS = tOff + (bX & tileMask)
166
+ const tOff = (canvasY - tile.y) * tileSize
167
+ const dS = tOff + (bX - tile.x)
168
168
 
169
169
  for (let j = 0; j < bW_t; j++) {
170
170
  const canvasX = bX + j
@@ -210,9 +210,9 @@ export class AlphaMaskPaintBuffer {
210
210
  const lookup = this.lookup
211
211
  const tilePool = this.tilePool
212
212
  const config = this.config
213
- const tileShift = config.tileShift
214
- const tileMask = config.tileMask
215
- const target = config.target
213
+ const targetW = config.targetWidth
214
+ const targetH = config.targetHeight
215
+ const tileSize = config.tileSize
216
216
 
217
217
  const brushWidth = brush.w
218
218
  const brushHeight = brush.h
@@ -237,8 +237,8 @@ export class AlphaMaskPaintBuffer {
237
237
  topLeftY,
238
238
  brushWidth,
239
239
  brushHeight,
240
- target.w,
241
- target.h,
240
+ targetW,
241
+ targetH,
242
242
  scratch,
243
243
  )
244
244
 
@@ -250,8 +250,8 @@ export class AlphaMaskPaintBuffer {
250
250
 
251
251
  for (let i = 0; i < bH_t; i++) {
252
252
  const canvasY = bY + i
253
- const tOff = (canvasY & tileMask) << tileShift
254
- const dS = tOff + (bX & tileMask)
253
+ const tOff = (canvasY - tile.y) * tileSize
254
+ const dS = tOff + (bX - tile.x)
255
255
 
256
256
  for (let j = 0; j < bW_t; j++) {
257
257
  const idx = dS + j