pixel-data-js 0.27.0 → 0.29.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 +2355 -1124
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +558 -424
- package/dist/index.prod.js +2304 -1115
- 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/_constants.ts +3 -0
- 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 +283 -0
- package/src/Paint/BinaryMaskPaintBuffer.ts +198 -0
- package/src/Paint/{PaintBuffer.ts → ColorPaintBuffer.ts} +95 -77
- package/src/Paint/Commit/AlphaMaskPaintBufferCommitter.ts +26 -0
- package/src/Paint/Commit/AlphaMaskPaintBufferManager.ts +34 -0
- package/src/Paint/Commit/BinaryMaskPaintBufferCommitter.ts +26 -0
- package/src/Paint/Commit/BinaryMaskPaintBufferManager.ts +31 -0
- package/src/Paint/Commit/ColorPaintBufferCommitter.ts +23 -0
- package/src/Paint/Commit/ColorPaintBufferManager.ts +34 -0
- package/src/Paint/Commit/commitColorPaintBuffer.ts +55 -0
- package/src/Paint/Commit/commitMaskPaintBuffer.ts +78 -0
- package/src/Paint/Render/AlphaMaskPaintBufferCanvasRenderer.ts +78 -0
- package/src/Paint/Render/BinaryMaskPaintBufferCanvasRenderer.ts +67 -0
- package/src/Paint/{PaintBufferCanvasRenderer.ts → Render/ColorPaintBufferCanvasRenderer.ts} +13 -14
- package/src/Paint/Render/PaintCursorRenderer.ts +118 -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 +57 -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,67 @@
|
|
|
1
|
+
import type { Color32 } from '../../_types'
|
|
2
|
+
import type { CanvasObjectFactory } from '../../Canvas/_canvas-types'
|
|
3
|
+
import { DEFAULT_CANVAS_FACTORY } from '../../Internal/_constants'
|
|
4
|
+
import { CANVAS_CTX_FAILED } from '../../Internal/_errors'
|
|
5
|
+
import { makePixelData } from '../../PixelData/PixelData'
|
|
6
|
+
import type { BinaryMaskPaintBuffer } from '../BinaryMaskPaintBuffer'
|
|
7
|
+
|
|
8
|
+
export type BinaryMaskPaintBufferCanvasRenderer = ReturnType<typeof makeBinaryMaskPaintBufferCanvasRenderer>
|
|
9
|
+
|
|
10
|
+
export function makeBinaryMaskPaintBufferCanvasRenderer(
|
|
11
|
+
paintBuffer: BinaryMaskPaintBuffer,
|
|
12
|
+
canvasFactory: CanvasObjectFactory<any> = DEFAULT_CANVAS_FACTORY,
|
|
13
|
+
) {
|
|
14
|
+
const config = paintBuffer.config
|
|
15
|
+
const tileSize = config.tileSize
|
|
16
|
+
const tileShift = config.tileShift
|
|
17
|
+
const tileArea = config.tileArea
|
|
18
|
+
const lookup = paintBuffer.lookup
|
|
19
|
+
|
|
20
|
+
const canvas = canvasFactory(tileSize, tileSize)
|
|
21
|
+
const ctx = canvas.getContext('2d')
|
|
22
|
+
if (!ctx) throw new Error(CANVAS_CTX_FAILED)
|
|
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 | OffscreenCanvasRenderingContext2D,
|
|
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,27 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
paintBuffer: PaintBuffer,
|
|
12
|
-
offscreenCanvasClass = OffscreenCanvas,
|
|
1
|
+
import type { CanvasObjectFactory } from '../../Canvas/_canvas-types'
|
|
2
|
+
import { DEFAULT_CANVAS_FACTORY } from '../../Internal/_constants'
|
|
3
|
+
import { CANVAS_CTX_FAILED } from '../../Internal/_errors'
|
|
4
|
+
import type { ColorPaintBuffer } from '../ColorPaintBuffer'
|
|
5
|
+
|
|
6
|
+
export type ColorPaintBufferCanvasRenderer = ReturnType<typeof makeColorPaintBufferCanvasRenderer>
|
|
7
|
+
|
|
8
|
+
export function makeColorPaintBufferCanvasRenderer(
|
|
9
|
+
paintBuffer: ColorPaintBuffer,
|
|
10
|
+
canvasFactory: CanvasObjectFactory<any> = DEFAULT_CANVAS_FACTORY,
|
|
13
11
|
) {
|
|
14
12
|
const config = paintBuffer.config
|
|
15
13
|
const tileSize = config.tileSize
|
|
16
14
|
const tileShift = config.tileShift
|
|
17
15
|
const lookup = paintBuffer.lookup
|
|
18
|
-
|
|
16
|
+
|
|
17
|
+
const canvas = canvasFactory(tileSize, tileSize)
|
|
19
18
|
const ctx = canvas.getContext('2d')
|
|
20
19
|
if (!ctx) throw new Error(CANVAS_CTX_FAILED)
|
|
21
20
|
ctx.imageSmoothingEnabled = false
|
|
22
21
|
|
|
23
22
|
return function drawPaintBuffer(
|
|
24
|
-
targetCtx: CanvasRenderingContext2D,
|
|
23
|
+
targetCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
|
25
24
|
alpha = 255,
|
|
26
25
|
compOperation: GlobalCompositeOperation = 'source-over',
|
|
27
26
|
): void {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type Color32 } from '../../_types'
|
|
2
|
+
import type { ReusableCanvasFactory } from '../../Canvas/_canvas-types'
|
|
3
|
+
import { makeReusableOffscreenCanvas } from '../../Canvas/ReusableCanvas'
|
|
4
|
+
import { packColor } from '../../color'
|
|
5
|
+
import { _macro_paintRectCenterOffset } from '../../Internal/macros'
|
|
6
|
+
import { type BinaryMask, MaskType } from '../../Mask/_mask-types'
|
|
7
|
+
import { makeBinaryMaskFromAlphaMask } from '../../Mask/BinaryMask/makeBinaryMaskFromAlphaMask'
|
|
8
|
+
import { makeBinaryMaskOutline } from '../../Mask/BinaryMask/makeBinaryMaskOutline'
|
|
9
|
+
import { makeCircleBinaryMaskOutline } from '../../Mask/BinaryMask/makeCircleBinaryMaskOutline'
|
|
10
|
+
import { makeRectBinaryMaskOutline } from '../../Mask/BinaryMask/makeRectBinaryMaskOutline'
|
|
11
|
+
import { fillPixelDataBinaryMask } from '../../PixelData/fillPixelDataBinaryMask'
|
|
12
|
+
import { makeReusablePixelData } from '../../PixelData/ReusablePixelData'
|
|
13
|
+
import type { Rect } from '../../Rect/_rect-types'
|
|
14
|
+
import { type PaintMask, PaintMaskOutline } from '../_paint-types'
|
|
15
|
+
|
|
16
|
+
export type PaintCursorRenderer = ReturnType<typeof makePaintCursorRenderer>
|
|
17
|
+
|
|
18
|
+
export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenCanvas = OffscreenCanvas>(
|
|
19
|
+
reusableCanvasFactory?: () => ReusableCanvasFactory<T>,
|
|
20
|
+
) {
|
|
21
|
+
const factory = (reusableCanvasFactory ?? makeReusableOffscreenCanvas) as unknown as () => ReusableCanvasFactory<T>
|
|
22
|
+
const updateBuffer = factory()
|
|
23
|
+
const { canvas, ctx } = updateBuffer(1, 1)
|
|
24
|
+
|
|
25
|
+
const getPixelData = makeReusablePixelData()
|
|
26
|
+
|
|
27
|
+
let _color = packColor(0, 255, 255, 255)
|
|
28
|
+
let _scale = 1
|
|
29
|
+
|
|
30
|
+
let currentMask: PaintMask = {
|
|
31
|
+
type: MaskType.BINARY,
|
|
32
|
+
outlineType: PaintMaskOutline.RECT,
|
|
33
|
+
w: 1,
|
|
34
|
+
h: 1,
|
|
35
|
+
centerOffsetX: _macro_paintRectCenterOffset(10),
|
|
36
|
+
centerOffsetY: _macro_paintRectCenterOffset(10),
|
|
37
|
+
} as PaintMask
|
|
38
|
+
|
|
39
|
+
let outline: BinaryMask
|
|
40
|
+
|
|
41
|
+
function update(paintMask?: PaintMask, scale?: number, color?: Color32, alphaThreshold = 127) {
|
|
42
|
+
currentMask = paintMask ?? currentMask
|
|
43
|
+
|
|
44
|
+
_scale = scale ?? _scale
|
|
45
|
+
_color = color ?? _color
|
|
46
|
+
|
|
47
|
+
updateBuffer(
|
|
48
|
+
currentMask.w * _scale + 2 * _scale,
|
|
49
|
+
currentMask.h * _scale + 2 * _scale,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if (currentMask.type === MaskType.BINARY) {
|
|
53
|
+
if (currentMask.outlineType === PaintMaskOutline.CIRCLE) {
|
|
54
|
+
outline = makeCircleBinaryMaskOutline(currentMask.w, _scale)
|
|
55
|
+
} else if (currentMask.outlineType === PaintMaskOutline.RECT) {
|
|
56
|
+
outline = makeRectBinaryMaskOutline(currentMask.w, currentMask.h, _scale)
|
|
57
|
+
} else if (currentMask.outlineType === PaintMaskOutline.MASKED) {
|
|
58
|
+
outline = makeBinaryMaskOutline(currentMask, _scale)
|
|
59
|
+
}
|
|
60
|
+
} else if (currentMask.type === MaskType.ALPHA) {
|
|
61
|
+
const mask = makeBinaryMaskFromAlphaMask(currentMask, alphaThreshold)
|
|
62
|
+
outline = makeBinaryMaskOutline(mask, _scale)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const pixelData = getPixelData(outline.w, outline.h)
|
|
66
|
+
fillPixelDataBinaryMask(pixelData, _color, outline)
|
|
67
|
+
ctx.putImageData(pixelData.imageData, 0, 0)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const boundsScratch = { x: 0, y: 0, w: 0, h: 0 }
|
|
71
|
+
|
|
72
|
+
function getBounds(centerX: number, centerY: number): Rect {
|
|
73
|
+
boundsScratch.x = centerX + currentMask.centerOffsetX
|
|
74
|
+
boundsScratch.y = centerY + currentMask.centerOffsetY
|
|
75
|
+
boundsScratch.w = currentMask.w
|
|
76
|
+
boundsScratch.h = currentMask.h
|
|
77
|
+
|
|
78
|
+
return boundsScratch
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const boundsScaledScratch = { x: 0, y: 0, w: 0, h: 0 }
|
|
82
|
+
|
|
83
|
+
function getOutlineBoundsScaled(centerX: number, centerY: number): Rect {
|
|
84
|
+
boundsScaledScratch.x = centerX * _scale + currentMask.centerOffsetX * _scale - 1
|
|
85
|
+
boundsScaledScratch.y = centerY * _scale + currentMask.centerOffsetY * _scale - 1
|
|
86
|
+
boundsScaledScratch.w = currentMask.w * _scale
|
|
87
|
+
boundsScaledScratch.h = currentMask.h * _scale
|
|
88
|
+
|
|
89
|
+
return boundsScaledScratch
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function draw(
|
|
93
|
+
drawCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
|
94
|
+
centerX: number,
|
|
95
|
+
centerY: number,
|
|
96
|
+
) {
|
|
97
|
+
const dx = centerX * _scale + currentMask.centerOffsetX * _scale - 1
|
|
98
|
+
const dy = centerY * _scale + currentMask.centerOffsetY * _scale - 1
|
|
99
|
+
|
|
100
|
+
drawCtx.drawImage(canvas, Math.floor(dx), Math.floor(dy))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getSettings() {
|
|
104
|
+
return {
|
|
105
|
+
color: _color,
|
|
106
|
+
scale: _scale,
|
|
107
|
+
currentMask,
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
update,
|
|
113
|
+
getBounds,
|
|
114
|
+
getBoundsScaled: getOutlineBoundsScaled,
|
|
115
|
+
draw,
|
|
116
|
+
getSettings,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AlphaMask, BinaryMask } from '../Mask/_mask-types'
|
|
2
|
+
|
|
3
|
+
export enum PaintMaskOutline {
|
|
4
|
+
MASKED,
|
|
5
|
+
CIRCLE,
|
|
6
|
+
RECT,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface BasePaintMask<T extends PaintMaskOutline = PaintMaskOutline> {
|
|
10
|
+
readonly outlineType: T
|
|
11
|
+
readonly centerOffsetX: number
|
|
12
|
+
readonly centerOffsetY: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PaintAlphaMask<T extends PaintMaskOutline = PaintMaskOutline> extends BasePaintMask<T>, AlphaMask {
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PaintBinaryMask<T extends PaintMaskOutline = PaintMaskOutline> extends BasePaintMask<T>, BinaryMask {
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PaintMask = PaintAlphaMask<any> | PaintBinaryMask<any>
|
|
22
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { PixelEngineConfig } from '../History/PixelEngineConfig'
|
|
2
|
+
import type { Rect } from '../Rect/_rect-types'
|
|
3
|
+
import type { Tile } from '../Tile/_tile-types'
|
|
4
|
+
import type { TilePool } from '../Tile/TilePool'
|
|
5
|
+
|
|
6
|
+
export function eachTileInBounds<T extends Tile>(
|
|
7
|
+
config: PixelEngineConfig,
|
|
8
|
+
lookup: (T | undefined)[],
|
|
9
|
+
tilePool: TilePool<T>,
|
|
10
|
+
bounds: Rect,
|
|
11
|
+
callback: (tile: T, bX: number, bY: number, bW: number, bH: number) => void,
|
|
12
|
+
): void {
|
|
13
|
+
const { tileShift, targetColumns, targetRows, tileSize } = config
|
|
14
|
+
|
|
15
|
+
const x1 = Math.max(0, bounds.x >> tileShift)
|
|
16
|
+
const y1 = Math.max(0, bounds.y >> tileShift)
|
|
17
|
+
const x2 = Math.min(targetColumns - 1, (bounds.x + bounds.w - 1) >> tileShift)
|
|
18
|
+
const y2 = Math.min(targetRows - 1, (bounds.y + bounds.h - 1) >> tileShift)
|
|
19
|
+
|
|
20
|
+
if (x1 > x2 || y1 > y2) return
|
|
21
|
+
|
|
22
|
+
for (let ty = y1; ty <= y2; ty++) {
|
|
23
|
+
const rowOffset = ty * targetColumns
|
|
24
|
+
const tileTop = ty << tileShift
|
|
25
|
+
|
|
26
|
+
for (let tx = x1; tx <= x2; tx++) {
|
|
27
|
+
const id = rowOffset + tx
|
|
28
|
+
const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty))
|
|
29
|
+
const tileLeft = tx << tileShift
|
|
30
|
+
|
|
31
|
+
const startX = bounds.x > tileLeft ? bounds.x : tileLeft
|
|
32
|
+
const startY = bounds.y > tileTop ? bounds.y : tileTop
|
|
33
|
+
|
|
34
|
+
const maskEndX = bounds.x + bounds.w
|
|
35
|
+
const tileEndX = tileLeft + tileSize
|
|
36
|
+
const endX = maskEndX < tileEndX ? maskEndX : tileEndX
|
|
37
|
+
|
|
38
|
+
const maskEndY = bounds.y + bounds.h
|
|
39
|
+
const tileEndY = tileTop + tileSize
|
|
40
|
+
const endY = maskEndY < tileEndY ? maskEndY : tileEndY
|
|
41
|
+
|
|
42
|
+
callback(tile, startX, startY, endX - startX, endY - startY)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { _macro_paintCircleCenterOffset } from '../Internal/macros'
|
|
2
|
+
import { MaskType } from '../Mask/_mask-types'
|
|
3
|
+
import { type PaintAlphaMask, type PaintBinaryMask, PaintMaskOutline } from './_paint-types'
|
|
4
|
+
|
|
5
|
+
export function makeCirclePaintAlphaMask(size: number, fallOff: (d: number) => number = (d) => d): PaintAlphaMask {
|
|
6
|
+
const area = size * size
|
|
7
|
+
const data = new Uint8Array(area)
|
|
8
|
+
const radius = size / 2
|
|
9
|
+
const invR = 1 / radius
|
|
10
|
+
|
|
11
|
+
const centerOffset = _macro_paintCircleCenterOffset(radius)
|
|
12
|
+
|
|
13
|
+
for (let y = 0; y < size; y++) {
|
|
14
|
+
const rowOffset = y * size
|
|
15
|
+
const dy = y - radius + 0.5
|
|
16
|
+
const dy2 = dy * dy
|
|
17
|
+
|
|
18
|
+
for (let x = 0; x < size; x++) {
|
|
19
|
+
const dx = x - radius + 0.5
|
|
20
|
+
const distSqr = dx * dx + dy2
|
|
21
|
+
|
|
22
|
+
if (distSqr <= (radius * radius)) {
|
|
23
|
+
const dist = Math.sqrt(distSqr) * invR
|
|
24
|
+
|
|
25
|
+
// Pass 1.0 at center, 0.0 at edge
|
|
26
|
+
const strength = fallOff(1 - dist)
|
|
27
|
+
if (strength > 0) {
|
|
28
|
+
const intensity = (strength * 255) | 0
|
|
29
|
+
data[rowOffset + x] = Math.max(0, Math.min(255, intensity))
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
type: MaskType.ALPHA,
|
|
37
|
+
outlineType: PaintMaskOutline.CIRCLE,
|
|
38
|
+
data,
|
|
39
|
+
w: size,
|
|
40
|
+
h: size,
|
|
41
|
+
centerOffsetX: centerOffset,
|
|
42
|
+
centerOffsetY: centerOffset,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function makeCirclePaintBinaryMask(size: number): PaintBinaryMask {
|
|
47
|
+
const area = size * size
|
|
48
|
+
const data = new Uint8Array(area)
|
|
49
|
+
const radius = size / 2
|
|
50
|
+
const r2 = radius * radius
|
|
51
|
+
|
|
52
|
+
for (let y = 0; y < size; y++) {
|
|
53
|
+
for (let x = 0; x < size; x++) {
|
|
54
|
+
const dx = x - radius + 0.5
|
|
55
|
+
const dy = y - radius + 0.5
|
|
56
|
+
const distSqr = dx * dx + dy * dy
|
|
57
|
+
if (distSqr <= r2) {
|
|
58
|
+
data[y * size + x] = 1
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const centerOffset = _macro_paintCircleCenterOffset(radius)
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
type: MaskType.BINARY,
|
|
67
|
+
outlineType: PaintMaskOutline.CIRCLE,
|
|
68
|
+
w: size,
|
|
69
|
+
h: size,
|
|
70
|
+
data,
|
|
71
|
+
centerOffsetX: centerOffset,
|
|
72
|
+
centerOffsetY: centerOffset,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { _macro_halfAndFloor } from '../Internal/macros'
|
|
2
|
+
import { type AlphaMask, type BinaryMask, MaskType } from '../Mask/_mask-types'
|
|
3
|
+
import { type PaintAlphaMask, type PaintBinaryMask, PaintMaskOutline } from './_paint-types'
|
|
3
4
|
|
|
4
5
|
export function makePaintBinaryMask(
|
|
5
6
|
mask: BinaryMask,
|
|
6
7
|
): PaintBinaryMask {
|
|
7
8
|
return {
|
|
8
9
|
type: MaskType.BINARY,
|
|
10
|
+
outlineType: PaintMaskOutline.MASKED,
|
|
9
11
|
data: mask.data,
|
|
10
12
|
w: mask.w,
|
|
11
13
|
h: mask.h,
|
|
@@ -19,6 +21,7 @@ export function makePaintAlphaMask(
|
|
|
19
21
|
): PaintAlphaMask {
|
|
20
22
|
return {
|
|
21
23
|
type: MaskType.ALPHA,
|
|
24
|
+
outlineType: PaintMaskOutline.MASKED,
|
|
22
25
|
data: mask.data,
|
|
23
26
|
w: mask.w,
|
|
24
27
|
h: mask.h,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { _macro_halfAndFloor } from '../Internal/macros'
|
|
2
|
+
import { MaskType } from '../Mask/_mask-types'
|
|
3
|
+
import { type PaintAlphaMask, PaintMaskOutline } from './_paint-types'
|
|
3
4
|
|
|
4
5
|
export function makeRectFalloffPaintAlphaMask(
|
|
5
6
|
width: number,
|
|
@@ -38,6 +39,7 @@ export function makeRectFalloffPaintAlphaMask(
|
|
|
38
39
|
|
|
39
40
|
return {
|
|
40
41
|
type: MaskType.ALPHA,
|
|
42
|
+
outlineType: PaintMaskOutline.RECT,
|
|
41
43
|
data: data,
|
|
42
44
|
w: width,
|
|
43
45
|
h: height,
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
import type { ImageDataLike
|
|
2
|
-
import {
|
|
1
|
+
import type { ImageDataLike } from '../ImageData/_ImageData-types'
|
|
2
|
+
import { _macro_imageDataToUint32Array } from '../ImageData/imageDataToUint32Array'
|
|
3
|
+
import type { PixelData } from './_pixelData-types'
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
constructor(imageData: T) {
|
|
11
|
-
this.data32 = imageDataToUInt32Array(imageData)
|
|
12
|
-
this.imageData = imageData
|
|
13
|
-
this.width = imageData.width
|
|
14
|
-
this.height = imageData.height
|
|
5
|
+
export function makePixelData<T extends ImageDataLike = ImageData>(imageData: T): PixelData<T> {
|
|
6
|
+
return {
|
|
7
|
+
data: _macro_imageDataToUint32Array(imageData),
|
|
8
|
+
imageData,
|
|
9
|
+
w: imageData.width,
|
|
10
|
+
h: imageData.height,
|
|
15
11
|
}
|
|
12
|
+
}
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
14
|
+
export function setPixelData(target: PixelData, imageData: ImageData) {
|
|
15
|
+
;(target as any).data = _macro_imageDataToUint32Array(imageData)
|
|
16
|
+
;(target as any).imageData = imageData
|
|
17
|
+
;(target as any).w = imageData.width
|
|
18
|
+
;(target as any).h = imageData.height
|
|
23
19
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PixelData } from './_pixelData-types'
|
|
2
|
+
import { setPixelData } from './PixelData'
|
|
3
|
+
|
|
4
|
+
export type ReusablePixelData = ReturnType<typeof makeReusablePixelData>
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a factory function that manages a single, reusable PixelData instance.
|
|
8
|
+
* This is used to minimize garbage collection overhead by recycling the
|
|
9
|
+
* underlying pixel buffer across multiple operations.
|
|
10
|
+
* @returns A function that takes width and height and returns a pooled PixelData instance.
|
|
11
|
+
*/
|
|
12
|
+
export function makeReusablePixelData() {
|
|
13
|
+
const pixelData = {
|
|
14
|
+
w: 0,
|
|
15
|
+
h: 0,
|
|
16
|
+
data: null as unknown as Uint32Array,
|
|
17
|
+
imageData: null as unknown as ImageData,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Retrieves a PixelData instance of the requested dimensions.
|
|
22
|
+
* If the requested dimensions differ from the cached instance, a new one is allocated.
|
|
23
|
+
* @param width - The desired width in pixels.
|
|
24
|
+
* @param height - The desired height in pixels.
|
|
25
|
+
* @returns The cached PixelData object.
|
|
26
|
+
*/
|
|
27
|
+
return function getReusablePixelData(width: number, height: number): PixelData {
|
|
28
|
+
if (pixelData.w !== width || pixelData.h !== height) {
|
|
29
|
+
setPixelData(pixelData, new ImageData(width, height))
|
|
30
|
+
} else {
|
|
31
|
+
pixelData.data.fill(0)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return pixelData
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ImageDataLike } from '../ImageData/_ImageData-types'
|
|
2
|
+
|
|
3
|
+
export interface PixelData32 {
|
|
4
|
+
readonly data: Uint32Array
|
|
5
|
+
readonly w: number
|
|
6
|
+
readonly h: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface MutablePixelData32 {
|
|
10
|
+
data: Uint32Array
|
|
11
|
+
w: number
|
|
12
|
+
h: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PixelData<T extends ImageDataLike = ImageData> extends PixelData32 {
|
|
16
|
+
readonly imageData: T
|
|
17
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ApplyMaskToPixelDataOptions } from '../_types'
|
|
2
|
+
import type { AlphaMask } from '../Mask/_mask-types'
|
|
3
|
+
import type { PixelData32 } from './_pixelData-types'
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Directly applies a mask to a region of PixelData,
|
|
@@ -6,14 +8,14 @@ import { type AlphaMask, type ApplyMaskToPixelDataOptions, type Color32, type IP
|
|
|
6
8
|
* @returns true if any pixels were actually modified.
|
|
7
9
|
*/
|
|
8
10
|
export function applyAlphaMaskToPixelData(
|
|
9
|
-
target:
|
|
11
|
+
target: PixelData32,
|
|
10
12
|
mask: AlphaMask,
|
|
11
13
|
opts?: ApplyMaskToPixelDataOptions,
|
|
12
14
|
): boolean {
|
|
13
15
|
const targetX = opts?.x ?? 0
|
|
14
16
|
const targetY = opts?.y ?? 0
|
|
15
|
-
const width = opts?.w ?? target.
|
|
16
|
-
const height = opts?.h ?? target.
|
|
17
|
+
const width = opts?.w ?? target.w
|
|
18
|
+
const height = opts?.h ?? target.h
|
|
17
19
|
const globalAlpha = opts?.alpha ?? 255
|
|
18
20
|
const mx = opts?.mx ?? 0
|
|
19
21
|
const my = opts?.my ?? 0
|
|
@@ -37,8 +39,8 @@ export function applyAlphaMaskToPixelData(
|
|
|
37
39
|
y = 0
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
w = Math.min(w, target.
|
|
41
|
-
h = Math.min(h, target.
|
|
42
|
+
w = Math.min(w, target.w - x)
|
|
43
|
+
h = Math.min(h, target.h - y)
|
|
42
44
|
|
|
43
45
|
if (w <= 0) return false
|
|
44
46
|
if (h <= 0) return false
|
|
@@ -70,8 +72,8 @@ export function applyAlphaMaskToPixelData(
|
|
|
70
72
|
const xShift = sX0 - startX
|
|
71
73
|
const yShift = sY0 - startY
|
|
72
74
|
|
|
73
|
-
const dst32 = target.
|
|
74
|
-
const dw = target.
|
|
75
|
+
const dst32 = target.data
|
|
76
|
+
const dw = target.w
|
|
75
77
|
const dStride = dw - finalW
|
|
76
78
|
const mStride = mPitch - finalW
|
|
77
79
|
const maskData = mask.data
|
|
@@ -80,51 +82,86 @@ export function applyAlphaMaskToPixelData(
|
|
|
80
82
|
let mIdx = sY0 * mPitch + sX0
|
|
81
83
|
|
|
82
84
|
let didChange = false
|
|
83
|
-
|
|
84
|
-
for (let
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const effectiveM = invertMask ? 255 - mVal : mVal
|
|
88
|
-
|
|
89
|
-
let weight = 0
|
|
90
|
-
|
|
91
|
-
if (effectiveM === 0) {
|
|
92
|
-
weight = 0
|
|
93
|
-
} else if (effectiveM === 255) {
|
|
94
|
-
weight = globalAlpha
|
|
95
|
-
} else if (globalAlpha === 255) {
|
|
96
|
-
weight = effectiveM
|
|
97
|
-
} else {
|
|
98
|
-
weight = (effectiveM * globalAlpha + 128) >> 8
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (weight === 0) {
|
|
102
|
-
// Clear alpha channel
|
|
103
|
-
dst32[dIdx] = (dst32[dIdx] & 0x00ffffff) >>> 0
|
|
104
|
-
didChange = true
|
|
105
|
-
} else if (weight !== 255) {
|
|
106
|
-
// Merge alpha channel
|
|
107
|
-
const d = dst32[dIdx]
|
|
108
|
-
const da = d >>> 24
|
|
85
|
+
if (invertMask) {
|
|
86
|
+
for (let iy = 0; iy < finalH; iy++) {
|
|
87
|
+
for (let ix = 0; ix < finalW; ix++) {
|
|
88
|
+
const effectiveM = 255 - maskData[mIdx]
|
|
109
89
|
|
|
110
|
-
if (
|
|
111
|
-
const
|
|
90
|
+
if (effectiveM === 0) {
|
|
91
|
+
const current = dst32[dIdx]
|
|
92
|
+
const next = (current & 0x00ffffff) >>> 0
|
|
112
93
|
|
|
113
|
-
const current = dst32[dIdx] as Color32
|
|
114
|
-
const next = ((d & 0x00ffffff) | (finalAlpha << 24)) >>> 0
|
|
115
94
|
if (current !== next) {
|
|
116
95
|
dst32[dIdx] = next
|
|
117
96
|
didChange = true
|
|
118
97
|
}
|
|
98
|
+
} else {
|
|
99
|
+
const t1 = effectiveM * globalAlpha + 128
|
|
100
|
+
const weight = (t1 + (t1 >> 8)) >> 8
|
|
101
|
+
|
|
102
|
+
if (weight < 255) {
|
|
103
|
+
const current = dst32[dIdx]
|
|
104
|
+
const da = current >>> 24
|
|
105
|
+
|
|
106
|
+
if (da !== 0) {
|
|
107
|
+
const t2 = da * weight + 128
|
|
108
|
+
const finalAlpha = (t2 + (t2 >> 8)) >> 8
|
|
109
|
+
const next = ((current & 0x00ffffff) | (finalAlpha << 24)) >>> 0
|
|
110
|
+
|
|
111
|
+
if (current !== next) {
|
|
112
|
+
dst32[dIdx] = next
|
|
113
|
+
didChange = true
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
119
117
|
}
|
|
120
|
-
}
|
|
121
118
|
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
dIdx++
|
|
120
|
+
mIdx++
|
|
121
|
+
}
|
|
122
|
+
dIdx += dStride
|
|
123
|
+
mIdx += mStride
|
|
124
124
|
}
|
|
125
|
+
} else {
|
|
126
|
+
for (let iy = 0; iy < finalH; iy++) {
|
|
127
|
+
for (let ix = 0; ix < finalW; ix++) {
|
|
128
|
+
const effectiveM = maskData[mIdx]
|
|
129
|
+
|
|
130
|
+
if (effectiveM === 0) {
|
|
131
|
+
const current = dst32[dIdx]
|
|
132
|
+
const next = (current & 0x00ffffff) >>> 0
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
if (current !== next) {
|
|
135
|
+
dst32[dIdx] = next
|
|
136
|
+
didChange = true
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
const t1 = effectiveM * globalAlpha + 128
|
|
140
|
+
const weight = (t1 + (t1 >> 8)) >> 8
|
|
141
|
+
|
|
142
|
+
if (weight < 255) {
|
|
143
|
+
const current = dst32[dIdx]
|
|
144
|
+
const da = current >>> 24
|
|
145
|
+
|
|
146
|
+
if (da !== 0) {
|
|
147
|
+
const t2 = da * weight + 128
|
|
148
|
+
const finalAlpha = (t2 + (t2 >> 8)) >> 8
|
|
149
|
+
const next = ((current & 0x00ffffff) | (finalAlpha << 24)) >>> 0
|
|
150
|
+
|
|
151
|
+
if (current !== next) {
|
|
152
|
+
dst32[dIdx] = next
|
|
153
|
+
didChange = true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
dIdx++
|
|
160
|
+
mIdx++
|
|
161
|
+
}
|
|
162
|
+
dIdx += dStride
|
|
163
|
+
mIdx += mStride
|
|
164
|
+
}
|
|
128
165
|
}
|
|
129
166
|
return didChange
|
|
130
167
|
}
|