pixel-data-js 0.23.0 → 0.24.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/dist/index.dev.cjs +1024 -596
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +1010 -592
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +1024 -596
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +280 -165
- package/dist/index.prod.js +1010 -592
- package/dist/index.prod.js.map +1 -1
- package/package.json +3 -2
- package/src/Canvas/CanvasFrameRenderer.ts +57 -0
- package/src/Canvas/ReusableCanvas.ts +60 -11
- package/src/History/HistoryAction.ts +38 -0
- package/src/History/HistoryManager.ts +4 -8
- package/src/History/PixelAccumulator.ts +95 -80
- package/src/History/PixelEngineConfig.ts +18 -6
- package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +6 -6
- package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +6 -6
- package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +6 -5
- package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +22 -22
- package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +6 -5
- package/src/History/PixelMutator/mutatorApplyRectBrush.ts +19 -19
- package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +6 -4
- package/src/History/PixelMutator/mutatorApplyRectPencil.ts +20 -20
- package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +6 -4
- package/src/History/PixelMutator/mutatorBlendColor.ts +8 -5
- package/src/History/PixelMutator/mutatorBlendColorCircleMask.ts +71 -0
- package/src/History/PixelMutator/mutatorBlendPixel.ts +22 -26
- package/src/History/PixelMutator/mutatorBlendPixelData.ts +5 -3
- package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +5 -3
- package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +5 -3
- package/src/History/PixelMutator/mutatorClear.ts +7 -6
- package/src/History/PixelMutator/mutatorFill.ts +34 -9
- package/src/History/PixelMutator/mutatorFillBinaryMask.ts +4 -2
- package/src/History/PixelMutator/mutatorInvert.ts +8 -4
- package/src/History/PixelMutator.ts +4 -3
- package/src/History/PixelPatchTiles.ts +3 -15
- package/src/History/PixelWriter.ts +29 -33
- package/src/ImageData/ReusableImageData.ts +3 -5
- package/src/Mask/{CircleBrushAlphaMask.ts → CircleAlphaMask.ts} +2 -2
- package/src/Mask/{CircleBrushBinaryMask.ts → CircleBinaryMask.ts} +2 -2
- package/src/PixelData/PixelData.ts +1 -27
- package/src/PixelData/applyAlphaMaskToPixelData.ts +19 -9
- package/src/PixelData/applyBinaryMaskToPixelData.ts +24 -17
- package/src/PixelData/applyRectBrushToPixelData.ts +18 -5
- package/src/PixelData/blendColorPixelData.ts +31 -7
- package/src/PixelData/blendColorPixelDataAlphaMask.ts +16 -6
- package/src/PixelData/blendColorPixelDataBinaryMask.ts +16 -7
- package/src/PixelData/{applyCircleBrushToPixelData.ts → blendColorPixelDataCircleMask.ts} +11 -10
- package/src/PixelData/blendPixel.ts +47 -0
- package/src/PixelData/blendPixelData.ts +14 -4
- package/src/PixelData/blendPixelDataAlphaMask.ts +12 -4
- package/src/PixelData/blendPixelDataBinaryMask.ts +13 -4
- package/src/PixelData/blendPixelDataPaintBuffer.ts +37 -0
- package/src/PixelData/clearPixelData.ts +2 -2
- package/src/PixelData/fillPixelData.ts +26 -16
- package/src/PixelData/fillPixelDataBinaryMask.ts +12 -4
- package/src/PixelData/fillPixelDataFast.ts +94 -0
- package/src/PixelData/invertPixelData.ts +4 -2
- package/src/PixelTile/PaintBuffer.ts +122 -0
- package/src/PixelTile/PaintBufferRenderer.ts +40 -0
- package/src/PixelTile/PixelTile.ts +21 -0
- package/src/PixelTile/PixelTilePool.ts +63 -0
- package/src/_types.ts +9 -9
- package/src/index.ts +16 -6
- package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +0 -78
|
@@ -12,13 +12,14 @@ import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
|
|
|
12
12
|
* @param color - The solid color to apply.
|
|
13
13
|
* @param mask - The mask defining the per-pixel opacity of the target area.
|
|
14
14
|
* @param opts - Configuration options including placement coordinates, bounds, global alpha, and mask offsets.
|
|
15
|
+
* @returns true if any pixels were actually modified.
|
|
15
16
|
*/
|
|
16
17
|
export function blendColorPixelDataBinaryMask(
|
|
17
18
|
dst: IPixelData,
|
|
18
19
|
color: Color32,
|
|
19
20
|
mask: BinaryMask,
|
|
20
21
|
opts: ColorBlendMaskOptions = {},
|
|
21
|
-
) {
|
|
22
|
+
): boolean {
|
|
22
23
|
const targetX = opts.x ?? 0
|
|
23
24
|
const targetY = opts.y ?? 0
|
|
24
25
|
let w = opts.w ?? mask.w
|
|
@@ -29,12 +30,12 @@ export function blendColorPixelDataBinaryMask(
|
|
|
29
30
|
const my = opts.my ?? 0
|
|
30
31
|
const invertMask = opts.invertMask ?? false
|
|
31
32
|
|
|
32
|
-
if (globalAlpha === 0) return
|
|
33
|
+
if (globalAlpha === 0) return false
|
|
33
34
|
|
|
34
35
|
const baseSrcAlpha = (color >>> 24)
|
|
35
36
|
const isOverwrite = (blendFn as any).isOverwrite || false
|
|
36
37
|
|
|
37
|
-
if (baseSrcAlpha === 0 && !isOverwrite) return
|
|
38
|
+
if (baseSrcAlpha === 0 && !isOverwrite) return false
|
|
38
39
|
|
|
39
40
|
let x = targetX
|
|
40
41
|
let y = targetY
|
|
@@ -52,19 +53,18 @@ export function blendColorPixelDataBinaryMask(
|
|
|
52
53
|
const actualW = Math.min(w, dst.width - x)
|
|
53
54
|
const actualH = Math.min(h, dst.height - y)
|
|
54
55
|
|
|
55
|
-
if (actualW <= 0 || actualH <= 0) return
|
|
56
|
+
if (actualW <= 0 || actualH <= 0) return false
|
|
56
57
|
|
|
57
58
|
let baseColorWithGlobalAlpha = color
|
|
58
59
|
|
|
59
60
|
if (globalAlpha < 255) {
|
|
60
61
|
const a = (baseSrcAlpha * globalAlpha + 128) >> 8
|
|
61
|
-
if (a === 0 && !isOverwrite) return
|
|
62
|
+
if (a === 0 && !isOverwrite) return false
|
|
62
63
|
baseColorWithGlobalAlpha = ((color & 0x00ffffff) | (a << 24)) >>> 0 as Color32
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
const dx = (x - targetX) | 0
|
|
66
67
|
const dy = (y - targetY) | 0
|
|
67
|
-
|
|
68
68
|
const dst32 = dst.data32
|
|
69
69
|
const dw = dst.width
|
|
70
70
|
const mPitch = mask.w
|
|
@@ -75,6 +75,7 @@ export function blendColorPixelDataBinaryMask(
|
|
|
75
75
|
const dStride = (dw - actualW) | 0
|
|
76
76
|
const mStride = (mPitch - actualW) | 0
|
|
77
77
|
const skipVal = invertMask ? 1 : 0
|
|
78
|
+
let didChange = false
|
|
78
79
|
|
|
79
80
|
for (let iy = 0; iy < actualH; iy++) {
|
|
80
81
|
for (let ix = 0; ix < actualW; ix++) {
|
|
@@ -84,7 +85,13 @@ export function blendColorPixelDataBinaryMask(
|
|
|
84
85
|
continue
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
const current = dst32[dIdx] as Color32
|
|
89
|
+
const next = blendFn(baseColorWithGlobalAlpha, current)
|
|
90
|
+
|
|
91
|
+
if (current !== next) {
|
|
92
|
+
dst32[dIdx] = next
|
|
93
|
+
didChange = true
|
|
94
|
+
}
|
|
88
95
|
|
|
89
96
|
dIdx++
|
|
90
97
|
mIdx++
|
|
@@ -93,4 +100,6 @@ export function blendColorPixelDataBinaryMask(
|
|
|
93
100
|
dIdx += dStride
|
|
94
101
|
mIdx += mStride
|
|
95
102
|
}
|
|
103
|
+
|
|
104
|
+
return didChange
|
|
96
105
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type BlendColor32,
|
|
3
|
-
type
|
|
3
|
+
type CircleMask,
|
|
4
4
|
type Color32,
|
|
5
5
|
type ColorBlendMaskOptions,
|
|
6
6
|
type IPixelData,
|
|
@@ -13,7 +13,7 @@ import { blendColorPixelDataAlphaMask } from './blendColorPixelDataAlphaMask'
|
|
|
13
13
|
import { blendColorPixelDataBinaryMask } from './blendColorPixelDataBinaryMask'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Applies a circular
|
|
16
|
+
* Applies a circular mask to pixel data using a pre-calculated alpha mask.
|
|
17
17
|
*
|
|
18
18
|
* @param target The PixelData to modify.
|
|
19
19
|
* @param color The brush color.
|
|
@@ -25,17 +25,17 @@ import { blendColorPixelDataBinaryMask } from './blendColorPixelDataBinaryMask'
|
|
|
25
25
|
* @param scratchOptions
|
|
26
26
|
* @param bounds precalculated result from {@link getCircleBrushOrPencilBounds}
|
|
27
27
|
*/
|
|
28
|
-
export function
|
|
28
|
+
export function blendColorPixelDataCircleMask(
|
|
29
29
|
target: IPixelData,
|
|
30
30
|
color: Color32,
|
|
31
31
|
centerX: number,
|
|
32
32
|
centerY: number,
|
|
33
|
-
brush:
|
|
33
|
+
brush: CircleMask,
|
|
34
34
|
alpha = 255,
|
|
35
35
|
blendFn: BlendColor32 = sourceOverPerfect,
|
|
36
36
|
scratchOptions: ColorBlendMaskOptions = {},
|
|
37
37
|
bounds?: Rect,
|
|
38
|
-
):
|
|
38
|
+
): boolean {
|
|
39
39
|
const b = bounds ?? getCircleBrushOrPencilBounds(
|
|
40
40
|
centerX,
|
|
41
41
|
centerY,
|
|
@@ -44,7 +44,7 @@ export function applyCircleBrushToPixelData(
|
|
|
44
44
|
target.height,
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
if (b.w <= 0 || b.h <= 0) return
|
|
47
|
+
if (b.w <= 0 || b.h <= 0) return false
|
|
48
48
|
|
|
49
49
|
const unclippedStartX = Math.floor(centerX + brush.minOffset)
|
|
50
50
|
const unclippedStartY = Math.floor(centerY + brush.minOffset)
|
|
@@ -59,9 +59,8 @@ export function applyCircleBrushToPixelData(
|
|
|
59
59
|
const ih = ib - iy
|
|
60
60
|
|
|
61
61
|
// If the mask falls entirely outside the bounds, exit
|
|
62
|
-
if (iw <= 0 || ih <= 0) return
|
|
62
|
+
if (iw <= 0 || ih <= 0) return false
|
|
63
63
|
|
|
64
|
-
// Apply the intersected coordinates and internal mask offsets
|
|
65
64
|
scratchOptions.x = ix
|
|
66
65
|
scratchOptions.y = iy
|
|
67
66
|
scratchOptions.w = iw
|
|
@@ -72,7 +71,7 @@ export function applyCircleBrushToPixelData(
|
|
|
72
71
|
scratchOptions.blendFn = blendFn
|
|
73
72
|
|
|
74
73
|
if (brush.type === MaskType.ALPHA) {
|
|
75
|
-
blendColorPixelDataAlphaMask(
|
|
74
|
+
return blendColorPixelDataAlphaMask(
|
|
76
75
|
target,
|
|
77
76
|
color,
|
|
78
77
|
brush,
|
|
@@ -81,11 +80,13 @@ export function applyCircleBrushToPixelData(
|
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
if (brush.type === MaskType.BINARY) {
|
|
84
|
-
blendColorPixelDataBinaryMask(
|
|
83
|
+
return blendColorPixelDataBinaryMask(
|
|
85
84
|
target,
|
|
86
85
|
color,
|
|
87
86
|
brush,
|
|
88
87
|
scratchOptions,
|
|
89
88
|
)
|
|
90
89
|
}
|
|
90
|
+
|
|
91
|
+
return false
|
|
91
92
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { BlendColor32, Color32, IPixelData } from '../_types'
|
|
2
|
+
import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
|
|
3
|
+
|
|
4
|
+
export function blendPixel(
|
|
5
|
+
target: IPixelData,
|
|
6
|
+
x: number,
|
|
7
|
+
y: number,
|
|
8
|
+
color: Color32,
|
|
9
|
+
alpha: number = 255,
|
|
10
|
+
blendFn: BlendColor32 = sourceOverPerfect,
|
|
11
|
+
): boolean {
|
|
12
|
+
if (alpha === 0) return false
|
|
13
|
+
|
|
14
|
+
let width = target.width
|
|
15
|
+
let height = target.height
|
|
16
|
+
|
|
17
|
+
if (x < 0 || x >= width || y < 0 || y >= height) return false
|
|
18
|
+
|
|
19
|
+
let srcAlpha = color >>> 24
|
|
20
|
+
let isOverwrite = blendFn.isOverwrite
|
|
21
|
+
|
|
22
|
+
// Early exit for transparent source unless we are in an overwrite mode
|
|
23
|
+
if (srcAlpha === 0 && !isOverwrite) return false
|
|
24
|
+
|
|
25
|
+
let dst32 = target.data32
|
|
26
|
+
let index = y * width + x
|
|
27
|
+
let finalColor = color
|
|
28
|
+
|
|
29
|
+
if (alpha !== 255) {
|
|
30
|
+
let finalAlpha = (srcAlpha * alpha + 128) >> 8
|
|
31
|
+
|
|
32
|
+
if (finalAlpha === 0 && !isOverwrite) return false
|
|
33
|
+
|
|
34
|
+
finalColor = (((color & 0x00ffffff) | (finalAlpha << 24)) >>> 0) as Color32
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let current = dst32[index] as Color32
|
|
38
|
+
let next = blendFn(finalColor, current)
|
|
39
|
+
|
|
40
|
+
if (current !== next) {
|
|
41
|
+
dst32[index] = next
|
|
42
|
+
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
@@ -19,7 +19,7 @@ export function blendPixelData(
|
|
|
19
19
|
dst: IPixelData,
|
|
20
20
|
src: IPixelData,
|
|
21
21
|
opts: PixelBlendOptions = {},
|
|
22
|
-
) {
|
|
22
|
+
): boolean {
|
|
23
23
|
const {
|
|
24
24
|
x: targetX = 0,
|
|
25
25
|
y: targetY = 0,
|
|
@@ -31,7 +31,7 @@ export function blendPixelData(
|
|
|
31
31
|
blendFn = sourceOverPerfect,
|
|
32
32
|
} = opts
|
|
33
33
|
|
|
34
|
-
if (globalAlpha === 0) return
|
|
34
|
+
if (globalAlpha === 0) return false
|
|
35
35
|
|
|
36
36
|
let x = targetX
|
|
37
37
|
let y = targetY
|
|
@@ -65,7 +65,7 @@ export function blendPixelData(
|
|
|
65
65
|
|
|
66
66
|
const actualW = Math.min(w, dst.width - x)
|
|
67
67
|
const actualH = Math.min(h, dst.height - y)
|
|
68
|
-
if (actualW <= 0 || actualH <= 0) return
|
|
68
|
+
if (actualW <= 0 || actualH <= 0) return false
|
|
69
69
|
|
|
70
70
|
const dst32 = dst.data32
|
|
71
71
|
const src32 = src.data32
|
|
@@ -79,6 +79,7 @@ export function blendPixelData(
|
|
|
79
79
|
const sStride = (sw - actualW) | 0
|
|
80
80
|
const isOpaque = globalAlpha === 255
|
|
81
81
|
const isOverwrite = blendFn.isOverwrite
|
|
82
|
+
let didChange = false
|
|
82
83
|
|
|
83
84
|
for (let iy = 0; iy < actualH; iy++) {
|
|
84
85
|
for (let ix = 0; ix < actualW; ix++) {
|
|
@@ -102,11 +103,20 @@ export function blendPixelData(
|
|
|
102
103
|
finalCol = ((srcCol & 0x00ffffff) | (a << 24)) >>> 0 as Color32
|
|
103
104
|
}
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
const current = dst32[dIdx] as Color32
|
|
107
|
+
const next = blendFn(finalCol, dst32[dIdx] as Color32)
|
|
108
|
+
|
|
109
|
+
if (current !== next) {
|
|
110
|
+
dst32[dIdx] = next
|
|
111
|
+
didChange = true
|
|
112
|
+
}
|
|
113
|
+
|
|
106
114
|
dIdx++
|
|
107
115
|
sIdx++
|
|
108
116
|
}
|
|
109
117
|
dIdx += dStride
|
|
110
118
|
sIdx += sStride
|
|
111
119
|
}
|
|
120
|
+
|
|
121
|
+
return didChange
|
|
112
122
|
}
|
|
@@ -6,7 +6,7 @@ export function blendPixelDataAlphaMask(
|
|
|
6
6
|
src: IPixelData,
|
|
7
7
|
alphaMask: AlphaMask,
|
|
8
8
|
opts: PixelBlendMaskOptions = {},
|
|
9
|
-
) {
|
|
9
|
+
): boolean {
|
|
10
10
|
const {
|
|
11
11
|
x: targetX = 0,
|
|
12
12
|
y: targetY = 0,
|
|
@@ -21,7 +21,7 @@ export function blendPixelDataAlphaMask(
|
|
|
21
21
|
invertMask = false,
|
|
22
22
|
} = opts
|
|
23
23
|
|
|
24
|
-
if (globalAlpha === 0) return
|
|
24
|
+
if (globalAlpha === 0) return false
|
|
25
25
|
|
|
26
26
|
let x = targetX
|
|
27
27
|
let y = targetY
|
|
@@ -56,7 +56,7 @@ export function blendPixelDataAlphaMask(
|
|
|
56
56
|
|
|
57
57
|
const actualW = Math.min(w, dst.width - x)
|
|
58
58
|
const actualH = Math.min(h, dst.height - y)
|
|
59
|
-
if (actualW <= 0 || actualH <= 0) return
|
|
59
|
+
if (actualW <= 0 || actualH <= 0) return false
|
|
60
60
|
|
|
61
61
|
// 2. Index Setup
|
|
62
62
|
const dw = dst.width
|
|
@@ -82,6 +82,7 @@ export function blendPixelDataAlphaMask(
|
|
|
82
82
|
|
|
83
83
|
const isOpaque = globalAlpha === 255
|
|
84
84
|
const isOverwrite = blendFn.isOverwrite || false
|
|
85
|
+
let didChange = false
|
|
85
86
|
|
|
86
87
|
for (let iy = 0; iy < actualH; iy++) {
|
|
87
88
|
for (let ix = 0; ix < actualW; ix++) {
|
|
@@ -135,8 +136,13 @@ export function blendPixelDataAlphaMask(
|
|
|
135
136
|
}
|
|
136
137
|
finalCol = ((srcCol & 0x00ffffff) | (a << 24)) >>> 0 as Color32
|
|
137
138
|
}
|
|
139
|
+
const current = dst32[dIdx] as Color32
|
|
140
|
+
const next = blendFn(finalCol, dst32[dIdx] as Color32)
|
|
138
141
|
|
|
139
|
-
|
|
142
|
+
if (current !== next) {
|
|
143
|
+
dst32[dIdx] = next
|
|
144
|
+
didChange = true
|
|
145
|
+
}
|
|
140
146
|
|
|
141
147
|
dIdx++
|
|
142
148
|
sIdx++
|
|
@@ -146,4 +152,6 @@ export function blendPixelDataAlphaMask(
|
|
|
146
152
|
sIdx += sStride
|
|
147
153
|
mIdx += mStride
|
|
148
154
|
}
|
|
155
|
+
|
|
156
|
+
return didChange
|
|
149
157
|
}
|
|
@@ -6,7 +6,7 @@ export function blendPixelDataBinaryMask(
|
|
|
6
6
|
src: IPixelData,
|
|
7
7
|
binaryMask: BinaryMask,
|
|
8
8
|
opts: PixelBlendMaskOptions = {},
|
|
9
|
-
) {
|
|
9
|
+
): boolean {
|
|
10
10
|
const {
|
|
11
11
|
x: targetX = 0,
|
|
12
12
|
y: targetY = 0,
|
|
@@ -21,7 +21,7 @@ export function blendPixelDataBinaryMask(
|
|
|
21
21
|
invertMask = false,
|
|
22
22
|
} = opts
|
|
23
23
|
|
|
24
|
-
if (globalAlpha === 0) return
|
|
24
|
+
if (globalAlpha === 0) return false
|
|
25
25
|
|
|
26
26
|
let x = targetX
|
|
27
27
|
let y = targetY
|
|
@@ -59,7 +59,7 @@ export function blendPixelDataBinaryMask(
|
|
|
59
59
|
const actualW = Math.min(w, dst.width - x)
|
|
60
60
|
const actualH = Math.min(h, dst.height - y)
|
|
61
61
|
|
|
62
|
-
if (actualW <= 0 || actualH <= 0) return
|
|
62
|
+
if (actualW <= 0 || actualH <= 0) return false
|
|
63
63
|
|
|
64
64
|
// 3. Coordinate Displacement for Mask Sync
|
|
65
65
|
// dx/dy represents how far the clipped start is from the requested start.
|
|
@@ -85,6 +85,7 @@ export function blendPixelDataBinaryMask(
|
|
|
85
85
|
const skipVal = invertMask ? 1 : 0
|
|
86
86
|
const isOpaque = globalAlpha === 255
|
|
87
87
|
const isOverwrite = blendFn.isOverwrite || false
|
|
88
|
+
let didChange = false
|
|
88
89
|
|
|
89
90
|
for (let iy = 0; iy < actualH; iy++) {
|
|
90
91
|
for (let ix = 0; ix < actualW; ix++) {
|
|
@@ -120,7 +121,13 @@ export function blendPixelDataBinaryMask(
|
|
|
120
121
|
finalCol = ((srcCol & 0x00ffffff) | (a << 24)) >>> 0 as Color32
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
const current = dst32[dIdx] as Color32
|
|
125
|
+
const next = blendFn(finalCol, dst32[dIdx] as Color32)
|
|
126
|
+
|
|
127
|
+
if (current !== next) {
|
|
128
|
+
dst32[dIdx] = next
|
|
129
|
+
didChange = true
|
|
130
|
+
}
|
|
124
131
|
|
|
125
132
|
dIdx++
|
|
126
133
|
sIdx++
|
|
@@ -130,4 +137,6 @@ export function blendPixelDataBinaryMask(
|
|
|
130
137
|
sIdx += sStride
|
|
131
138
|
mIdx += mStride
|
|
132
139
|
}
|
|
140
|
+
|
|
141
|
+
return didChange
|
|
133
142
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BlendColor32, IPixelData } from '../_types'
|
|
2
|
+
import { blendPixelData } from './blendPixelData'
|
|
3
|
+
import type { PaintBuffer } from '../PixelTile/PaintBuffer'
|
|
4
|
+
|
|
5
|
+
const SCRATCH_OPTS = {
|
|
6
|
+
x: 0,
|
|
7
|
+
y: 0,
|
|
8
|
+
alpha: 255,
|
|
9
|
+
blendFn: undefined as BlendColor32 | undefined,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function blendPixelDataPaintBuffer(
|
|
13
|
+
paintBuffer: PaintBuffer,
|
|
14
|
+
target: IPixelData,
|
|
15
|
+
alpha = 255,
|
|
16
|
+
blendFn?: BlendColor32,
|
|
17
|
+
blendPixelDataFn = blendPixelData,
|
|
18
|
+
): void {
|
|
19
|
+
const tileShift = paintBuffer.config.tileShift
|
|
20
|
+
const lookup = paintBuffer.lookup
|
|
21
|
+
for (let i = 0; i < lookup.length; i++) {
|
|
22
|
+
const tile = lookup[i]
|
|
23
|
+
|
|
24
|
+
if (tile) {
|
|
25
|
+
const x = tile.tx << tileShift
|
|
26
|
+
const y = tile.ty << tileShift
|
|
27
|
+
|
|
28
|
+
SCRATCH_OPTS.x = x
|
|
29
|
+
SCRATCH_OPTS.y = y
|
|
30
|
+
SCRATCH_OPTS.alpha = alpha
|
|
31
|
+
SCRATCH_OPTS.blendFn = blendFn
|
|
32
|
+
|
|
33
|
+
blendPixelDataFn(target, tile, SCRATCH_OPTS)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BinaryMaskRect, Color32, IPixelData } from '../_types'
|
|
2
|
-
import {
|
|
2
|
+
import { fillPixelDataFast } from './fillPixelDataFast'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Clears a region of the PixelData to transparent (0x00000000).
|
|
@@ -9,5 +9,5 @@ export function clearPixelData(
|
|
|
9
9
|
dst: IPixelData,
|
|
10
10
|
rect?: Partial<BinaryMaskRect>,
|
|
11
11
|
): void {
|
|
12
|
-
|
|
12
|
+
fillPixelDataFast(dst, 0 as Color32, rect)
|
|
13
13
|
}
|
|
@@ -9,13 +9,13 @@ const SCRATCH_RECT = makeClippedRect()
|
|
|
9
9
|
* @param dst - The target to modify.
|
|
10
10
|
* @param color - The color to apply.
|
|
11
11
|
* @param rect - Defines the area to fill. If omitted, the entire
|
|
12
|
-
*
|
|
12
|
+
* @returns true if any pixels were actually modified.
|
|
13
13
|
*/
|
|
14
14
|
export function fillPixelData(
|
|
15
15
|
dst: IPixelData,
|
|
16
16
|
color: Color32,
|
|
17
17
|
rect?: Partial<Rect>,
|
|
18
|
-
):
|
|
18
|
+
): boolean
|
|
19
19
|
/**
|
|
20
20
|
* @param dst - The target to modify.
|
|
21
21
|
* @param color - The color to apply.
|
|
@@ -31,7 +31,7 @@ export function fillPixelData(
|
|
|
31
31
|
y: number,
|
|
32
32
|
w: number,
|
|
33
33
|
h: number,
|
|
34
|
-
):
|
|
34
|
+
): boolean
|
|
35
35
|
export function fillPixelData(
|
|
36
36
|
dst: IPixelData,
|
|
37
37
|
color: Color32,
|
|
@@ -39,7 +39,7 @@ export function fillPixelData(
|
|
|
39
39
|
_y?: number,
|
|
40
40
|
_w?: number,
|
|
41
41
|
_h?: number,
|
|
42
|
-
):
|
|
42
|
+
): boolean {
|
|
43
43
|
let x: number
|
|
44
44
|
let y: number
|
|
45
45
|
let w: number
|
|
@@ -62,11 +62,18 @@ export function fillPixelData(
|
|
|
62
62
|
h = dst.height
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const clip = resolveRectClipping(
|
|
65
|
+
const clip = resolveRectClipping(
|
|
66
|
+
x,
|
|
67
|
+
y,
|
|
68
|
+
w,
|
|
69
|
+
h,
|
|
70
|
+
dst.width,
|
|
71
|
+
dst.height,
|
|
72
|
+
SCRATCH_RECT,
|
|
73
|
+
)
|
|
66
74
|
|
|
67
|
-
if (!clip.inBounds) return
|
|
75
|
+
if (!clip.inBounds) return false
|
|
68
76
|
|
|
69
|
-
// Use the clipped values
|
|
70
77
|
const {
|
|
71
78
|
x: finalX,
|
|
72
79
|
y: finalY,
|
|
@@ -76,17 +83,20 @@ export function fillPixelData(
|
|
|
76
83
|
|
|
77
84
|
const dst32 = dst.data32
|
|
78
85
|
const dw = dst.width
|
|
86
|
+
let hasChanged = false
|
|
79
87
|
|
|
80
|
-
// Optimization: If filling the entire buffer, use the native .fill()
|
|
81
|
-
if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
|
|
82
|
-
dst32.fill(color)
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Row-by-row fill for partial rectangles
|
|
87
88
|
for (let iy = 0; iy < actualH; iy++) {
|
|
88
|
-
const
|
|
89
|
+
const rowOffset = (finalY + iy) * dw
|
|
90
|
+
const start = rowOffset + finalX
|
|
89
91
|
const end = start + actualW
|
|
90
|
-
|
|
92
|
+
|
|
93
|
+
for (let i = start; i < end; i++) {
|
|
94
|
+
if (dst32[i] !== color) {
|
|
95
|
+
dst32[i] = color
|
|
96
|
+
hasChanged = true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
91
99
|
}
|
|
100
|
+
|
|
101
|
+
return hasChanged
|
|
92
102
|
}
|
|
@@ -19,8 +19,8 @@ export function fillPixelDataBinaryMask(
|
|
|
19
19
|
alpha = 255,
|
|
20
20
|
x = 0,
|
|
21
21
|
y = 0,
|
|
22
|
-
):
|
|
23
|
-
if (alpha === 0) return
|
|
22
|
+
): boolean {
|
|
23
|
+
if (alpha === 0) return false
|
|
24
24
|
|
|
25
25
|
const maskW = mask.w
|
|
26
26
|
const maskH = mask.h
|
|
@@ -35,7 +35,7 @@ export function fillPixelDataBinaryMask(
|
|
|
35
35
|
SCRATCH_RECT,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
if (!clip.inBounds) return
|
|
38
|
+
if (!clip.inBounds) return false
|
|
39
39
|
|
|
40
40
|
const {
|
|
41
41
|
x: finalX,
|
|
@@ -59,6 +59,8 @@ export function fillPixelDataBinaryMask(
|
|
|
59
59
|
finalCol = ((colorRGB | (a << 24)) >>> 0) as Color32
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
let hasChanged = false
|
|
63
|
+
|
|
62
64
|
for (let iy = 0; iy < actualH; iy++) {
|
|
63
65
|
const currentY = finalY + iy
|
|
64
66
|
const maskY = currentY - y
|
|
@@ -72,8 +74,14 @@ export function fillPixelDataBinaryMask(
|
|
|
72
74
|
const maskIndex = maskOffset + maskX
|
|
73
75
|
|
|
74
76
|
if (maskData[maskIndex]) {
|
|
75
|
-
dst32[dstRowOffset + currentX]
|
|
77
|
+
const current = dst32[dstRowOffset + currentX]
|
|
78
|
+
if (current !== finalCol) {
|
|
79
|
+
dst32[dstRowOffset + currentX] = finalCol
|
|
80
|
+
hasChanged = true
|
|
81
|
+
}
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
}
|
|
85
|
+
|
|
86
|
+
return hasChanged
|
|
79
87
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Color32, IPixelData, Rect } from '../_types'
|
|
2
|
+
import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
|
|
3
|
+
|
|
4
|
+
const SCRATCH_RECT = makeClippedRect()
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fills a region or the {@link IPixelData} buffer with a solid color.
|
|
8
|
+
* This function is faster than {@link fillPixelData} but does not
|
|
9
|
+
* return a boolean value indicating changes were made.
|
|
10
|
+
*
|
|
11
|
+
* @param dst - The target to modify.
|
|
12
|
+
* @param color - The color to apply.
|
|
13
|
+
* @param rect - Defines the area to fill. If omitted, the entire
|
|
14
|
+
* buffer is filled.
|
|
15
|
+
*/
|
|
16
|
+
export function fillPixelDataFast(
|
|
17
|
+
dst: IPixelData,
|
|
18
|
+
color: Color32,
|
|
19
|
+
rect?: Partial<Rect>,
|
|
20
|
+
): void
|
|
21
|
+
/**
|
|
22
|
+
* @param dst - The target to modify.
|
|
23
|
+
* @param color - The color to apply.
|
|
24
|
+
* @param x - Starting horizontal coordinate.
|
|
25
|
+
* @param y - Starting vertical coordinate.
|
|
26
|
+
* @param w - Width of the fill area.
|
|
27
|
+
* @param h - Height of the fill area.
|
|
28
|
+
*/
|
|
29
|
+
export function fillPixelDataFast(
|
|
30
|
+
dst: IPixelData,
|
|
31
|
+
color: Color32,
|
|
32
|
+
x: number,
|
|
33
|
+
y: number,
|
|
34
|
+
w: number,
|
|
35
|
+
h: number,
|
|
36
|
+
): void
|
|
37
|
+
export function fillPixelDataFast(
|
|
38
|
+
dst: IPixelData,
|
|
39
|
+
color: Color32,
|
|
40
|
+
_x?: Partial<Rect> | number,
|
|
41
|
+
_y?: number,
|
|
42
|
+
_w?: number,
|
|
43
|
+
_h?: number,
|
|
44
|
+
): void {
|
|
45
|
+
let x: number
|
|
46
|
+
let y: number
|
|
47
|
+
let w: number
|
|
48
|
+
let h: number
|
|
49
|
+
|
|
50
|
+
if (typeof _x === 'object') {
|
|
51
|
+
x = _x.x ?? 0
|
|
52
|
+
y = _x.y ?? 0
|
|
53
|
+
w = _x.w ?? dst.width
|
|
54
|
+
h = _x.h ?? dst.height
|
|
55
|
+
} else if (typeof _x === 'number') {
|
|
56
|
+
x = _x
|
|
57
|
+
y = _y!
|
|
58
|
+
w = _w!
|
|
59
|
+
h = _h!
|
|
60
|
+
} else {
|
|
61
|
+
x = 0
|
|
62
|
+
y = 0
|
|
63
|
+
w = dst.width
|
|
64
|
+
h = dst.height
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT)
|
|
68
|
+
|
|
69
|
+
if (!clip.inBounds) return
|
|
70
|
+
|
|
71
|
+
// Use the clipped values
|
|
72
|
+
const {
|
|
73
|
+
x: finalX,
|
|
74
|
+
y: finalY,
|
|
75
|
+
w: actualW,
|
|
76
|
+
h: actualH,
|
|
77
|
+
} = clip
|
|
78
|
+
|
|
79
|
+
const dst32 = dst.data32
|
|
80
|
+
const dw = dst.width
|
|
81
|
+
|
|
82
|
+
// Optimization: If filling the entire buffer, use the native .fill()
|
|
83
|
+
if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
|
|
84
|
+
dst32.fill(color)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Row-by-row fill for partial rectangles
|
|
89
|
+
for (let iy = 0; iy < actualH; iy++) {
|
|
90
|
+
const start = (finalY + iy) * dw + finalX
|
|
91
|
+
const end = start + actualW
|
|
92
|
+
dst32.fill(color, start, end)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -6,7 +6,7 @@ const SCRATCH_RECT = makeClippedRect()
|
|
|
6
6
|
export function invertPixelData(
|
|
7
7
|
pixelData: IPixelData,
|
|
8
8
|
opts: PixelMutateOptions = {},
|
|
9
|
-
):
|
|
9
|
+
): boolean {
|
|
10
10
|
const dst = pixelData
|
|
11
11
|
const {
|
|
12
12
|
x: targetX = 0,
|
|
@@ -21,7 +21,7 @@ export function invertPixelData(
|
|
|
21
21
|
|
|
22
22
|
const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT)
|
|
23
23
|
|
|
24
|
-
if (!clip.inBounds) return
|
|
24
|
+
if (!clip.inBounds) return false
|
|
25
25
|
|
|
26
26
|
const {
|
|
27
27
|
x,
|
|
@@ -72,4 +72,6 @@ export function invertPixelData(
|
|
|
72
72
|
dIdx += dStride
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
+
|
|
76
|
+
return true
|
|
75
77
|
}
|