pixel-data-js 0.24.0 → 0.25.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 +1431 -1845
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +1297 -1702
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +1305 -1719
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +220 -328
- package/dist/index.prod.js +1423 -1828
- 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/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 +9 -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
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { BlendColor32, Color32, HistoryMutator, Rect } from '../../_types'
|
|
2
|
-
import { applyRectBrushToPixelData } from '../../PixelData/applyRectBrushToPixelData'
|
|
3
|
-
import { getRectBrushOrPencilBounds } from '../../Rect/getRectBrushOrPencilBounds'
|
|
4
|
-
import { PixelWriter } from '../PixelWriter'
|
|
5
|
-
|
|
6
|
-
const defaults = {
|
|
7
|
-
applyRectBrushToPixelData,
|
|
8
|
-
getRectBrushOrPencilBounds,
|
|
9
|
-
fallOff: () => 1,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type Deps = Partial<typeof defaults>
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param deps - @hidden
|
|
16
|
-
*/
|
|
17
|
-
export const mutatorApplyRectPencil = ((writer: PixelWriter<any>, deps: Deps = defaults) => {
|
|
18
|
-
const {
|
|
19
|
-
applyRectBrushToPixelData = defaults.applyRectBrushToPixelData,
|
|
20
|
-
getRectBrushOrPencilBounds = defaults.getRectBrushOrPencilBounds,
|
|
21
|
-
fallOff = defaults.fallOff,
|
|
22
|
-
} = deps
|
|
23
|
-
|
|
24
|
-
const boundsOut: Rect = { x: 0, y: 0, w: 0, h: 0 }
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
applyRectPencil(
|
|
28
|
-
color: Color32,
|
|
29
|
-
centerX: number,
|
|
30
|
-
centerY: number,
|
|
31
|
-
brushWidth: number,
|
|
32
|
-
brushHeight: number,
|
|
33
|
-
alpha = 255,
|
|
34
|
-
blendFn?: BlendColor32,
|
|
35
|
-
): boolean {
|
|
36
|
-
|
|
37
|
-
const target = writer.config.target
|
|
38
|
-
const b = getRectBrushOrPencilBounds(
|
|
39
|
-
centerX,
|
|
40
|
-
centerY,
|
|
41
|
-
brushWidth,
|
|
42
|
-
brushHeight,
|
|
43
|
-
target.width,
|
|
44
|
-
target.height,
|
|
45
|
-
boundsOut,
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h)
|
|
49
|
-
return didChange(
|
|
50
|
-
applyRectBrushToPixelData(
|
|
51
|
-
target,
|
|
52
|
-
color,
|
|
53
|
-
centerX,
|
|
54
|
-
centerY,
|
|
55
|
-
brushWidth,
|
|
56
|
-
brushHeight,
|
|
57
|
-
alpha,
|
|
58
|
-
fallOff,
|
|
59
|
-
blendFn,
|
|
60
|
-
b,
|
|
61
|
-
),
|
|
62
|
-
)
|
|
63
|
-
},
|
|
64
|
-
}
|
|
65
|
-
}) satisfies HistoryMutator<any, Deps>
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type BinaryMask,
|
|
3
|
-
type BlendColor32,
|
|
4
|
-
type Color32,
|
|
5
|
-
type ColorBlendOptions,
|
|
6
|
-
type HistoryMutator,
|
|
7
|
-
MaskType,
|
|
8
|
-
type Rect,
|
|
9
|
-
} from '../../_types'
|
|
10
|
-
import { forEachLinePoint } from '../../Algorithm/forEachLinePoint'
|
|
11
|
-
import { sourceOverPerfect } from '../../BlendModes/blend-modes-perfect'
|
|
12
|
-
import { blendColorPixelDataBinaryMask } from '../../PixelData/blendColorPixelDataBinaryMask'
|
|
13
|
-
import { getRectBrushOrPencilBounds } from '../../Rect/getRectBrushOrPencilBounds'
|
|
14
|
-
import { getRectBrushOrPencilStrokeBounds } from '../../Rect/getRectBrushOrPencilStrokeBounds'
|
|
15
|
-
import { PixelWriter } from '../PixelWriter'
|
|
16
|
-
|
|
17
|
-
const defaults = {
|
|
18
|
-
forEachLinePoint,
|
|
19
|
-
getRectBrushOrPencilBounds,
|
|
20
|
-
getRectBrushOrPencilStrokeBounds,
|
|
21
|
-
blendColorPixelDataBinaryMask,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type Deps = Partial<typeof defaults>
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @param deps - @hidden
|
|
28
|
-
*/
|
|
29
|
-
export const mutatorApplyRectPencilStroke = ((writer: PixelWriter<any>, deps: Deps = defaults) => {
|
|
30
|
-
const {
|
|
31
|
-
forEachLinePoint = defaults.forEachLinePoint,
|
|
32
|
-
blendColorPixelDataBinaryMask = defaults.blendColorPixelDataBinaryMask,
|
|
33
|
-
getRectBrushOrPencilBounds = defaults.getRectBrushOrPencilBounds,
|
|
34
|
-
getRectBrushOrPencilStrokeBounds = defaults.getRectBrushOrPencilStrokeBounds,
|
|
35
|
-
} = deps
|
|
36
|
-
|
|
37
|
-
const strokeBoundsOut: Rect = {
|
|
38
|
-
x: 0,
|
|
39
|
-
y: 0,
|
|
40
|
-
w: 0,
|
|
41
|
-
h: 0,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const rectPencilBounds: Rect = {
|
|
45
|
-
x: 0,
|
|
46
|
-
y: 0,
|
|
47
|
-
w: 0,
|
|
48
|
-
h: 0,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const blendColorPixelOptions: ColorBlendOptions = {
|
|
52
|
-
alpha: 255,
|
|
53
|
-
blendFn: sourceOverPerfect,
|
|
54
|
-
x: 0,
|
|
55
|
-
y: 0,
|
|
56
|
-
w: 0,
|
|
57
|
-
h: 0,
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const mask = {
|
|
61
|
-
type: MaskType.BINARY,
|
|
62
|
-
data: null as unknown as Uint8Array,
|
|
63
|
-
w: 0,
|
|
64
|
-
h: 0,
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
applyRectPencilStroke(
|
|
69
|
-
color: Color32,
|
|
70
|
-
x0: number,
|
|
71
|
-
y0: number,
|
|
72
|
-
x1: number,
|
|
73
|
-
y1: number,
|
|
74
|
-
brushWidth: number,
|
|
75
|
-
brushHeight: number,
|
|
76
|
-
alpha = 255,
|
|
77
|
-
blendFn: BlendColor32 = sourceOverPerfect,
|
|
78
|
-
) {
|
|
79
|
-
const {
|
|
80
|
-
x: bx,
|
|
81
|
-
y: by,
|
|
82
|
-
w: bw,
|
|
83
|
-
h: bh,
|
|
84
|
-
} = getRectBrushOrPencilStrokeBounds(
|
|
85
|
-
x0,
|
|
86
|
-
y0,
|
|
87
|
-
x1,
|
|
88
|
-
y1,
|
|
89
|
-
brushWidth,
|
|
90
|
-
brushHeight,
|
|
91
|
-
strokeBoundsOut,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
if (bw <= 0 || bh <= 0) return
|
|
95
|
-
|
|
96
|
-
mask.data = new Uint8Array(bw * bh)
|
|
97
|
-
mask.w = bw
|
|
98
|
-
mask.h = bh
|
|
99
|
-
|
|
100
|
-
const maskData = mask.data
|
|
101
|
-
|
|
102
|
-
const halfW = brushWidth / 2
|
|
103
|
-
const halfH = brushHeight / 2
|
|
104
|
-
const centerOffset = (brushWidth % 2 === 0) ? 0.5 : 0
|
|
105
|
-
|
|
106
|
-
const target = writer.config.target
|
|
107
|
-
const targetWidth = target.width
|
|
108
|
-
const targetHeight = target.height
|
|
109
|
-
|
|
110
|
-
forEachLinePoint(x0, y0, x1, y1, (px, py) => {
|
|
111
|
-
const {
|
|
112
|
-
x: rbx,
|
|
113
|
-
y: rby,
|
|
114
|
-
w: rbw,
|
|
115
|
-
h: rbh,
|
|
116
|
-
} = getRectBrushOrPencilBounds(
|
|
117
|
-
px,
|
|
118
|
-
py,
|
|
119
|
-
brushWidth,
|
|
120
|
-
brushHeight,
|
|
121
|
-
targetWidth,
|
|
122
|
-
targetHeight,
|
|
123
|
-
rectPencilBounds,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
writer.accumulator.storeRegionBeforeState(
|
|
127
|
-
rbx,
|
|
128
|
-
rby,
|
|
129
|
-
rbw,
|
|
130
|
-
rbh,
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
const startX = Math.max(bx, rbx)
|
|
134
|
-
const startY = Math.max(by, rby)
|
|
135
|
-
const endX = Math.min(bx + bw, rbx + rbw)
|
|
136
|
-
const endY = Math.min(by + bh, rby + rbh)
|
|
137
|
-
|
|
138
|
-
const fPx = Math.floor(px)
|
|
139
|
-
const fPy = Math.floor(py)
|
|
140
|
-
|
|
141
|
-
for (let my = startY; my < endY; my++) {
|
|
142
|
-
const dy = Math.abs((my - fPy) + centerOffset)
|
|
143
|
-
const maskRowOffset = (my - by) * bw
|
|
144
|
-
|
|
145
|
-
for (let mx = startX; mx < endX; mx++) {
|
|
146
|
-
const dx = Math.abs((mx - fPx) + centerOffset)
|
|
147
|
-
const maskIdx = maskRowOffset + (mx - bx)
|
|
148
|
-
|
|
149
|
-
if (dx <= halfW && dy <= halfH) {
|
|
150
|
-
maskData[maskIdx] = 1
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
blendColorPixelOptions.blendFn = blendFn
|
|
157
|
-
blendColorPixelOptions.alpha = alpha
|
|
158
|
-
blendColorPixelOptions.x = bx
|
|
159
|
-
blendColorPixelOptions.y = by
|
|
160
|
-
blendColorPixelOptions.w = bw
|
|
161
|
-
blendColorPixelOptions.h = bh
|
|
162
|
-
|
|
163
|
-
blendColorPixelDataBinaryMask(target, color, mask as BinaryMask, blendColorPixelOptions)
|
|
164
|
-
},
|
|
165
|
-
}
|
|
166
|
-
}) satisfies HistoryMutator<any, Deps>
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { BlendColor32, CircleMask, Color32, ColorBlendMaskOptions, HistoryMutator, Rect } from '../../_types'
|
|
2
|
-
import { sourceOverPerfect } from '../../BlendModes/blend-modes-perfect'
|
|
3
|
-
import { blendColorPixelDataCircleMask } from '../../PixelData/blendColorPixelDataCircleMask'
|
|
4
|
-
import { getCircleBrushOrPencilBounds } from '../../Rect/getCircleBrushOrPencilBounds'
|
|
5
|
-
import { PixelWriter } from '../PixelWriter'
|
|
6
|
-
|
|
7
|
-
const defaults = {
|
|
8
|
-
blendColorPixelDataCircleMask,
|
|
9
|
-
getCircleBrushOrPencilBounds,
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type Deps = Partial<typeof defaults>
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param deps - @hidden
|
|
16
|
-
*/
|
|
17
|
-
export const mutatorBlendColorCircleMask = ((writer: PixelWriter<any>, deps: Deps = defaults) => {
|
|
18
|
-
const {
|
|
19
|
-
blendColorPixelDataCircleMask = defaults.blendColorPixelDataCircleMask,
|
|
20
|
-
getCircleBrushOrPencilBounds = defaults.getCircleBrushOrPencilBounds,
|
|
21
|
-
|
|
22
|
-
} = deps
|
|
23
|
-
|
|
24
|
-
const boundsOut: Rect = { x: 0, y: 0, w: 0, h: 0 }
|
|
25
|
-
|
|
26
|
-
const blendColorPixelOptions: ColorBlendMaskOptions = {
|
|
27
|
-
alpha: 255,
|
|
28
|
-
blendFn: sourceOverPerfect,
|
|
29
|
-
x: 0,
|
|
30
|
-
y: 0,
|
|
31
|
-
w: 0,
|
|
32
|
-
h: 0,
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
applyCircleMask(
|
|
37
|
-
color: Color32,
|
|
38
|
-
centerX: number,
|
|
39
|
-
centerY: number,
|
|
40
|
-
brush: CircleMask,
|
|
41
|
-
alpha = 255,
|
|
42
|
-
blendFn?: BlendColor32,
|
|
43
|
-
): boolean {
|
|
44
|
-
|
|
45
|
-
const target = writer.config.target
|
|
46
|
-
const b = getCircleBrushOrPencilBounds(
|
|
47
|
-
centerX,
|
|
48
|
-
centerY,
|
|
49
|
-
brush.size,
|
|
50
|
-
target.width,
|
|
51
|
-
target.height,
|
|
52
|
-
boundsOut,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h)
|
|
56
|
-
return didChange(
|
|
57
|
-
blendColorPixelDataCircleMask(
|
|
58
|
-
target,
|
|
59
|
-
color,
|
|
60
|
-
centerX,
|
|
61
|
-
centerY,
|
|
62
|
-
brush,
|
|
63
|
-
alpha,
|
|
64
|
-
blendFn,
|
|
65
|
-
blendColorPixelOptions,
|
|
66
|
-
b,
|
|
67
|
-
),
|
|
68
|
-
)
|
|
69
|
-
},
|
|
70
|
-
}
|
|
71
|
-
}) satisfies HistoryMutator<any, Deps>
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { type CircleAlphaMask, MaskType } from '../_types'
|
|
2
|
-
|
|
3
|
-
export function makeCircleAlphaMask(size: number, fallOff: (d: number) => number = () => 1): CircleAlphaMask {
|
|
4
|
-
const area = size * size
|
|
5
|
-
const data = new Uint8Array(area)
|
|
6
|
-
const radius = size / 2
|
|
7
|
-
const invR = 1 / radius
|
|
8
|
-
|
|
9
|
-
const minOffset = -Math.ceil(radius - 0.5)
|
|
10
|
-
|
|
11
|
-
for (let y = 0; y < size; y++) {
|
|
12
|
-
for (let x = 0; x < size; x++) {
|
|
13
|
-
const dx = x - radius + 0.5
|
|
14
|
-
const dy = y - radius + 0.5
|
|
15
|
-
const distSqr = dx * dx + dy * dy
|
|
16
|
-
if (distSqr <= (radius * radius)) {
|
|
17
|
-
const dist = Math.sqrt(distSqr)
|
|
18
|
-
data[y * size + x] = (fallOff(1 - (dist * invR)) * 255) | 0
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
type: MaskType.ALPHA,
|
|
25
|
-
data,
|
|
26
|
-
w: size,
|
|
27
|
-
h: size,
|
|
28
|
-
radius,
|
|
29
|
-
size,
|
|
30
|
-
minOffset,
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type { BlendColor32, Color32, IPixelData, Rect } from '../_types'
|
|
2
|
-
import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
|
|
3
|
-
import { getRectBrushOrPencilBounds } from '../Rect/getRectBrushOrPencilBounds'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Applies a rectangular brush to the pixel data.
|
|
7
|
-
* @returns true if any pixels were actually modified.
|
|
8
|
-
*/
|
|
9
|
-
export function applyRectBrushToPixelData(
|
|
10
|
-
target: IPixelData,
|
|
11
|
-
color: Color32,
|
|
12
|
-
centerX: number,
|
|
13
|
-
centerY: number,
|
|
14
|
-
brushWidth: number,
|
|
15
|
-
brushHeight: number,
|
|
16
|
-
alpha = 255,
|
|
17
|
-
fallOff: (dist: number) => number,
|
|
18
|
-
blendFn: BlendColor32 = sourceOverPerfect,
|
|
19
|
-
bounds?: Rect,
|
|
20
|
-
): boolean {
|
|
21
|
-
const targetWidth = target.width
|
|
22
|
-
const targetHeight = target.height
|
|
23
|
-
|
|
24
|
-
const b = bounds ?? getRectBrushOrPencilBounds(
|
|
25
|
-
centerX,
|
|
26
|
-
centerY,
|
|
27
|
-
brushWidth,
|
|
28
|
-
brushHeight,
|
|
29
|
-
targetWidth,
|
|
30
|
-
targetHeight,
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
if (b.w <= 0 || b.h <= 0) {
|
|
34
|
-
return false
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const data32 = target.data32
|
|
38
|
-
const baseColor = color & 0x00ffffff
|
|
39
|
-
const baseSrcAlpha = color >>> 24
|
|
40
|
-
const isOpaque = alpha === 255
|
|
41
|
-
|
|
42
|
-
const invHalfW = 1 / (brushWidth / 2)
|
|
43
|
-
const invHalfH = 1 / (brushHeight / 2)
|
|
44
|
-
|
|
45
|
-
const centerOffsetX = (brushWidth % 2 === 0) ? 0.5 : 0
|
|
46
|
-
const centerOffsetY = (brushHeight % 2 === 0) ? 0.5 : 0
|
|
47
|
-
const fCenterX = Math.floor(centerX)
|
|
48
|
-
const fCenterY = Math.floor(centerY)
|
|
49
|
-
|
|
50
|
-
const endX = b.x + b.w
|
|
51
|
-
const endY = b.y + b.h
|
|
52
|
-
const isOverwrite = (blendFn as any).isOverwrite
|
|
53
|
-
let didChange = false
|
|
54
|
-
|
|
55
|
-
for (let py = b.y; py < endY; py++) {
|
|
56
|
-
const rowOffset = py * targetWidth
|
|
57
|
-
const dy = Math.abs((py - fCenterY) + centerOffsetY) * invHalfH
|
|
58
|
-
|
|
59
|
-
for (let px = b.x; px < endX; px++) {
|
|
60
|
-
const idx = rowOffset + px
|
|
61
|
-
const dx = Math.abs((px - fCenterX) + centerOffsetX) * invHalfW
|
|
62
|
-
const dist = dx > dy ? dx : dy
|
|
63
|
-
|
|
64
|
-
const strength = fallOff(dist)
|
|
65
|
-
const maskVal = (strength * 255) | 0
|
|
66
|
-
|
|
67
|
-
if (maskVal <= 0) continue
|
|
68
|
-
|
|
69
|
-
let weight = alpha
|
|
70
|
-
|
|
71
|
-
if (isOpaque) {
|
|
72
|
-
weight = maskVal
|
|
73
|
-
} else if (maskVal !== 255) {
|
|
74
|
-
weight = (maskVal * alpha + 128) >> 8
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let finalCol = color
|
|
78
|
-
|
|
79
|
-
if (weight < 255) {
|
|
80
|
-
const a = (baseSrcAlpha * weight + 128) >> 8
|
|
81
|
-
|
|
82
|
-
if (a === 0 && !isOverwrite) continue
|
|
83
|
-
|
|
84
|
-
finalCol = ((a << 24) | baseColor) >>> 0 as Color32
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const current = data32[idx] as Color32
|
|
88
|
-
const next = blendFn(finalCol, current)
|
|
89
|
-
|
|
90
|
-
if (current !== next) {
|
|
91
|
-
data32[idx] = next
|
|
92
|
-
didChange = true
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return didChange
|
|
98
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type BlendColor32,
|
|
3
|
-
type CircleMask,
|
|
4
|
-
type Color32,
|
|
5
|
-
type ColorBlendMaskOptions,
|
|
6
|
-
type IPixelData,
|
|
7
|
-
MaskType,
|
|
8
|
-
type Rect,
|
|
9
|
-
} from '../_types'
|
|
10
|
-
import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
|
|
11
|
-
import { getCircleBrushOrPencilBounds } from '../Rect/getCircleBrushOrPencilBounds'
|
|
12
|
-
import { blendColorPixelDataAlphaMask } from './blendColorPixelDataAlphaMask'
|
|
13
|
-
import { blendColorPixelDataBinaryMask } from './blendColorPixelDataBinaryMask'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Applies a circular mask to pixel data using a pre-calculated alpha mask.
|
|
17
|
-
*
|
|
18
|
-
* @param target The PixelData to modify.
|
|
19
|
-
* @param color The brush color.
|
|
20
|
-
* @param centerX The center x-coordinate of the brush.
|
|
21
|
-
* @param centerY The center y-coordinate of the brush.
|
|
22
|
-
* @param brush The pre-calculated CircleBrushAlphaMask.
|
|
23
|
-
* @param alpha The overall opacity of the brush (0-255).
|
|
24
|
-
* @param blendFn
|
|
25
|
-
* @param scratchOptions
|
|
26
|
-
* @param bounds precalculated result from {@link getCircleBrushOrPencilBounds}
|
|
27
|
-
*/
|
|
28
|
-
export function blendColorPixelDataCircleMask(
|
|
29
|
-
target: IPixelData,
|
|
30
|
-
color: Color32,
|
|
31
|
-
centerX: number,
|
|
32
|
-
centerY: number,
|
|
33
|
-
brush: CircleMask,
|
|
34
|
-
alpha = 255,
|
|
35
|
-
blendFn: BlendColor32 = sourceOverPerfect,
|
|
36
|
-
scratchOptions: ColorBlendMaskOptions = {},
|
|
37
|
-
bounds?: Rect,
|
|
38
|
-
): boolean {
|
|
39
|
-
const b = bounds ?? getCircleBrushOrPencilBounds(
|
|
40
|
-
centerX,
|
|
41
|
-
centerY,
|
|
42
|
-
brush.size,
|
|
43
|
-
target.width,
|
|
44
|
-
target.height,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if (b.w <= 0 || b.h <= 0) return false
|
|
48
|
-
|
|
49
|
-
const unclippedStartX = Math.floor(centerX + brush.minOffset)
|
|
50
|
-
const unclippedStartY = Math.floor(centerY + brush.minOffset)
|
|
51
|
-
|
|
52
|
-
// Calculate the intersection between the unclipped mask rect and the allowed bounds
|
|
53
|
-
const ix = Math.max(unclippedStartX, b.x)
|
|
54
|
-
const iy = Math.max(unclippedStartY, b.y)
|
|
55
|
-
const ir = Math.min(unclippedStartX + brush.w, b.x + b.w)
|
|
56
|
-
const ib = Math.min(unclippedStartY + brush.h, b.y + b.h)
|
|
57
|
-
|
|
58
|
-
const iw = ir - ix
|
|
59
|
-
const ih = ib - iy
|
|
60
|
-
|
|
61
|
-
// If the mask falls entirely outside the bounds, exit
|
|
62
|
-
if (iw <= 0 || ih <= 0) return false
|
|
63
|
-
|
|
64
|
-
scratchOptions.x = ix
|
|
65
|
-
scratchOptions.y = iy
|
|
66
|
-
scratchOptions.w = iw
|
|
67
|
-
scratchOptions.h = ih
|
|
68
|
-
scratchOptions.mx = ix - unclippedStartX
|
|
69
|
-
scratchOptions.my = iy - unclippedStartY
|
|
70
|
-
scratchOptions.alpha = alpha
|
|
71
|
-
scratchOptions.blendFn = blendFn
|
|
72
|
-
|
|
73
|
-
if (brush.type === MaskType.ALPHA) {
|
|
74
|
-
return blendColorPixelDataAlphaMask(
|
|
75
|
-
target,
|
|
76
|
-
color,
|
|
77
|
-
brush,
|
|
78
|
-
scratchOptions,
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (brush.type === MaskType.BINARY) {
|
|
83
|
-
return blendColorPixelDataBinaryMask(
|
|
84
|
-
target,
|
|
85
|
-
color,
|
|
86
|
-
brush,
|
|
87
|
-
scratchOptions,
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return false
|
|
92
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { AlphaMaskRect, BinaryMaskRect, Color32, Rect } from '../_types'
|
|
2
|
-
import type { PixelEngineConfig } from '../History/PixelEngineConfig'
|
|
3
|
-
import type { PixelTile } from './PixelTile'
|
|
4
|
-
import type { PixelTilePool } from './PixelTilePool'
|
|
5
|
-
|
|
6
|
-
export class PaintBuffer {
|
|
7
|
-
readonly lookup: (PixelTile | undefined)[]
|
|
8
|
-
|
|
9
|
-
constructor(
|
|
10
|
-
readonly config: PixelEngineConfig,
|
|
11
|
-
readonly tilePool: PixelTilePool,
|
|
12
|
-
) {
|
|
13
|
-
this.lookup = []
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
private processMaskTiles(
|
|
17
|
-
mask: Rect,
|
|
18
|
-
callback: (tile: PixelTile, bX: number, bY: number, bW: number, bH: number, mX: number, mY: number) => void,
|
|
19
|
-
): void {
|
|
20
|
-
const { tileShift, targetColumns } = this.config
|
|
21
|
-
|
|
22
|
-
const x1 = mask.x >> tileShift
|
|
23
|
-
const y1 = mask.y >> tileShift
|
|
24
|
-
const x2 = (mask.x + mask.w - 1) >> tileShift
|
|
25
|
-
const y2 = (mask.y + mask.h - 1) >> tileShift
|
|
26
|
-
|
|
27
|
-
for (let ty = y1; ty <= y2; ty++) {
|
|
28
|
-
const tileRowIndex = ty * targetColumns
|
|
29
|
-
const tileTop = ty << tileShift
|
|
30
|
-
|
|
31
|
-
for (let tx = x1; tx <= x2; tx++) {
|
|
32
|
-
const id = tileRowIndex + tx
|
|
33
|
-
let tile = this.lookup[id]
|
|
34
|
-
|
|
35
|
-
if (!tile) {
|
|
36
|
-
tile = this.tilePool.getTile(id, tx, ty)
|
|
37
|
-
this.lookup[id] = tile
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const tileLeft = tx << tileShift
|
|
41
|
-
|
|
42
|
-
const startX = Math.max(mask.x, tileLeft)
|
|
43
|
-
const endX = Math.min(mask.x + mask.w, tileLeft + this.config.tileSize)
|
|
44
|
-
const startY = Math.max(mask.y, tileTop)
|
|
45
|
-
const endY = Math.min(mask.y + mask.h, tileTop + this.config.tileSize)
|
|
46
|
-
|
|
47
|
-
// Passing 7 primitive arguments to avoid object allocation
|
|
48
|
-
callback(
|
|
49
|
-
tile,
|
|
50
|
-
startX,
|
|
51
|
-
startY,
|
|
52
|
-
endX - startX,
|
|
53
|
-
endY - startY,
|
|
54
|
-
startX - mask.x,
|
|
55
|
-
startY - mask.y,
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
writeColorBinaryMaskRect(color: Color32, mask: BinaryMaskRect): void {
|
|
62
|
-
const { tileShift, tileMask } = this.config
|
|
63
|
-
const maskData = mask.data
|
|
64
|
-
const maskW = mask.w
|
|
65
|
-
|
|
66
|
-
this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
|
|
67
|
-
const data32 = tile.data32
|
|
68
|
-
const startTileX = bX & tileMask
|
|
69
|
-
|
|
70
|
-
for (let i = 0; i < bH; i++) {
|
|
71
|
-
const tileY = (bY + i) & tileMask
|
|
72
|
-
const maskY = mY + i
|
|
73
|
-
const tileRowOffset = tileY << tileShift
|
|
74
|
-
const maskRowOffset = maskY * maskW
|
|
75
|
-
|
|
76
|
-
const destStart = tileRowOffset + startTileX
|
|
77
|
-
const maskStart = maskRowOffset + mX
|
|
78
|
-
|
|
79
|
-
for (let j = 0; j < bW; j++) {
|
|
80
|
-
if (maskData[maskStart + j]) {
|
|
81
|
-
data32[destStart + j] = color
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
writeColorAlphaMaskRect(color: Color32, mask: AlphaMaskRect): void {
|
|
89
|
-
const { tileShift, tileMask } = this.config
|
|
90
|
-
const maskData = mask.data
|
|
91
|
-
const maskW = mask.w
|
|
92
|
-
const colorRGB = color & 0x00ffffff
|
|
93
|
-
const colorA = color >>> 24
|
|
94
|
-
|
|
95
|
-
this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
|
|
96
|
-
const data32 = tile.data32
|
|
97
|
-
const startTileX = bX & tileMask
|
|
98
|
-
|
|
99
|
-
for (let i = 0; i < bH; i++) {
|
|
100
|
-
const tileY = (bY + i) & tileMask
|
|
101
|
-
const maskY = mY + i
|
|
102
|
-
const tileRowOffset = tileY << tileShift
|
|
103
|
-
const maskRowOffset = maskY * maskW
|
|
104
|
-
|
|
105
|
-
const destStart = tileRowOffset + startTileX
|
|
106
|
-
const maskStart = maskRowOffset + mX
|
|
107
|
-
|
|
108
|
-
for (let j = 0; j < bW; j++) {
|
|
109
|
-
const maskA = maskData[maskStart + j]
|
|
110
|
-
if (maskA > 0) {
|
|
111
|
-
const finalA = (colorA * maskA + 128) >> 8
|
|
112
|
-
data32[destStart + j] = (colorRGB | (finalA << 24)) >>> 0 as Color32
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
clear(): void {
|
|
120
|
-
this.tilePool.releaseTiles(this.lookup)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { Rect } from '../_types'
|
|
2
|
-
|
|
3
|
-
export function getCircleBrushOrPencilBounds(
|
|
4
|
-
centerX: number,
|
|
5
|
-
centerY: number,
|
|
6
|
-
brushSize: number,
|
|
7
|
-
targetWidth: number,
|
|
8
|
-
targetHeight: number,
|
|
9
|
-
out?: Rect,
|
|
10
|
-
): Rect {
|
|
11
|
-
const r = brushSize / 2
|
|
12
|
-
|
|
13
|
-
const minOffset = -Math.ceil(r - 0.5)
|
|
14
|
-
const maxOffset = Math.floor(r - 0.5)
|
|
15
|
-
|
|
16
|
-
// start is inclusive, end is exclusive
|
|
17
|
-
const startX = Math.floor(centerX + minOffset)
|
|
18
|
-
const startY = Math.floor(centerY + minOffset)
|
|
19
|
-
const endX = Math.floor(centerX + maxOffset) + 1
|
|
20
|
-
const endY = Math.floor(centerY + maxOffset) + 1
|
|
21
|
-
|
|
22
|
-
const res = out ?? {
|
|
23
|
-
x: 0,
|
|
24
|
-
y: 0,
|
|
25
|
-
w: 0,
|
|
26
|
-
h: 0,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const cStartX = Math.max(0, startX)
|
|
30
|
-
const cStartY = Math.max(0, startY)
|
|
31
|
-
const cEndX = Math.min(targetWidth, endX)
|
|
32
|
-
const cEndY = Math.min(targetHeight, endY)
|
|
33
|
-
|
|
34
|
-
const w = cEndX - cStartX
|
|
35
|
-
const h = cEndY - cStartY
|
|
36
|
-
|
|
37
|
-
res.x = cStartX
|
|
38
|
-
res.y = cStartY
|
|
39
|
-
res.w = w < 0 ? 0 : w
|
|
40
|
-
res.h = h < 0 ? 0 : h
|
|
41
|
-
|
|
42
|
-
return res
|
|
43
|
-
}
|