pixel-data-js 0.24.0 → 0.25.2
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/dist/index.dev.cjs +1476 -1834
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +1465 -1816
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +1475 -1833
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +233 -310
- package/dist/index.prod.js +1465 -1816
- package/dist/index.prod.js.map +1 -1
- package/package.json +1 -1
- package/src/Algorithm/floodFillSelection.ts +2 -2
- package/src/Canvas/canvas-blend-modes.ts +28 -0
- package/src/History/PixelAccumulator.ts +52 -29
- package/src/History/PixelEngineConfig.ts +7 -9
- package/src/History/PixelMutator/mutatorBlendPaintMask.ts +60 -0
- package/src/History/PixelMutator/mutatorBlendPixelData.ts +2 -2
- package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +2 -2
- package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +2 -2
- package/src/History/PixelMutator.ts +0 -20
- package/src/History/PixelPatchTiles.ts +2 -2
- package/src/History/PixelWriter.ts +132 -9
- package/src/Internal/helpers.ts +2 -0
- package/src/Paint/PaintBuffer.ts +269 -0
- package/src/{PixelTile/PaintBufferRenderer.ts → Paint/PaintBufferCanvasRenderer.ts} +13 -5
- package/src/Paint/makeCirclePaintAlphaMask.ts +41 -0
- package/src/{Mask/CircleBinaryMask.ts → Paint/makeCirclePaintBinaryMask.ts} +5 -6
- package/src/Paint/makePaintMask.ts +28 -0
- package/src/Paint/makeRectFalloffPaintAlphaMask.ts +47 -0
- package/src/PixelData/PixelBuffer32.ts +2 -2
- package/src/PixelData/PixelData.ts +1 -1
- package/src/PixelData/applyAlphaMaskToPixelData.ts +2 -2
- package/src/PixelData/applyBinaryMaskToPixelData.ts +2 -2
- package/src/PixelData/blendColorPixelData.ts +2 -2
- package/src/PixelData/blendColorPixelDataAlphaMask.ts +3 -3
- package/src/PixelData/blendColorPixelDataBinaryMask.ts +3 -3
- package/src/PixelData/blendPixel.ts +2 -2
- package/src/PixelData/blendPixelData.ts +3 -3
- package/src/PixelData/blendPixelDataAlphaMask.ts +3 -3
- package/src/PixelData/blendPixelDataBinaryMask.ts +3 -3
- package/src/PixelData/blendPixelDataPaintBuffer.ts +3 -3
- package/src/PixelData/clearPixelData.ts +2 -2
- package/src/PixelData/extractPixelData.ts +4 -4
- package/src/PixelData/extractPixelDataBuffer.ts +4 -4
- package/src/PixelData/fillPixelData.ts +5 -5
- package/src/PixelData/fillPixelDataBinaryMask.ts +3 -3
- package/src/PixelData/fillPixelDataFast.ts +5 -5
- package/src/PixelData/invertPixelData.ts +2 -2
- package/src/PixelData/pixelDataToAlphaMask.ts +2 -2
- package/src/PixelData/reflectPixelData.ts +3 -3
- package/src/PixelData/resamplePixelData.ts +2 -2
- package/src/PixelData/writePaintBufferToPixelData.ts +26 -0
- package/src/PixelData/writePixelDataBuffer.ts +5 -5
- package/src/Rect/trimMaskRectBounds.ts +121 -0
- package/src/Rect/trimRectBounds.ts +25 -116
- package/src/_types.ts +16 -15
- package/src/index.ts +11 -24
- package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +0 -182
- package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +0 -59
- package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +0 -172
- package/src/History/PixelMutator/mutatorApplyRectBrush.ts +0 -64
- package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +0 -184
- package/src/History/PixelMutator/mutatorApplyRectPencil.ts +0 -65
- package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +0 -166
- package/src/History/PixelMutator/mutatorBlendColorCircleMask.ts +0 -71
- package/src/Mask/CircleAlphaMask.ts +0 -32
- package/src/PixelData/applyRectBrushToPixelData.ts +0 -98
- package/src/PixelData/blendColorPixelDataCircleMask.ts +0 -92
- package/src/PixelTile/PaintBuffer.ts +0 -122
- package/src/Rect/getCircleBrushOrPencilBounds.ts +0 -43
- package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +0 -24
- package/src/Rect/getRectBrushOrPencilBounds.ts +0 -38
- package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +0 -26
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import { type BinaryMaskRect, type Color32, type ImageDataLike, MaskType, type R
|
|
|
2
2
|
import { colorDistance } from '../color'
|
|
3
3
|
import { extractImageDataBuffer } from '../ImageData/extractImageDataBuffer'
|
|
4
4
|
import type { PixelData } from '../PixelData/PixelData'
|
|
5
|
-
import {
|
|
5
|
+
import { trimMaskRectBounds } from '../Rect/trimMaskRectBounds'
|
|
6
6
|
|
|
7
7
|
export type FloodFillImageDataOptions = {
|
|
8
8
|
contiguous?: boolean
|
|
@@ -205,7 +205,7 @@ export function floodFillSelection(
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
trimMaskRectBounds(
|
|
209
209
|
selectionRect,
|
|
210
210
|
{ x: 0, y: 0, w: width, h: height },
|
|
211
211
|
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BaseBlendMode } from '../BlendModes/blend-modes'
|
|
2
|
+
|
|
3
|
+
export const CANVAS_COMPOSITE_MAP = {
|
|
4
|
+
[BaseBlendMode.overwrite]: 'copy',
|
|
5
|
+
[BaseBlendMode.sourceOver]: 'source-over',
|
|
6
|
+
[BaseBlendMode.darken]: 'darken',
|
|
7
|
+
[BaseBlendMode.multiply]: 'multiply',
|
|
8
|
+
[BaseBlendMode.colorBurn]: 'color-burn',
|
|
9
|
+
[BaseBlendMode.lighten]: 'lighten',
|
|
10
|
+
[BaseBlendMode.screen]: 'screen',
|
|
11
|
+
[BaseBlendMode.colorDodge]: 'color-dodge',
|
|
12
|
+
[BaseBlendMode.linearDodge]: 'lighter',
|
|
13
|
+
[BaseBlendMode.overlay]: 'overlay',
|
|
14
|
+
[BaseBlendMode.softLight]: 'soft-light',
|
|
15
|
+
[BaseBlendMode.hardLight]: 'hard-light',
|
|
16
|
+
[BaseBlendMode.difference]: 'difference',
|
|
17
|
+
[BaseBlendMode.exclusion]: 'exclusion',
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
export type CanvasBlendModeIndex = keyof typeof CANVAS_COMPOSITE_MAP
|
|
21
|
+
export type CanvasCompositeOperation = typeof CANVAS_COMPOSITE_MAP[CanvasBlendModeIndex]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* example
|
|
25
|
+
* function getCanvasCompositeOperation(mode: CanvasBlendModeIndex): CanvasCompositeOperation {
|
|
26
|
+
* return CANVAS_COMPOSITE_MAP[mode]
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
@@ -27,11 +27,11 @@ export class PixelAccumulator {
|
|
|
27
27
|
* @param y pixel y coordinate
|
|
28
28
|
*/
|
|
29
29
|
storePixelBeforeState(x: number, y: number): DidChangeFn {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const shift = this.config.tileShift
|
|
31
|
+
const columns = this.config.targetColumns
|
|
32
|
+
const tx = x >> shift
|
|
33
|
+
const ty = y >> shift
|
|
34
|
+
const id = ty * columns + tx
|
|
35
35
|
|
|
36
36
|
let tile = this.lookup[id]
|
|
37
37
|
let added = false
|
|
@@ -67,19 +67,19 @@ export class PixelAccumulator {
|
|
|
67
67
|
w: number,
|
|
68
68
|
h: number,
|
|
69
69
|
): DidChangeFn {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const shift = this.config.tileShift
|
|
71
|
+
const columns = this.config.targetColumns
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
const startX = x >> shift
|
|
74
|
+
const startY = y >> shift
|
|
75
|
+
const endX = (x + w - 1) >> shift
|
|
76
|
+
const endY = (y + h - 1) >> shift
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
const startIndex = this.beforeTiles.length
|
|
79
79
|
|
|
80
80
|
for (let ty = startY; ty <= endY; ty++) {
|
|
81
81
|
for (let tx = startX; tx <= endX; tx++) {
|
|
82
|
-
|
|
82
|
+
const id = ty * columns + tx
|
|
83
83
|
let tile = this.lookup[id]
|
|
84
84
|
|
|
85
85
|
if (!tile) {
|
|
@@ -94,7 +94,7 @@ export class PixelAccumulator {
|
|
|
94
94
|
|
|
95
95
|
return (didChange: boolean) => {
|
|
96
96
|
if (!didChange) {
|
|
97
|
-
|
|
97
|
+
const length = this.beforeTiles.length
|
|
98
98
|
|
|
99
99
|
for (let i = startIndex; i < length; i++) {
|
|
100
100
|
let t = this.beforeTiles[i]
|
|
@@ -111,15 +111,38 @@ export class PixelAccumulator {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
storeTileBeforeState(id: number, tx: number, ty: number): DidChangeFn {
|
|
115
|
+
let tile = this.lookup[id]
|
|
116
|
+
let added = false
|
|
117
|
+
|
|
118
|
+
if (!tile) {
|
|
119
|
+
tile = this.tilePool.getTile(id, tx, ty)
|
|
120
|
+
|
|
121
|
+
this.extractState(tile)
|
|
122
|
+
this.lookup[id] = tile
|
|
123
|
+
this.beforeTiles.push(tile)
|
|
124
|
+
added = true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (didChange: boolean) => {
|
|
128
|
+
if (!didChange && added) {
|
|
129
|
+
this.beforeTiles.pop()
|
|
130
|
+
this.lookup[id] = undefined
|
|
131
|
+
this.tilePool.releaseTile(tile!)
|
|
132
|
+
}
|
|
133
|
+
return didChange
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
114
137
|
extractState(tile: PixelTile) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
const target = this.config.target
|
|
139
|
+
const TILE_SIZE = this.config.tileSize
|
|
140
|
+
const dst = tile.data32
|
|
141
|
+
const src = target.data32
|
|
142
|
+
const startX = tile.tx * TILE_SIZE
|
|
143
|
+
const startY = tile.ty * TILE_SIZE
|
|
144
|
+
const targetWidth = target.width
|
|
145
|
+
const targetHeight = target.height
|
|
123
146
|
|
|
124
147
|
// If the tile is completely outside the canvas, zero it out.
|
|
125
148
|
if (startX >= targetWidth || startX + TILE_SIZE <= 0 || startY >= targetHeight || startY + TILE_SIZE <= 0) {
|
|
@@ -160,8 +183,8 @@ export class PixelAccumulator {
|
|
|
160
183
|
}
|
|
161
184
|
|
|
162
185
|
extractPatch(): PixelPatchTiles {
|
|
163
|
-
|
|
164
|
-
|
|
186
|
+
const afterTiles: PixelTile[] = []
|
|
187
|
+
const length = this.beforeTiles.length
|
|
165
188
|
|
|
166
189
|
for (let i = 0; i < length; i++) {
|
|
167
190
|
let beforeTile = this.beforeTiles[i]
|
|
@@ -174,7 +197,7 @@ export class PixelAccumulator {
|
|
|
174
197
|
}
|
|
175
198
|
}
|
|
176
199
|
|
|
177
|
-
|
|
200
|
+
const beforeTiles = this.beforeTiles
|
|
178
201
|
this.beforeTiles = []
|
|
179
202
|
this.lookup.length = 0
|
|
180
203
|
|
|
@@ -184,10 +207,10 @@ export class PixelAccumulator {
|
|
|
184
207
|
}
|
|
185
208
|
}
|
|
186
209
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
210
|
+
rollbackAfterError() {
|
|
211
|
+
const target = this.config.target
|
|
212
|
+
const tileSize = this.config.tileSize
|
|
213
|
+
const length = this.beforeTiles.length
|
|
191
214
|
|
|
192
215
|
applyPatchTiles(target, this.beforeTiles, tileSize)
|
|
193
216
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PixelData } from '../PixelData/PixelData'
|
|
2
2
|
|
|
3
3
|
export class PixelEngineConfig {
|
|
4
4
|
readonly tileSize: number
|
|
@@ -7,10 +7,11 @@ export class PixelEngineConfig {
|
|
|
7
7
|
readonly tileShift: number
|
|
8
8
|
readonly tileMask: number
|
|
9
9
|
readonly tileArea: number
|
|
10
|
-
readonly target!:
|
|
10
|
+
readonly target!: PixelData
|
|
11
11
|
readonly targetColumns: number = 0
|
|
12
|
+
readonly targetRows: number = 0
|
|
12
13
|
|
|
13
|
-
constructor(tileSize: number, target:
|
|
14
|
+
constructor(tileSize: number, target: PixelData) {
|
|
14
15
|
// Ensure it's a power of 2 to guarantee bitwise math works
|
|
15
16
|
if ((tileSize & (tileSize - 1)) !== 0) {
|
|
16
17
|
throw new Error('tileSize must be a power of 2')
|
|
@@ -20,11 +21,8 @@ export class PixelEngineConfig {
|
|
|
20
21
|
this.tileShift = 31 - Math.clz32(tileSize)
|
|
21
22
|
this.tileMask = tileSize - 1
|
|
22
23
|
this.tileArea = tileSize * tileSize
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
setTarget(target: IPixelData) {
|
|
27
|
-
;(this as any).target = target
|
|
28
|
-
;(this as any).targetColumns = (target.width + this.tileMask) >> this.tileShift
|
|
24
|
+
this.target = target
|
|
25
|
+
this.targetColumns = (target.width + this.tileMask) >> this.tileShift
|
|
26
|
+
this.targetRows = (target.height + this.tileMask) >> this.tileShift
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type Color32, type HistoryMutator, MaskType, type PaintMask } from '../../_types'
|
|
2
|
+
import { sourceOverPerfect } from '../../BlendModes/blend-modes-perfect'
|
|
3
|
+
import { blendColorPixelDataAlphaMask } from '../../PixelData/blendColorPixelDataAlphaMask'
|
|
4
|
+
import { blendColorPixelDataBinaryMask } from '../../PixelData/blendColorPixelDataBinaryMask'
|
|
5
|
+
import { PixelWriter } from '../PixelWriter'
|
|
6
|
+
|
|
7
|
+
const defaults = {
|
|
8
|
+
blendColorPixelDataAlphaMask,
|
|
9
|
+
blendColorPixelDataBinaryMask,
|
|
10
|
+
}
|
|
11
|
+
type Deps = Partial<typeof defaults>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param deps - @hidden
|
|
15
|
+
*/
|
|
16
|
+
export const mutatorBlendPaintMask = ((writer: PixelWriter<any>, deps: Partial<Deps> = defaults) => {
|
|
17
|
+
const {
|
|
18
|
+
blendColorPixelDataBinaryMask = defaults.blendColorPixelDataBinaryMask,
|
|
19
|
+
blendColorPixelDataAlphaMask = defaults.blendColorPixelDataAlphaMask,
|
|
20
|
+
} = deps
|
|
21
|
+
|
|
22
|
+
const OPTS = {
|
|
23
|
+
x: 0,
|
|
24
|
+
y: 0,
|
|
25
|
+
blendFn: sourceOverPerfect,
|
|
26
|
+
alpha: 255,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
blendColorPaintMask(
|
|
31
|
+
color: Color32,
|
|
32
|
+
mask: PaintMask,
|
|
33
|
+
x: number,
|
|
34
|
+
y: number,
|
|
35
|
+
alpha = 255,
|
|
36
|
+
blendFn = sourceOverPerfect,
|
|
37
|
+
): boolean {
|
|
38
|
+
const tx = x + mask.centerOffsetX
|
|
39
|
+
const ty = y + mask.centerOffsetY
|
|
40
|
+
|
|
41
|
+
const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h)
|
|
42
|
+
|
|
43
|
+
OPTS.x = tx
|
|
44
|
+
OPTS.y = ty
|
|
45
|
+
OPTS.alpha = alpha
|
|
46
|
+
OPTS.blendFn = blendFn
|
|
47
|
+
|
|
48
|
+
if (mask.type === MaskType.BINARY) {
|
|
49
|
+
return didChange(
|
|
50
|
+
blendColorPixelDataBinaryMask(writer.config.target, color, mask, OPTS),
|
|
51
|
+
)
|
|
52
|
+
} else {
|
|
53
|
+
return didChange(
|
|
54
|
+
blendColorPixelDataAlphaMask(writer.config.target, color, mask, OPTS),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}) satisfies HistoryMutator<any, Deps>
|
|
60
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HistoryMutator,
|
|
1
|
+
import type { HistoryMutator, IPixelData32, PixelBlendOptions } from '../../_types'
|
|
2
2
|
import { blendPixelData } from '../../PixelData/blendPixelData'
|
|
3
3
|
import { PixelWriter } from '../PixelWriter'
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ export const mutatorBlendPixelData = ((writer: PixelWriter<any>, deps: Partial<D
|
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
17
|
blendPixelData(
|
|
18
|
-
src:
|
|
18
|
+
src: IPixelData32,
|
|
19
19
|
opts: PixelBlendOptions = {},
|
|
20
20
|
): boolean {
|
|
21
21
|
const {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AlphaMask, HistoryMutator,
|
|
1
|
+
import type { AlphaMask, HistoryMutator, IPixelData32, PixelBlendMaskOptions } from '../../_types'
|
|
2
2
|
import { blendPixelDataAlphaMask } from '../../PixelData/blendPixelDataAlphaMask'
|
|
3
3
|
import { PixelWriter } from '../PixelWriter'
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ export const mutatorBlendPixelDataAlphaMask = ((writer: PixelWriter<any>, deps:
|
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
17
|
blendPixelDataAlphaMask(
|
|
18
|
-
src:
|
|
18
|
+
src: IPixelData32,
|
|
19
19
|
mask: AlphaMask,
|
|
20
20
|
opts: PixelBlendMaskOptions = {},
|
|
21
21
|
): boolean {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BinaryMask, HistoryMutator,
|
|
1
|
+
import type { BinaryMask, HistoryMutator, IPixelData32, PixelBlendMaskOptions } from '../../_types'
|
|
2
2
|
import { blendPixelDataBinaryMask } from '../../PixelData/blendPixelDataBinaryMask'
|
|
3
3
|
import { PixelWriter } from '../PixelWriter'
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ export const mutatorBlendPixelDataBinaryMask = ((writer: PixelWriter<any>, deps:
|
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
17
|
blendPixelDataBinaryMask(
|
|
18
|
-
src:
|
|
18
|
+
src: IPixelData32,
|
|
19
19
|
mask: BinaryMask,
|
|
20
20
|
opts: PixelBlendMaskOptions = {},
|
|
21
21
|
): boolean {
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
import { mutatorApplyAlphaMask } from './PixelMutator/mutatorApplyAlphaMask'
|
|
2
|
-
import { mutatorApplyBinaryMask } from './PixelMutator/mutatorApplyBinaryMask'
|
|
3
|
-
import { mutatorApplyCircleBrushStroke } from './PixelMutator/mutatorApplyCircleBrushStroke'
|
|
4
|
-
import { mutatorBlendColorCircleMask } from './PixelMutator/mutatorBlendColorCircleMask'
|
|
5
|
-
import { mutatorApplyCirclePencil } from './PixelMutator/mutatorApplyCirclePencil'
|
|
6
|
-
import { mutatorApplyCirclePencilStroke } from './PixelMutator/mutatorApplyCirclePencilStroke'
|
|
7
|
-
import { mutatorApplyRectBrush } from './PixelMutator/mutatorApplyRectBrush'
|
|
8
|
-
import { mutatorApplyRectBrushStroke } from './PixelMutator/mutatorApplyRectBrushStroke'
|
|
9
|
-
import { mutatorApplyRectPencil } from './PixelMutator/mutatorApplyRectPencil'
|
|
10
|
-
import { mutatorApplyRectPencilStroke } from './PixelMutator/mutatorApplyRectPencilStroke'
|
|
11
1
|
import { mutatorBlendColor } from './PixelMutator/mutatorBlendColor'
|
|
12
2
|
import { mutatorBlendPixel } from './PixelMutator/mutatorBlendPixel'
|
|
13
3
|
import { mutatorBlendPixelData } from './PixelMutator/mutatorBlendPixelData'
|
|
@@ -22,17 +12,7 @@ import type { PixelWriter } from './PixelWriter'
|
|
|
22
12
|
export function makeFullPixelMutator(writer: PixelWriter<any>) {
|
|
23
13
|
return {
|
|
24
14
|
// @sort
|
|
25
|
-
...mutatorApplyAlphaMask(writer),
|
|
26
|
-
...mutatorApplyBinaryMask(writer),
|
|
27
|
-
...mutatorApplyCircleBrushStroke(writer),
|
|
28
|
-
...mutatorApplyCirclePencil(writer),
|
|
29
|
-
...mutatorApplyCirclePencilStroke(writer),
|
|
30
|
-
...mutatorApplyRectBrush(writer),
|
|
31
|
-
...mutatorApplyRectBrushStroke(writer),
|
|
32
|
-
...mutatorApplyRectPencil(writer),
|
|
33
|
-
...mutatorApplyRectPencilStroke(writer),
|
|
34
15
|
...mutatorBlendColor(writer),
|
|
35
|
-
...mutatorBlendColorCircleMask(writer),
|
|
36
16
|
...mutatorBlendPixel(writer),
|
|
37
17
|
...mutatorBlendPixelData(writer),
|
|
38
18
|
...mutatorBlendPixelDataAlphaMask(writer),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IPixelData32 } from '../_types'
|
|
2
2
|
import { PixelTile } from '../PixelTile/PixelTile'
|
|
3
3
|
|
|
4
4
|
export type PixelPatchTiles = {
|
|
@@ -6,7 +6,7 @@ export type PixelPatchTiles = {
|
|
|
6
6
|
afterTiles: PixelTile[]
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function applyPatchTiles(target:
|
|
9
|
+
export function applyPatchTiles(target: IPixelData32, tiles: PixelTile[], tileSize: number) {
|
|
10
10
|
for (let i = 0; i < tiles.length; i++) {
|
|
11
11
|
const tile = tiles[i]
|
|
12
12
|
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
|
|
2
|
+
import { resizeImageData } from '../ImageData/resizeImageData'
|
|
3
|
+
import { PaintBuffer } from '../Paint/PaintBuffer'
|
|
4
|
+
import { blendPixelData } from '../PixelData/blendPixelData'
|
|
5
|
+
import type { PixelData } from '../PixelData/PixelData'
|
|
6
|
+
import { PixelTilePool } from '../PixelTile/PixelTilePool'
|
|
2
7
|
import { type HistoryActionFactory, makeHistoryAction } from './HistoryAction'
|
|
3
8
|
import { HistoryManager } from './HistoryManager'
|
|
4
9
|
import { PixelAccumulator } from './PixelAccumulator'
|
|
5
10
|
import { PixelEngineConfig } from './PixelEngineConfig'
|
|
6
|
-
import { PixelTilePool } from '../PixelTile/PixelTilePool'
|
|
7
11
|
|
|
8
12
|
export interface PixelWriterOptions {
|
|
9
13
|
maxHistorySteps?: number
|
|
@@ -11,6 +15,7 @@ export interface PixelWriterOptions {
|
|
|
11
15
|
historyManager?: HistoryManager
|
|
12
16
|
historyActionFactory?: HistoryActionFactory
|
|
13
17
|
pixelTilePool?: PixelTilePool,
|
|
18
|
+
accumulator?: PixelAccumulator
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -39,34 +44,72 @@ export class PixelWriter<M> {
|
|
|
39
44
|
readonly accumulator: PixelAccumulator
|
|
40
45
|
readonly historyActionFactory: HistoryActionFactory
|
|
41
46
|
readonly config: PixelEngineConfig
|
|
47
|
+
readonly pixelTilePool: PixelTilePool
|
|
48
|
+
readonly paintBuffer: PaintBuffer
|
|
42
49
|
readonly mutator: M
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
private blendPixelDataOpts = {
|
|
52
|
+
alpha: 255,
|
|
53
|
+
blendFn: sourceOverPerfect,
|
|
54
|
+
x: 0,
|
|
55
|
+
y: 0,
|
|
56
|
+
w: 0,
|
|
57
|
+
h: 0,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private _inProgress = false
|
|
61
|
+
|
|
62
|
+
constructor(target: PixelData, mutatorFactory: (writer: PixelWriter<any>) => M, {
|
|
45
63
|
tileSize = 256,
|
|
46
64
|
maxHistorySteps = 50,
|
|
47
65
|
historyManager = new HistoryManager(maxHistorySteps),
|
|
48
66
|
historyActionFactory = makeHistoryAction,
|
|
49
67
|
pixelTilePool,
|
|
68
|
+
accumulator,
|
|
50
69
|
}: PixelWriterOptions = {}) {
|
|
51
70
|
this.config = new PixelEngineConfig(tileSize, target)
|
|
52
71
|
this.historyManager = historyManager
|
|
53
|
-
pixelTilePool
|
|
54
|
-
this.accumulator = new PixelAccumulator(this.config, pixelTilePool)
|
|
72
|
+
this.pixelTilePool = pixelTilePool ?? new PixelTilePool(this.config)
|
|
73
|
+
this.accumulator = accumulator ?? new PixelAccumulator(this.config, this.pixelTilePool)
|
|
55
74
|
this.historyActionFactory = historyActionFactory
|
|
56
75
|
this.mutator = mutatorFactory(this)
|
|
76
|
+
this.paintBuffer = new PaintBuffer(this.config, this.pixelTilePool)
|
|
57
77
|
}
|
|
58
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Executes `transaction` and commits the resulting pixel changes as a single
|
|
81
|
+
* undoable history action.
|
|
82
|
+
*
|
|
83
|
+
* - If `transaction` throws, all accumulated changes are rolled back and the error
|
|
84
|
+
* is re-thrown. No action is committed.
|
|
85
|
+
* - If `transaction` completes without modifying any pixels, no action is committed.
|
|
86
|
+
* - `withHistory` is not re-entrant. Calling it again from inside `transaction` will
|
|
87
|
+
* throw immediately to prevent silent data loss from a nested extractPatch.
|
|
88
|
+
*
|
|
89
|
+
* @param transaction Callback to be executed inside the transaction.
|
|
90
|
+
* @param after Called after both undo and redo — use for generic change notifications.
|
|
91
|
+
* @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
|
|
92
|
+
* @param afterRedo Called after redo only.
|
|
93
|
+
*/
|
|
59
94
|
withHistory(
|
|
60
|
-
|
|
95
|
+
transaction: (mutator: M) => void,
|
|
61
96
|
after?: () => void,
|
|
62
97
|
afterUndo?: () => void,
|
|
63
98
|
afterRedo?: () => void,
|
|
64
|
-
) {
|
|
99
|
+
): void {
|
|
100
|
+
if (this._inProgress) {
|
|
101
|
+
throw new Error('withHistory is not re-entrant — commit or rollback the current operation first')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this._inProgress = true
|
|
105
|
+
|
|
65
106
|
try {
|
|
66
|
-
|
|
107
|
+
transaction(this.mutator)
|
|
67
108
|
} catch (e) {
|
|
68
|
-
this.accumulator.
|
|
109
|
+
this.accumulator.rollbackAfterError()
|
|
69
110
|
throw e
|
|
111
|
+
} finally {
|
|
112
|
+
this._inProgress = false
|
|
70
113
|
}
|
|
71
114
|
|
|
72
115
|
if (this.accumulator.beforeTiles.length === 0) return
|
|
@@ -76,4 +119,84 @@ export class PixelWriter<M> {
|
|
|
76
119
|
|
|
77
120
|
this.historyManager.commit(action)
|
|
78
121
|
}
|
|
122
|
+
|
|
123
|
+
resize(
|
|
124
|
+
newWidth: number,
|
|
125
|
+
newHeight: number,
|
|
126
|
+
offsetX = 0,
|
|
127
|
+
offsetY = 0,
|
|
128
|
+
after?: (target: ImageData) => void,
|
|
129
|
+
afterUndo?: (target: ImageData) => void,
|
|
130
|
+
afterRedo?: (target: ImageData) => void,
|
|
131
|
+
resizeImageDataFn = resizeImageData,
|
|
132
|
+
): void {
|
|
133
|
+
if (this._inProgress) {
|
|
134
|
+
throw new Error('Cannot resize inside a withHistory callback')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (this.accumulator.beforeTiles.length > 0) {
|
|
138
|
+
throw new Error('Cannot resize with an open accumulator — commit or rollback first')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const config = this.config
|
|
142
|
+
const target = config.target
|
|
143
|
+
const beforeImageData = target.imageData
|
|
144
|
+
const afterImageData = resizeImageDataFn(beforeImageData, newWidth, newHeight, offsetX, offsetY)
|
|
145
|
+
|
|
146
|
+
target.set(afterImageData)
|
|
147
|
+
|
|
148
|
+
this.historyManager.commit({
|
|
149
|
+
undo: () => {
|
|
150
|
+
target.set(beforeImageData)
|
|
151
|
+
afterUndo?.(beforeImageData)
|
|
152
|
+
after?.(beforeImageData)
|
|
153
|
+
},
|
|
154
|
+
redo: () => {
|
|
155
|
+
target.set(afterImageData)
|
|
156
|
+
afterRedo?.(afterImageData)
|
|
157
|
+
after?.(afterImageData)
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
commitPaintBuffer(
|
|
163
|
+
alpha = 255,
|
|
164
|
+
blendFn = sourceOverPerfect,
|
|
165
|
+
blendPixelDataFn = blendPixelData,
|
|
166
|
+
) {
|
|
167
|
+
const paintBuffer = this.paintBuffer
|
|
168
|
+
const tileShift = paintBuffer.config.tileShift
|
|
169
|
+
const lookup = paintBuffer.lookup
|
|
170
|
+
|
|
171
|
+
const opts = this.blendPixelDataOpts
|
|
172
|
+
|
|
173
|
+
opts.alpha = alpha
|
|
174
|
+
opts.blendFn = blendFn
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < lookup.length; i++) {
|
|
177
|
+
const tile = lookup[i]
|
|
178
|
+
|
|
179
|
+
if (tile) {
|
|
180
|
+
const didChange = this.accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty)
|
|
181
|
+
|
|
182
|
+
const dx = tile.tx << tileShift
|
|
183
|
+
const dy = tile.ty << tileShift
|
|
184
|
+
|
|
185
|
+
opts.x = dx
|
|
186
|
+
opts.y = dy
|
|
187
|
+
opts.w = tile.width
|
|
188
|
+
opts.h = tile.height
|
|
189
|
+
|
|
190
|
+
didChange(
|
|
191
|
+
blendPixelDataFn(
|
|
192
|
+
this.config.target,
|
|
193
|
+
tile,
|
|
194
|
+
opts,
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
paintBuffer.clear()
|
|
201
|
+
}
|
|
79
202
|
}
|