pixel-data-js 0.27.0 → 0.28.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.
- package/README.md +12 -2
- package/dist/index.prod.cjs +2222 -1045
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +542 -417
- package/dist/index.prod.js +2167 -1024
- package/dist/index.prod.js.map +1 -1
- package/package.json +11 -11
- package/src/Algorithm/floodFillSelection.ts +8 -6
- package/src/Algorithm/forEachLinePoint.ts +6 -6
- package/src/{Internal/resample32.ts → Algorithm/resampleUint32Array.ts} +11 -21
- package/src/BlendModes/blend-modes-fast.ts +169 -0
- package/src/BlendModes/blend-modes-perfect.ts +207 -0
- package/src/BlendModes/blend-modes.ts +9 -0
- package/src/Canvas/CanvasFrameRenderer.ts +20 -28
- package/src/Canvas/CanvasPixelDataRenderer.ts +23 -0
- package/src/Canvas/PixelCanvas.ts +2 -7
- package/src/Canvas/ReusableCanvas.ts +4 -12
- package/src/Canvas/_canvas-types.ts +26 -0
- package/src/History/PixelAccumulator.ts +17 -17
- package/src/History/PixelEngineConfig.ts +3 -3
- package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +4 -3
- package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +4 -3
- package/src/History/PixelMutator/mutatorApplyMask.ts +4 -3
- package/src/History/PixelMutator/mutatorBlendAlphaMask.ts +6 -4
- package/src/History/PixelMutator/mutatorBlendBinaryMask.ts +6 -4
- package/src/History/PixelMutator/mutatorBlendColor.ts +2 -2
- package/src/History/PixelMutator/mutatorBlendColorPaintAlphaMask.ts +2 -1
- package/src/History/PixelMutator/mutatorBlendColorPaintBinaryMask.ts +2 -1
- package/src/History/PixelMutator/mutatorBlendColorPaintMask.ts +3 -1
- package/src/History/PixelMutator/mutatorBlendColorPaintRect.ts +3 -3
- package/src/History/PixelMutator/mutatorBlendMask.ts +6 -4
- package/src/History/PixelMutator/mutatorBlendPixelData.ts +5 -4
- package/src/History/PixelMutator/mutatorClear.ts +4 -3
- package/src/History/PixelMutator/mutatorFill.ts +5 -4
- package/src/History/PixelMutator/mutatorFillBinaryMask.ts +2 -1
- package/src/History/PixelMutator/mutatorInvert.ts +2 -2
- package/src/History/PixelMutator.ts +1 -1
- package/src/History/PixelPatchTiles.ts +7 -7
- package/src/History/PixelWriter.ts +12 -63
- package/src/ImageData/ImageDataLike.ts +1 -1
- package/src/ImageData/_ImageData-types.ts +13 -0
- package/src/ImageData/copyImageData.ts +1 -1
- package/src/ImageData/extractImageDataBuffer.ts +3 -2
- package/src/ImageData/imageDataToUint32Array.ts +18 -0
- package/src/ImageData/resampleImageData.ts +3 -3
- package/src/ImageData/resizeImageData.ts +1 -1
- package/src/ImageData/serialization.ts +1 -1
- package/src/ImageData/uInt32ArrayToImageData.ts +1 -1
- package/src/ImageData/writeImageData.ts +2 -2
- package/src/ImageData/writeImageDataBuffer.ts +2 -2
- package/src/IndexedImage/IndexedImage.ts +56 -98
- package/src/IndexedImage/_indexedImage-types.ts +18 -0
- package/src/IndexedImage/getIndexedImageColorCounts.ts +3 -3
- package/src/IndexedImage/indexedImageToAverageColor.ts +1 -1
- package/src/IndexedImage/indexedImageToImageData.ts +4 -6
- package/src/IndexedImage/resampleIndexedImage.ts +7 -15
- package/src/Input/fileToImageData.ts +1 -1
- package/src/Internal/_errors.ts +2 -0
- package/src/Internal/macros.ts +14 -0
- package/src/Mask/AlphaMask.ts +1 -1
- package/src/Mask/BinaryMask/makeBinaryMaskFromAlphaMask.ts +23 -0
- package/src/Mask/BinaryMask/makeBinaryMaskOutline.ts +88 -0
- package/src/Mask/BinaryMask/makeCircleBinaryMaskOutline.ts +104 -0
- package/src/Mask/BinaryMask/makeRectBinaryMaskOutline.ts +34 -0
- package/src/Mask/BinaryMask.ts +1 -1
- package/src/Mask/_mask-types.ts +73 -0
- package/src/Mask/applyBinaryMaskToAlphaMask.ts +2 -1
- package/src/Mask/copyMask.ts +1 -1
- package/src/Mask/extractMask.ts +2 -1
- package/src/Mask/extractMaskBuffer.ts +1 -1
- package/src/Mask/mergeAlphaMasks.ts +6 -3
- package/src/Mask/mergeBinaryMasks.ts +2 -1
- package/src/Mask/setMaskData.ts +1 -1
- package/src/MaskRect/merge2BinaryMaskRects.ts +2 -2
- package/src/MaskRect/mergeBinaryMaskRects.ts +1 -1
- package/src/MaskRect/subtractBinaryMaskRects.ts +1 -1
- package/src/Paint/AlphaMaskPaintBuffer.ts +339 -0
- package/src/Paint/AlphaMaskPaintBufferCanvasRenderer.ts +78 -0
- package/src/Paint/BinaryMaskPaintBuffer.ts +254 -0
- package/src/Paint/BinaryMaskPaintBufferCanvasRenderer.ts +67 -0
- package/src/Paint/{PaintBuffer.ts → ColorPaintBuffer.ts} +148 -77
- package/src/Paint/{PaintBufferCanvasRenderer.ts → ColorPaintBufferCanvasRenderer.ts} +6 -5
- package/src/Paint/PaintCursorRenderer.ts +117 -0
- package/src/Paint/_paint-types.ts +22 -0
- package/src/Paint/eachTileInBounds.ts +45 -0
- package/src/Paint/makeCirclePaintMask.ts +74 -0
- package/src/Paint/makePaintMask.ts +5 -2
- package/src/Paint/makeRectFalloffPaintAlphaMask.ts +4 -2
- package/src/PixelData/PixelData.ts +15 -19
- package/src/PixelData/ReusablePixelData.ts +36 -0
- package/src/PixelData/_pixelData-types.ts +17 -0
- package/src/PixelData/applyAlphaMaskToPixelData.ts +80 -43
- package/src/PixelData/applyBinaryMaskToPixelData.ts +10 -8
- package/src/PixelData/applyMaskToPixelData.ts +4 -9
- package/src/PixelData/blendColorPixelData.ts +9 -8
- package/src/PixelData/blendColorPixelDataAlphaMask.ts +9 -7
- package/src/PixelData/blendColorPixelDataBinaryMask.ts +9 -7
- package/src/PixelData/blendColorPixelDataMask.ts +4 -2
- package/src/PixelData/blendColorPixelDataPaintAlphaMask.ts +4 -2
- package/src/PixelData/blendColorPixelDataPaintBinaryMask.ts +4 -2
- package/src/PixelData/blendColorPixelDataPaintMask.ts +5 -2
- package/src/PixelData/blendPixel.ts +6 -5
- package/src/PixelData/blendPixelData.ts +14 -13
- package/src/PixelData/blendPixelDataAlphaMask.ts +15 -13
- package/src/PixelData/blendPixelDataBinaryMask.ts +15 -13
- package/src/PixelData/blendPixelDataMask.ts +5 -3
- package/src/PixelData/blendPixelDataPaintBuffer.ts +5 -4
- package/src/PixelData/clearPixelDataFast.ts +4 -2
- package/src/PixelData/copyPixelData.ts +14 -0
- package/src/PixelData/extractPixelData.ts +8 -7
- package/src/PixelData/extractPixelDataBuffer.ts +9 -8
- package/src/PixelData/fillPixelData.ts +16 -14
- package/src/PixelData/fillPixelDataBinaryMask.ts +10 -8
- package/src/PixelData/fillPixelDataFast.ts +16 -14
- package/src/PixelData/invertPixelData.ts +9 -8
- package/src/PixelData/pixelDataToAlphaMask.ts +9 -8
- package/src/PixelData/reflectPixelData.ts +9 -9
- package/src/PixelData/resamplePixelData.ts +20 -9
- package/src/PixelData/rotatePixelData.ts +8 -7
- package/src/PixelData/uInt32ArrayToPixelData.ts +15 -0
- package/src/PixelData/writePaintBufferToPixelData.ts +5 -5
- package/src/PixelData/writePixelDataBuffer.ts +10 -9
- package/src/Rect/_rect-types.ts +7 -0
- package/src/Rect/getRectsBounds.ts +1 -1
- package/src/Rect/trimMaskRectBounds.ts +2 -1
- package/src/Rect/trimRectBounds.ts +1 -1
- package/src/Tile/MaskTile.ts +40 -0
- package/src/Tile/PixelTile.ts +23 -0
- package/src/{PixelTile/PixelTilePool.ts → Tile/TilePool.ts} +9 -9
- package/src/Tile/_tile-types.ts +33 -0
- package/src/_errors.ts +1 -0
- package/src/_types.ts +2 -118
- package/src/index.ts +46 -21
- package/src/ImageData/imageDataToUInt32Array.ts +0 -13
- package/src/Internal/helpers.ts +0 -5
- package/src/Paint/makeCirclePaintAlphaMask.ts +0 -41
- package/src/Paint/makeCirclePaintBinaryMask.ts +0 -29
- package/src/PixelTile/PixelTile.ts +0 -21
- /package/src/{Internal → Rect}/resolveClipping.ts +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { type Color32 } from '../_types'
|
|
2
|
+
import { forEachLinePoint } from '../Algorithm/forEachLinePoint'
|
|
3
|
+
import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
|
|
4
|
+
import type { PixelAccumulator } from '../History/PixelAccumulator'
|
|
5
|
+
import type { PixelEngineConfig } from '../History/PixelEngineConfig'
|
|
6
|
+
import { _macro_paintRectCenterOffset } from '../Internal/macros'
|
|
7
|
+
import { blendColorPixelDataBinaryMask } from '../PixelData/blendColorPixelDataBinaryMask'
|
|
8
|
+
import type { Rect } from '../Rect/_rect-types'
|
|
9
|
+
import { trimRectBounds } from '../Rect/trimRectBounds'
|
|
10
|
+
import type { BinaryMaskTile } from '../Tile/_tile-types'
|
|
11
|
+
import type { TilePool } from '../Tile/TilePool'
|
|
12
|
+
import type { PaintBinaryMask } from './_paint-types'
|
|
13
|
+
import { eachTileInBounds } from './eachTileInBounds'
|
|
14
|
+
|
|
15
|
+
export class BinaryMaskPaintBuffer {
|
|
16
|
+
readonly lookup: (BinaryMaskTile | undefined)[]
|
|
17
|
+
private readonly scratchBounds: Rect = { x: 0, y: 0, w: 0, h: 0 }
|
|
18
|
+
|
|
19
|
+
private blendColorPixelDataBinaryMaskFn = blendColorPixelDataBinaryMask
|
|
20
|
+
private forEachLinePointFn = forEachLinePoint
|
|
21
|
+
private trimRectBoundsFn = trimRectBounds
|
|
22
|
+
private eachTileInBoundsFn = eachTileInBounds
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
readonly config: PixelEngineConfig,
|
|
26
|
+
readonly tilePool: TilePool<BinaryMaskTile>,
|
|
27
|
+
) {
|
|
28
|
+
this.lookup = []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
paintBinaryMask(
|
|
32
|
+
brush: PaintBinaryMask,
|
|
33
|
+
x: number,
|
|
34
|
+
y: number,
|
|
35
|
+
): boolean
|
|
36
|
+
paintBinaryMask(
|
|
37
|
+
brush: PaintBinaryMask,
|
|
38
|
+
startX: number,
|
|
39
|
+
startY: number,
|
|
40
|
+
endX: number,
|
|
41
|
+
endY: number,
|
|
42
|
+
): boolean
|
|
43
|
+
paintBinaryMask(
|
|
44
|
+
brush: PaintBinaryMask,
|
|
45
|
+
x0: number,
|
|
46
|
+
y0: number,
|
|
47
|
+
x1: number = x0,
|
|
48
|
+
y1: number = y0,
|
|
49
|
+
): boolean {
|
|
50
|
+
const scratch = this.scratchBounds
|
|
51
|
+
const lookup = this.lookup
|
|
52
|
+
const tilePool = this.tilePool
|
|
53
|
+
const config = this.config
|
|
54
|
+
const tileShift = config.tileShift
|
|
55
|
+
const tileMask = config.tileMask
|
|
56
|
+
const target = config.target
|
|
57
|
+
|
|
58
|
+
const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
|
|
59
|
+
let changed = false
|
|
60
|
+
|
|
61
|
+
const trimRectBoundsFn = this.trimRectBoundsFn
|
|
62
|
+
const eachTileInBoundsFn = this.eachTileInBoundsFn
|
|
63
|
+
|
|
64
|
+
this.forEachLinePointFn(x0, y0, x1, y1, (px, py) => {
|
|
65
|
+
const topLeftX = Math.floor(px + centerOffsetX)
|
|
66
|
+
const topLeftY = Math.floor(py + centerOffsetY)
|
|
67
|
+
|
|
68
|
+
trimRectBoundsFn(
|
|
69
|
+
topLeftX,
|
|
70
|
+
topLeftY,
|
|
71
|
+
bW,
|
|
72
|
+
bH,
|
|
73
|
+
target.w,
|
|
74
|
+
target.h,
|
|
75
|
+
scratch,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (scratch.w <= 0 || scratch.h <= 0) return
|
|
79
|
+
|
|
80
|
+
eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
|
|
81
|
+
const data = tile.data
|
|
82
|
+
let tileChanged = false
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < bH_t; i++) {
|
|
85
|
+
const canvasY = bY + i
|
|
86
|
+
const bOff = (canvasY - topLeftY) * bW
|
|
87
|
+
const tOff = (canvasY & tileMask) << tileShift
|
|
88
|
+
const dS = tOff + (bX & tileMask)
|
|
89
|
+
|
|
90
|
+
for (let j = 0; j < bW_t; j++) {
|
|
91
|
+
const canvasX = bX + j
|
|
92
|
+
|
|
93
|
+
if (bD[bOff + (canvasX - topLeftX)]) {
|
|
94
|
+
const idx = dS + j
|
|
95
|
+
|
|
96
|
+
// Only write if it's not already "on"
|
|
97
|
+
if (data[idx] === 0) {
|
|
98
|
+
data[idx] = 1
|
|
99
|
+
tileChanged = true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (tileChanged) changed = true
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return changed
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
paintRect(
|
|
112
|
+
brushWidth: number,
|
|
113
|
+
brushHeight: number,
|
|
114
|
+
x: number,
|
|
115
|
+
y: number,
|
|
116
|
+
): boolean
|
|
117
|
+
paintRect(
|
|
118
|
+
brushWidth: number,
|
|
119
|
+
brushHeight: number,
|
|
120
|
+
startX: number,
|
|
121
|
+
startY: number,
|
|
122
|
+
endX: number,
|
|
123
|
+
endY: number,
|
|
124
|
+
): boolean
|
|
125
|
+
paintRect(
|
|
126
|
+
brushWidth: number,
|
|
127
|
+
brushHeight: number,
|
|
128
|
+
x0: number,
|
|
129
|
+
y0: number,
|
|
130
|
+
x1: number = x0,
|
|
131
|
+
y1: number = y0,
|
|
132
|
+
): boolean {
|
|
133
|
+
const scratch = this.scratchBounds
|
|
134
|
+
const lookup = this.lookup
|
|
135
|
+
const tilePool = this.tilePool
|
|
136
|
+
const config = this.config
|
|
137
|
+
const tileShift = config.tileShift
|
|
138
|
+
const tileMask = config.tileMask
|
|
139
|
+
const target = config.target
|
|
140
|
+
|
|
141
|
+
const centerOffsetX = _macro_paintRectCenterOffset(brushWidth)
|
|
142
|
+
const centerOffsetY = _macro_paintRectCenterOffset(brushHeight)
|
|
143
|
+
|
|
144
|
+
const trimRectBoundsFn = this.trimRectBoundsFn
|
|
145
|
+
const eachTileInBoundsFn = this.eachTileInBoundsFn
|
|
146
|
+
|
|
147
|
+
let changed = false
|
|
148
|
+
this.forEachLinePointFn(
|
|
149
|
+
x0,
|
|
150
|
+
y0,
|
|
151
|
+
x1,
|
|
152
|
+
y1,
|
|
153
|
+
(px, py) => {
|
|
154
|
+
const topLeftX = Math.floor(px + centerOffsetX)
|
|
155
|
+
const topLeftY = Math.floor(py + centerOffsetY)
|
|
156
|
+
|
|
157
|
+
trimRectBoundsFn(
|
|
158
|
+
topLeftX,
|
|
159
|
+
topLeftY,
|
|
160
|
+
brushWidth,
|
|
161
|
+
brushHeight,
|
|
162
|
+
target.w,
|
|
163
|
+
target.h,
|
|
164
|
+
scratch,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if (scratch.w <= 0 || scratch.h <= 0) return
|
|
168
|
+
|
|
169
|
+
eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
|
|
170
|
+
const data = tile.data
|
|
171
|
+
let tileChanged = false
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < bH_t; i++) {
|
|
174
|
+
const canvasY = bY + i
|
|
175
|
+
const tOff = (canvasY & tileMask) << tileShift
|
|
176
|
+
const dS = tOff + (bX & tileMask)
|
|
177
|
+
|
|
178
|
+
for (let j = 0; j < bW_t; j++) {
|
|
179
|
+
const idx = dS + j
|
|
180
|
+
|
|
181
|
+
// Only write if it's not already "on"
|
|
182
|
+
if (data[idx] === 0) {
|
|
183
|
+
data[idx] = 1
|
|
184
|
+
tileChanged = true
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (tileChanged) {
|
|
190
|
+
changed = true
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
)
|
|
194
|
+
},
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
return changed
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private opts = {
|
|
201
|
+
alpha: 255,
|
|
202
|
+
blendFn: sourceOverPerfect,
|
|
203
|
+
x: 0,
|
|
204
|
+
y: 0,
|
|
205
|
+
w: 0,
|
|
206
|
+
h: 0,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
commit(
|
|
210
|
+
accumulator: PixelAccumulator,
|
|
211
|
+
color: Color32,
|
|
212
|
+
alpha = 255,
|
|
213
|
+
blendFn = sourceOverPerfect,
|
|
214
|
+
) {
|
|
215
|
+
const blendColorPixelDataBinaryMaskFn = this.blendColorPixelDataBinaryMaskFn
|
|
216
|
+
const tileShift = this.config.tileShift
|
|
217
|
+
const lookup = this.lookup
|
|
218
|
+
const opts = this.opts
|
|
219
|
+
|
|
220
|
+
opts.alpha = alpha
|
|
221
|
+
opts.blendFn = blendFn
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < lookup.length; i++) {
|
|
224
|
+
const tile = lookup[i]
|
|
225
|
+
|
|
226
|
+
if (tile) {
|
|
227
|
+
const didChange = accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty)
|
|
228
|
+
|
|
229
|
+
const dx = tile.tx << tileShift
|
|
230
|
+
const dy = tile.ty << tileShift
|
|
231
|
+
|
|
232
|
+
opts.x = dx
|
|
233
|
+
opts.y = dy
|
|
234
|
+
opts.w = tile.w
|
|
235
|
+
opts.h = tile.h
|
|
236
|
+
|
|
237
|
+
didChange(
|
|
238
|
+
blendColorPixelDataBinaryMaskFn(
|
|
239
|
+
this.config.target,
|
|
240
|
+
color,
|
|
241
|
+
tile,
|
|
242
|
+
opts,
|
|
243
|
+
),
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
this.clear()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
clear(): void {
|
|
252
|
+
this.tilePool.releaseTiles(this.lookup)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Color32 } from '../_types'
|
|
2
|
+
import { CANVAS_CTX_FAILED } from '../Internal/_errors'
|
|
3
|
+
import { makePixelData } from '../PixelData/PixelData'
|
|
4
|
+
import type { BinaryMaskPaintBuffer } from './BinaryMaskPaintBuffer'
|
|
5
|
+
|
|
6
|
+
export type BinaryMaskPaintBufferCanvasRenderer = ReturnType<typeof makeBinaryMaskPaintBufferCanvasRenderer>
|
|
7
|
+
|
|
8
|
+
export function makeBinaryMaskPaintBufferCanvasRenderer(
|
|
9
|
+
paintBuffer: BinaryMaskPaintBuffer,
|
|
10
|
+
offscreenCanvasClass = OffscreenCanvas,
|
|
11
|
+
) {
|
|
12
|
+
const config = paintBuffer.config
|
|
13
|
+
const tileSize = config.tileSize
|
|
14
|
+
const tileShift = config.tileShift
|
|
15
|
+
const tileArea = config.tileArea
|
|
16
|
+
const lookup = paintBuffer.lookup
|
|
17
|
+
|
|
18
|
+
const canvas = new offscreenCanvasClass(tileSize, tileSize)
|
|
19
|
+
const ctx = canvas.getContext('2d')
|
|
20
|
+
|
|
21
|
+
if (!ctx) throw new Error(CANVAS_CTX_FAILED)
|
|
22
|
+
|
|
23
|
+
ctx.imageSmoothingEnabled = false
|
|
24
|
+
|
|
25
|
+
const bridge = makePixelData(new ImageData(tileSize, tileSize))
|
|
26
|
+
const view32 = bridge.data
|
|
27
|
+
|
|
28
|
+
return function drawPaintBuffer(
|
|
29
|
+
targetCtx: CanvasRenderingContext2D,
|
|
30
|
+
color: Color32,
|
|
31
|
+
alpha = 255,
|
|
32
|
+
compOperation: GlobalCompositeOperation = 'source-over',
|
|
33
|
+
): void {
|
|
34
|
+
if (alpha === 0) return
|
|
35
|
+
|
|
36
|
+
const baseSrcAlpha = (color >>> 24)
|
|
37
|
+
if (baseSrcAlpha === 0) return
|
|
38
|
+
|
|
39
|
+
targetCtx.globalAlpha = alpha / 255
|
|
40
|
+
targetCtx.globalCompositeOperation = compOperation
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < lookup.length; i++) {
|
|
43
|
+
const tile = lookup[i]
|
|
44
|
+
|
|
45
|
+
if (tile) {
|
|
46
|
+
const data8 = tile.data
|
|
47
|
+
view32.fill(0)
|
|
48
|
+
|
|
49
|
+
for (let p = 0; p < tileArea; p++) {
|
|
50
|
+
// If mask is solid, the final pixel is just the unmodified color
|
|
51
|
+
if (data8[p] === 1) {
|
|
52
|
+
view32[p] = color
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const dx = tile.tx << tileShift
|
|
57
|
+
const dy = tile.ty << tileShift
|
|
58
|
+
|
|
59
|
+
ctx.putImageData(bridge.imageData, 0, 0)
|
|
60
|
+
targetCtx.drawImage(canvas, dx, dy)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
targetCtx.globalAlpha = 1
|
|
65
|
+
targetCtx.globalCompositeOperation = 'source-over'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,78 +1,64 @@
|
|
|
1
|
-
import type { Color32
|
|
1
|
+
import type { Color32 } from '../_types'
|
|
2
2
|
import { forEachLinePoint } from '../Algorithm/forEachLinePoint'
|
|
3
|
+
import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
|
|
4
|
+
import type { PixelAccumulator } from '../History/PixelAccumulator'
|
|
3
5
|
import type { PixelEngineConfig } from '../History/PixelEngineConfig'
|
|
4
|
-
import { _macro_paintRectCenterOffset } from '../Internal/
|
|
5
|
-
import
|
|
6
|
-
import type {
|
|
6
|
+
import { _macro_paintRectCenterOffset } from '../Internal/macros'
|
|
7
|
+
import { blendPixelData } from '../PixelData/blendPixelData'
|
|
8
|
+
import type { Rect } from '../Rect/_rect-types'
|
|
7
9
|
import { trimRectBounds } from '../Rect/trimRectBounds'
|
|
10
|
+
import type { PixelTile } from '../Tile/_tile-types'
|
|
11
|
+
import type { TilePool } from '../Tile/TilePool'
|
|
12
|
+
import type { PaintAlphaMask, PaintBinaryMask } from './_paint-types'
|
|
13
|
+
import { eachTileInBounds } from './eachTileInBounds'
|
|
8
14
|
|
|
9
|
-
export class
|
|
15
|
+
export class ColorPaintBuffer {
|
|
10
16
|
readonly lookup: (PixelTile | undefined)[]
|
|
11
17
|
private readonly scratchBounds: Rect = { x: 0, y: 0, w: 0, h: 0 }
|
|
12
18
|
|
|
13
19
|
constructor(
|
|
14
20
|
readonly config: PixelEngineConfig,
|
|
15
|
-
readonly tilePool:
|
|
21
|
+
readonly tilePool: TilePool<PixelTile>,
|
|
22
|
+
private blendPixelDataFn = blendPixelData,
|
|
16
23
|
) {
|
|
17
24
|
this.lookup = []
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for (let ty = y1; ty <= y2; ty++) {
|
|
37
|
-
const rowOffset = ty * targetColumns
|
|
38
|
-
const tileTop = ty << tileShift
|
|
39
|
-
|
|
40
|
-
for (let tx = x1; tx <= x2; tx++) {
|
|
41
|
-
const id = rowOffset + tx
|
|
42
|
-
const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty))
|
|
43
|
-
const tileLeft = tx << tileShift
|
|
44
|
-
|
|
45
|
-
const startX = bounds.x > tileLeft ? bounds.x : tileLeft
|
|
46
|
-
const startY = bounds.y > tileTop ? bounds.y : tileTop
|
|
47
|
-
|
|
48
|
-
const maskEndX = bounds.x + bounds.w
|
|
49
|
-
const tileEndX = tileLeft + tileSize
|
|
50
|
-
const endX = maskEndX < tileEndX ? maskEndX : tileEndX
|
|
51
|
-
|
|
52
|
-
const maskEndY = bounds.y + bounds.h
|
|
53
|
-
const tileEndY = tileTop + tileSize
|
|
54
|
-
const endY = maskEndY < tileEndY ? maskEndY : tileEndY
|
|
55
|
-
|
|
56
|
-
callback(tile, startX, startY, endX - startX, endY - startY)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
writePaintAlphaMaskStroke(
|
|
27
|
+
paintAlphaMask(
|
|
28
|
+
color: Color32,
|
|
29
|
+
brush: PaintAlphaMask,
|
|
30
|
+
x: number,
|
|
31
|
+
y: number,
|
|
32
|
+
): boolean
|
|
33
|
+
paintAlphaMask(
|
|
34
|
+
color: Color32,
|
|
35
|
+
brush: PaintAlphaMask,
|
|
36
|
+
startX: number,
|
|
37
|
+
startY: number,
|
|
38
|
+
endX: number,
|
|
39
|
+
endY: number,
|
|
40
|
+
): boolean
|
|
41
|
+
paintAlphaMask(
|
|
62
42
|
color: Color32,
|
|
63
43
|
brush: PaintAlphaMask,
|
|
64
44
|
x0: number,
|
|
65
45
|
y0: number,
|
|
66
|
-
x1: number,
|
|
67
|
-
y1: number,
|
|
46
|
+
x1: number = x0,
|
|
47
|
+
y1: number = y0,
|
|
68
48
|
): boolean {
|
|
69
49
|
const cA = color >>> 24
|
|
70
50
|
if (cA === 0) return false
|
|
71
51
|
|
|
72
|
-
const
|
|
52
|
+
const scratch = this.scratchBounds
|
|
53
|
+
const lookup = this.lookup
|
|
54
|
+
const tilePool = this.tilePool
|
|
55
|
+
const config = this.config
|
|
56
|
+
const tileShift = config.tileShift
|
|
57
|
+
const tileMask = config.tileMask
|
|
58
|
+
const target = config.target
|
|
59
|
+
|
|
73
60
|
const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
|
|
74
61
|
const cRGB = color & 0x00ffffff
|
|
75
|
-
const scratch = this.scratchBounds
|
|
76
62
|
|
|
77
63
|
let changed = false
|
|
78
64
|
|
|
@@ -85,15 +71,15 @@ export class PaintBuffer {
|
|
|
85
71
|
topLeftY,
|
|
86
72
|
bW,
|
|
87
73
|
bH,
|
|
88
|
-
target.
|
|
89
|
-
target.
|
|
74
|
+
target.w,
|
|
75
|
+
target.h,
|
|
90
76
|
scratch,
|
|
91
77
|
)
|
|
92
78
|
|
|
93
79
|
if (scratch.w <= 0 || scratch.h <= 0) return
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
const d32 = tile.
|
|
81
|
+
eachTileInBounds(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
|
|
82
|
+
const d32 = tile.data
|
|
97
83
|
let tileChanged = false
|
|
98
84
|
|
|
99
85
|
for (let i = 0; i < bH_t; i++) {
|
|
@@ -127,20 +113,40 @@ export class PaintBuffer {
|
|
|
127
113
|
return changed
|
|
128
114
|
}
|
|
129
115
|
|
|
130
|
-
|
|
116
|
+
paintBinaryMask(
|
|
117
|
+
color: Color32,
|
|
118
|
+
brush: PaintBinaryMask,
|
|
119
|
+
x: number,
|
|
120
|
+
y: number,
|
|
121
|
+
): boolean
|
|
122
|
+
paintBinaryMask(
|
|
123
|
+
color: Color32,
|
|
124
|
+
brush: PaintBinaryMask,
|
|
125
|
+
startX: number,
|
|
126
|
+
startY: number,
|
|
127
|
+
endX: number,
|
|
128
|
+
endY: number,
|
|
129
|
+
): boolean
|
|
130
|
+
paintBinaryMask(
|
|
131
131
|
color: Color32,
|
|
132
132
|
brush: PaintBinaryMask,
|
|
133
133
|
x0: number,
|
|
134
134
|
y0: number,
|
|
135
|
-
x1: number,
|
|
136
|
-
y1: number,
|
|
135
|
+
x1: number = x0,
|
|
136
|
+
y1: number = y0,
|
|
137
137
|
): boolean {
|
|
138
138
|
const alphaIsZero = (color >>> 24) === 0
|
|
139
139
|
if (alphaIsZero) return false
|
|
140
140
|
|
|
141
|
-
const { tileShift, tileMask, target } = this.config
|
|
142
|
-
const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
|
|
143
141
|
const scratch = this.scratchBounds
|
|
142
|
+
const lookup = this.lookup
|
|
143
|
+
const tilePool = this.tilePool
|
|
144
|
+
const config = this.config
|
|
145
|
+
const tileShift = config.tileShift
|
|
146
|
+
const tileMask = config.tileMask
|
|
147
|
+
const target = config.target
|
|
148
|
+
|
|
149
|
+
const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
|
|
144
150
|
let changed = false
|
|
145
151
|
|
|
146
152
|
forEachLinePoint(x0, y0, x1, y1, (px, py) => {
|
|
@@ -152,15 +158,15 @@ export class PaintBuffer {
|
|
|
152
158
|
topLeftY,
|
|
153
159
|
bW,
|
|
154
160
|
bH,
|
|
155
|
-
target.
|
|
156
|
-
target.
|
|
161
|
+
target.w,
|
|
162
|
+
target.h,
|
|
157
163
|
scratch,
|
|
158
164
|
)
|
|
159
165
|
|
|
160
166
|
if (scratch.w <= 0 || scratch.h <= 0) return
|
|
161
167
|
|
|
162
|
-
|
|
163
|
-
const d32 = tile.
|
|
168
|
+
eachTileInBounds(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
|
|
169
|
+
const d32 = tile.data
|
|
164
170
|
let tileChanged = false
|
|
165
171
|
|
|
166
172
|
for (let i = 0; i < bH_t; i++) {
|
|
@@ -187,26 +193,44 @@ export class PaintBuffer {
|
|
|
187
193
|
return changed
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
|
|
196
|
+
paintRect(
|
|
197
|
+
color: Color32,
|
|
198
|
+
brushWidth: number,
|
|
199
|
+
brushHeight: number,
|
|
200
|
+
x: number,
|
|
201
|
+
y: number,
|
|
202
|
+
): boolean
|
|
203
|
+
paintRect(
|
|
204
|
+
color: Color32,
|
|
205
|
+
brushWidth: number,
|
|
206
|
+
brushHeight: number,
|
|
207
|
+
startX: number,
|
|
208
|
+
startY: number,
|
|
209
|
+
endX: number,
|
|
210
|
+
endY: number,
|
|
211
|
+
): boolean
|
|
212
|
+
paintRect(
|
|
191
213
|
color: Color32,
|
|
192
214
|
brushWidth: number,
|
|
193
215
|
brushHeight: number,
|
|
194
216
|
x0: number,
|
|
195
217
|
y0: number,
|
|
196
|
-
x1: number,
|
|
197
|
-
y1: number,
|
|
218
|
+
x1: number = x0,
|
|
219
|
+
y1: number = y0,
|
|
198
220
|
): boolean {
|
|
199
221
|
const alphaIsZero = (color >>> 24) === 0
|
|
200
222
|
if (alphaIsZero) return false
|
|
201
223
|
|
|
224
|
+
const scratch = this.scratchBounds
|
|
225
|
+
const lookup = this.lookup
|
|
226
|
+
const tilePool = this.tilePool
|
|
202
227
|
const config = this.config
|
|
203
228
|
const tileShift = config.tileShift
|
|
204
229
|
const tileMask = config.tileMask
|
|
205
230
|
const target = config.target
|
|
206
|
-
const scratch = this.scratchBounds
|
|
207
231
|
|
|
208
|
-
const centerOffsetX =
|
|
209
|
-
const centerOffsetY =
|
|
232
|
+
const centerOffsetX = _macro_paintRectCenterOffset(brushWidth)
|
|
233
|
+
const centerOffsetY = _macro_paintRectCenterOffset(brushHeight)
|
|
210
234
|
|
|
211
235
|
let changed = false
|
|
212
236
|
|
|
@@ -224,17 +248,15 @@ export class PaintBuffer {
|
|
|
224
248
|
topLeftY,
|
|
225
249
|
brushWidth,
|
|
226
250
|
brushHeight,
|
|
227
|
-
target.
|
|
228
|
-
target.
|
|
251
|
+
target.w,
|
|
252
|
+
target.h,
|
|
229
253
|
scratch,
|
|
230
254
|
)
|
|
231
255
|
|
|
232
256
|
if (scratch.w <= 0 || scratch.h <= 0) return
|
|
233
257
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
(tile, bX, bY, bW_t, bH_t) => {
|
|
237
|
-
const d32 = tile.data32
|
|
258
|
+
eachTileInBounds(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
|
|
259
|
+
const d32 = tile.data
|
|
238
260
|
let tileChanged = false
|
|
239
261
|
|
|
240
262
|
for (let i = 0; i < bH_t; i++) {
|
|
@@ -263,6 +285,55 @@ export class PaintBuffer {
|
|
|
263
285
|
return changed
|
|
264
286
|
}
|
|
265
287
|
|
|
288
|
+
private opts = {
|
|
289
|
+
alpha: 255,
|
|
290
|
+
blendFn: sourceOverPerfect,
|
|
291
|
+
x: 0,
|
|
292
|
+
y: 0,
|
|
293
|
+
w: 0,
|
|
294
|
+
h: 0,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
commit(
|
|
298
|
+
accumulator: PixelAccumulator,
|
|
299
|
+
alpha = 255,
|
|
300
|
+
blendFn = sourceOverPerfect,
|
|
301
|
+
) {
|
|
302
|
+
const tileShift = this.config.tileShift
|
|
303
|
+
const lookup = this.lookup
|
|
304
|
+
const opts = this.opts
|
|
305
|
+
|
|
306
|
+
const blendPixelDataFn = this.blendPixelDataFn
|
|
307
|
+
opts.alpha = alpha
|
|
308
|
+
opts.blendFn = blendFn
|
|
309
|
+
|
|
310
|
+
for (let i = 0; i < lookup.length; i++) {
|
|
311
|
+
const tile = lookup[i]
|
|
312
|
+
|
|
313
|
+
if (tile) {
|
|
314
|
+
const didChange = accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty)
|
|
315
|
+
|
|
316
|
+
const dx = tile.tx << tileShift
|
|
317
|
+
const dy = tile.ty << tileShift
|
|
318
|
+
|
|
319
|
+
opts.x = dx
|
|
320
|
+
opts.y = dy
|
|
321
|
+
opts.w = tile.w
|
|
322
|
+
opts.h = tile.h
|
|
323
|
+
|
|
324
|
+
didChange(
|
|
325
|
+
blendPixelDataFn(
|
|
326
|
+
this.config.target,
|
|
327
|
+
tile,
|
|
328
|
+
opts,
|
|
329
|
+
),
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
this.clear()
|
|
335
|
+
}
|
|
336
|
+
|
|
266
337
|
clear(): void {
|
|
267
338
|
this.tilePool.releaseTiles(this.lookup)
|
|
268
339
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { CANVAS_CTX_FAILED } from '
|
|
2
|
-
import type {
|
|
1
|
+
import { CANVAS_CTX_FAILED } from '../Internal/_errors'
|
|
2
|
+
import type { ColorPaintBuffer } from './ColorPaintBuffer'
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type ColorPaintBufferCanvasRenderer = ReturnType<typeof makeColorPaintBufferCanvasRenderer>
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
*
|
|
8
|
+
* @param paintBuffer
|
|
8
9
|
* @param offscreenCanvasClass - @internal
|
|
9
10
|
*/
|
|
10
|
-
export function
|
|
11
|
-
paintBuffer:
|
|
11
|
+
export function makeColorPaintBufferCanvasRenderer(
|
|
12
|
+
paintBuffer: ColorPaintBuffer,
|
|
12
13
|
offscreenCanvasClass = OffscreenCanvas,
|
|
13
14
|
) {
|
|
14
15
|
const config = paintBuffer.config
|