pixel-data-js 0.34.0 → 0.36.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.prod.cjs +188 -56
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +30 -14
- package/dist/index.prod.js +184 -55
- package/dist/index.prod.js.map +1 -1
- package/package.json +1 -1
- package/src/History/PixelAccumulator.ts +27 -6
- package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +2 -0
- package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +5 -1
- package/src/History/PixelMutator/mutatorApplyMask.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendAlphaMask.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendBinaryMask.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendColor.ts +2 -0
- package/src/History/PixelMutator/mutatorBlendColorPaintAlphaMask.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendColorPaintBinaryMask.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendColorPaintMask.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendColorPaintRect.ts +2 -0
- package/src/History/PixelMutator/mutatorBlendMask.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendPixel.ts +1 -0
- package/src/History/PixelMutator/mutatorBlendPixelData.ts +1 -0
- package/src/History/PixelMutator/mutatorClear.ts +2 -1
- package/src/History/PixelMutator/mutatorFill.ts +53 -37
- package/src/History/PixelMutator/mutatorFillBinaryMask.ts +2 -1
- package/src/History/PixelMutator/mutatorInvert.ts +3 -2
- package/src/History/PixelMutator.ts +1 -2
- package/src/Paint/PaintRect.ts +4 -1
- package/src/Paint/Render/PaintCursorRenderer.ts +40 -28
- package/src/Paint/_paint-types.ts +5 -0
- package/src/PixelData/_pixelData-types.ts +7 -0
- package/src/PixelData/cropPixelData.ts +36 -0
- package/src/PixelData/fillPixelData.ts +6 -6
- package/src/PixelData/trimPixelData.ts +49 -0
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -26,9 +26,17 @@ export class PixelAccumulator {
|
|
|
26
26
|
* @param x pixel x coordinate
|
|
27
27
|
* @param y pixel y coordinate
|
|
28
28
|
*/
|
|
29
|
-
storePixelBeforeState(x: number, y: number): DidChangeFn {
|
|
29
|
+
storePixelBeforeState(x: number, y: number): DidChangeFn | null {
|
|
30
30
|
const shift = this.config.tileShift
|
|
31
31
|
const columns = this.config.targetColumns
|
|
32
|
+
const targetWidth = this.config.target.w
|
|
33
|
+
const targetHeight = this.config.target.h
|
|
34
|
+
|
|
35
|
+
// Return a no-op if the pixel is outside the target boundaries
|
|
36
|
+
if (x < 0 || x >= targetWidth || y < 0 || y >= targetHeight) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
const tx = x >> shift
|
|
33
41
|
const ty = y >> shift
|
|
34
42
|
const id = ty * columns + tx
|
|
@@ -66,14 +74,27 @@ export class PixelAccumulator {
|
|
|
66
74
|
y: number,
|
|
67
75
|
w: number,
|
|
68
76
|
h: number,
|
|
69
|
-
): DidChangeFn {
|
|
77
|
+
): DidChangeFn | null {
|
|
70
78
|
const shift = this.config.tileShift
|
|
71
79
|
const columns = this.config.targetColumns
|
|
80
|
+
const targetWidth = this.config.target.w
|
|
81
|
+
const targetHeight = this.config.target.h
|
|
82
|
+
|
|
83
|
+
// Clamp the bounding box to the actual canvas dimensions
|
|
84
|
+
const clipX1 = Math.max(0, x)
|
|
85
|
+
const clipY1 = Math.max(0, y)
|
|
86
|
+
const clipX2 = Math.min(targetWidth - 1, x + w - 1)
|
|
87
|
+
const clipY2 = Math.min(targetHeight - 1, y + h - 1)
|
|
88
|
+
|
|
89
|
+
// If the region is entirely off-canvas, return a no-op
|
|
90
|
+
if (clipX2 < clipX1 || clipY2 < clipY1) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
72
93
|
|
|
73
|
-
const startX =
|
|
74
|
-
const startY =
|
|
75
|
-
const endX =
|
|
76
|
-
const endY =
|
|
94
|
+
const startX = clipX1 >> shift
|
|
95
|
+
const startY = clipY1 >> shift
|
|
96
|
+
const endX = clipX2 >> shift
|
|
97
|
+
const endY = clipY2 >> shift
|
|
77
98
|
|
|
78
99
|
const startIndex = this.beforeTiles.length
|
|
79
100
|
|
|
@@ -26,6 +26,8 @@ export const mutatorApplyAlphaMask = ((writer: PixelWriter<any>, deps: Deps = de
|
|
|
26
26
|
const h = opts?.h ?? target.h
|
|
27
27
|
|
|
28
28
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
29
|
+
if (!didChange) return false
|
|
30
|
+
|
|
29
31
|
return didChange(applyAlphaMaskToPixelData(target, mask, opts))
|
|
30
32
|
},
|
|
31
33
|
}
|
|
@@ -26,7 +26,11 @@ export const mutatorApplyBinaryMask = ((writer: PixelWriter<any>, deps: Deps = d
|
|
|
26
26
|
const h = opts?.h ?? target.h
|
|
27
27
|
|
|
28
28
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
29
|
-
|
|
29
|
+
if (!didChange) return false
|
|
30
|
+
|
|
31
|
+
const b = applyBinaryMaskToPixelData(target, mask, opts)
|
|
32
|
+
console.log({ b })
|
|
33
|
+
return didChange(b)
|
|
30
34
|
},
|
|
31
35
|
}
|
|
32
36
|
}) satisfies HistoryMutator<any, Deps>
|
|
@@ -29,6 +29,7 @@ export const mutatorApplyMask = ((writer: PixelWriter<any>, deps: Deps = default
|
|
|
29
29
|
const h = opts?.h ?? target.h
|
|
30
30
|
|
|
31
31
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
32
|
+
if (!didChange) return false
|
|
32
33
|
|
|
33
34
|
if (mask.type === MaskType.BINARY) {
|
|
34
35
|
return didChange(applyBinaryMaskToPixelData(target, mask, opts))
|
|
@@ -27,6 +27,7 @@ export const mutatorBlendAlphaMask = ((writer: PixelWriter<any>, deps: Partial<D
|
|
|
27
27
|
const h = opts?.h ?? src.h
|
|
28
28
|
|
|
29
29
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
30
|
+
if (!didChange) return false
|
|
30
31
|
|
|
31
32
|
return didChange(
|
|
32
33
|
blendPixelDataAlphaMask(writer.config.target, src, mask, opts),
|
|
@@ -27,6 +27,7 @@ export const mutatorBlendBinaryMask = ((writer: PixelWriter<any>, deps: Partial<
|
|
|
27
27
|
const h = opts?.h ?? src.h
|
|
28
28
|
|
|
29
29
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
30
|
+
if (!didChange) return false
|
|
30
31
|
|
|
31
32
|
return didChange(
|
|
32
33
|
blendPixelDataBinaryMask(writer.config.target, src, mask, opts),
|
|
@@ -25,6 +25,8 @@ export const mutatorBlendColor = ((writer: PixelWriter<any>, deps: Deps = defaul
|
|
|
25
25
|
const h = opts?.h ?? target.h
|
|
26
26
|
|
|
27
27
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
28
|
+
if (!didChange) return false
|
|
29
|
+
|
|
28
30
|
return didChange(
|
|
29
31
|
blendColorPixelData(target, color, opts),
|
|
30
32
|
)
|
|
@@ -37,6 +37,7 @@ export const mutatorBlendColorPaintAlphaMask = ((writer: PixelWriter<any>, deps:
|
|
|
37
37
|
const ty = y + mask.centerOffsetY
|
|
38
38
|
|
|
39
39
|
const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h)
|
|
40
|
+
if (!didChange) return false
|
|
40
41
|
|
|
41
42
|
OPTS.x = tx
|
|
42
43
|
OPTS.y = ty
|
|
@@ -37,6 +37,7 @@ export const mutatorBlendColorPaintBinaryMask = ((writer: PixelWriter<any>, deps
|
|
|
37
37
|
const ty = y + mask.centerOffsetY
|
|
38
38
|
|
|
39
39
|
const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h)
|
|
40
|
+
if (!didChange) return false
|
|
40
41
|
|
|
41
42
|
OPTS.x = tx
|
|
42
43
|
OPTS.y = ty
|
|
@@ -41,6 +41,7 @@ export const mutatorBlendColorPaintMask = ((writer: PixelWriter<any>, deps: Part
|
|
|
41
41
|
const ty = y + mask.centerOffsetY
|
|
42
42
|
|
|
43
43
|
const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h)
|
|
44
|
+
if (!didChange) return false
|
|
44
45
|
|
|
45
46
|
OPTS.x = tx
|
|
46
47
|
OPTS.y = ty
|
|
@@ -43,6 +43,8 @@ export const mutatorBlendColorPaintRect = ((writer: PixelWriter<any>, deps: Deps
|
|
|
43
43
|
OPTS.alpha = alpha
|
|
44
44
|
|
|
45
45
|
const didChange = writer.accumulator.storeRegionBeforeState(topLeftX, topLeftY, brushWidth, brushHeight)
|
|
46
|
+
if (!didChange) return false
|
|
47
|
+
|
|
46
48
|
return didChange(
|
|
47
49
|
blendColorPixelData(
|
|
48
50
|
target,
|
|
@@ -29,6 +29,7 @@ export const mutatorBlendMask = ((writer: PixelWriter<any>, deps: Partial<Deps>
|
|
|
29
29
|
const h = opts?.h ?? src.h
|
|
30
30
|
|
|
31
31
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
32
|
+
if (!didChange) return false
|
|
32
33
|
|
|
33
34
|
if (mask.type === MaskType.BINARY) {
|
|
34
35
|
return didChange(
|
|
@@ -23,6 +23,7 @@ export const mutatorBlendPixel = ((writer: PixelWriter<any>, deps: Partial<Deps>
|
|
|
23
23
|
): boolean {
|
|
24
24
|
|
|
25
25
|
const didChange = writer.accumulator.storePixelBeforeState(x, y)
|
|
26
|
+
if (!didChange) return false
|
|
26
27
|
|
|
27
28
|
return didChange(
|
|
28
29
|
blendPixel(writer.config.target, x, y, color, alpha, blendFn),
|
|
@@ -25,6 +25,7 @@ export const mutatorBlendPixelData = ((writer: PixelWriter<any>, deps: Partial<D
|
|
|
25
25
|
const h = opts?.h ?? src.h
|
|
26
26
|
|
|
27
27
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
28
|
+
if (!didChange) return false
|
|
28
29
|
|
|
29
30
|
return didChange(
|
|
30
31
|
blendPixelData(writer.config.target, src, opts),
|
|
@@ -18,7 +18,7 @@ export const mutatorClear = ((writer: PixelWriter<any>, deps: Deps = defaults) =
|
|
|
18
18
|
return {
|
|
19
19
|
clear(
|
|
20
20
|
rect?: Partial<Rect>,
|
|
21
|
-
) {
|
|
21
|
+
): boolean {
|
|
22
22
|
const target = writer.config.target
|
|
23
23
|
const x = rect?.x ?? 0
|
|
24
24
|
const y = rect?.y ?? 0
|
|
@@ -26,6 +26,7 @@ export const mutatorClear = ((writer: PixelWriter<any>, deps: Deps = defaults) =
|
|
|
26
26
|
const h = rect?.h ?? target.h
|
|
27
27
|
|
|
28
28
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
29
|
+
if (!didChange) return false
|
|
29
30
|
return didChange(
|
|
30
31
|
fillPixelData(target, 0 as Color32, x, y, w, h),
|
|
31
32
|
)
|
|
@@ -14,44 +14,60 @@ export const mutatorFill = ((writer: PixelWriter<any>, deps: Deps = defaults) =>
|
|
|
14
14
|
fillPixelData = defaults.fillPixelData,
|
|
15
15
|
} = deps
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
fill(
|
|
19
|
-
color: Color32,
|
|
20
|
-
x = 0,
|
|
21
|
-
y = 0,
|
|
22
|
-
w = writer.config.target.w,
|
|
23
|
-
h = writer.config.target.h,
|
|
24
|
-
) {
|
|
25
|
-
const target = writer.config.target
|
|
26
|
-
|
|
27
|
-
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
28
|
-
return didChange(
|
|
29
|
-
fillPixelData(target, color, x, y, w, h),
|
|
30
|
-
)
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
}) satisfies HistoryMutator<any, Deps>
|
|
17
|
+
const config = writer.config
|
|
34
18
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
function fill(
|
|
20
|
+
color: Color32,
|
|
21
|
+
rect?: Partial<Rect>,
|
|
22
|
+
): boolean
|
|
23
|
+
|
|
24
|
+
function fill(
|
|
25
|
+
color: Color32,
|
|
26
|
+
x: number,
|
|
27
|
+
y: number,
|
|
28
|
+
w: number,
|
|
29
|
+
h: number,
|
|
30
|
+
): boolean
|
|
31
|
+
function fill(
|
|
32
|
+
color: Color32,
|
|
33
|
+
_x?: Partial<Rect> | number,
|
|
34
|
+
_y?: number,
|
|
35
|
+
_w?: number,
|
|
36
|
+
_h?: number,
|
|
37
|
+
): boolean {
|
|
38
|
+
const target = config.target
|
|
39
|
+
|
|
40
|
+
const dstW = target.w
|
|
41
|
+
const dstH = target.h
|
|
42
|
+
|
|
43
|
+
let x: number
|
|
44
|
+
let y: number
|
|
45
|
+
let w: number
|
|
46
|
+
let h: number
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
if (typeof _x === 'number') {
|
|
49
|
+
x = _x
|
|
50
|
+
y = _y!
|
|
51
|
+
w = _w!
|
|
52
|
+
h = _h!
|
|
53
|
+
} else if (typeof _x === 'object') {
|
|
54
|
+
x = _x.x ?? 0
|
|
55
|
+
y = _x.y ?? 0
|
|
56
|
+
w = _x.w ?? dstW
|
|
57
|
+
h = _x.h ?? dstH
|
|
58
|
+
} else {
|
|
59
|
+
x = 0
|
|
60
|
+
y = 0
|
|
61
|
+
w = dstW
|
|
62
|
+
h = dstH
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
66
|
+
if (!didChange) return false
|
|
67
|
+
return didChange(
|
|
68
|
+
fillPixelData(target, color, x, y, w, h),
|
|
69
|
+
)
|
|
55
70
|
}
|
|
56
|
-
}) satisfies HistoryMutator<any, Deps>
|
|
57
71
|
|
|
72
|
+
return { fill }
|
|
73
|
+
}) satisfies HistoryMutator<any, Deps>
|
|
@@ -20,8 +20,9 @@ export const mutatorFillBinaryMask = ((writer: PixelWriter<any>, deps: Deps = de
|
|
|
20
20
|
mask: BinaryMask,
|
|
21
21
|
x = 0,
|
|
22
22
|
y = 0,
|
|
23
|
-
) {
|
|
23
|
+
): boolean {
|
|
24
24
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h)
|
|
25
|
+
if (!didChange) return false
|
|
25
26
|
return didChange(
|
|
26
27
|
fillPixelDataBinaryMask(writer.config.target, color, mask, x, y),
|
|
27
28
|
)
|
|
@@ -14,7 +14,7 @@ export const mutatorInvert = ((writer: PixelWriter<any>, deps: Deps = defaults)
|
|
|
14
14
|
} = deps
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
|
-
invert(opts?: PixelMutateOptions) {
|
|
17
|
+
invert(opts?: PixelMutateOptions): boolean {
|
|
18
18
|
const target = writer.config.target
|
|
19
19
|
const x = opts?.x ?? 0
|
|
20
20
|
const y = opts?.y ?? 0
|
|
@@ -22,8 +22,9 @@ export const mutatorInvert = ((writer: PixelWriter<any>, deps: Deps = defaults)
|
|
|
22
22
|
const h = opts?.h ?? target.h
|
|
23
23
|
|
|
24
24
|
const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h)
|
|
25
|
+
if (!didChange) return false
|
|
25
26
|
|
|
26
|
-
return didChange(
|
|
27
|
+
return didChange?.(
|
|
27
28
|
invertPixelData(target, opts),
|
|
28
29
|
)
|
|
29
30
|
},
|
|
@@ -12,7 +12,7 @@ import { mutatorBlendMask } from './PixelMutator/mutatorBlendMask'
|
|
|
12
12
|
import { mutatorBlendPixel } from './PixelMutator/mutatorBlendPixel'
|
|
13
13
|
import { mutatorBlendPixelData } from './PixelMutator/mutatorBlendPixelData'
|
|
14
14
|
import { mutatorClear } from './PixelMutator/mutatorClear'
|
|
15
|
-
import { mutatorFill
|
|
15
|
+
import { mutatorFill } from './PixelMutator/mutatorFill'
|
|
16
16
|
import { mutatorFillBinaryMask } from './PixelMutator/mutatorFillBinaryMask'
|
|
17
17
|
import { mutatorInvert } from './PixelMutator/mutatorInvert'
|
|
18
18
|
import type { PixelWriter } from './PixelWriter'
|
|
@@ -36,7 +36,6 @@ export function makeFullPixelMutator(writer: PixelWriter<any>) {
|
|
|
36
36
|
...mutatorClear(writer),
|
|
37
37
|
...mutatorFill(writer),
|
|
38
38
|
...mutatorFillBinaryMask(writer),
|
|
39
|
-
...mutatorFillRect(writer),
|
|
40
39
|
...mutatorInvert(writer),
|
|
41
40
|
}
|
|
42
41
|
}
|
package/src/Paint/PaintRect.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { _macro_paintRectCenterOffset } from '../Internal/macros'
|
|
2
|
-
import type
|
|
2
|
+
import { PaintMaskOutline, type PaintRect } from './_paint-types'
|
|
3
3
|
|
|
4
4
|
export function makePaintRect(w: number, h: number): PaintRect {
|
|
5
5
|
return {
|
|
6
|
+
type: null,
|
|
7
|
+
outlineType: PaintMaskOutline.RECT,
|
|
8
|
+
data: null,
|
|
6
9
|
w,
|
|
7
10
|
h,
|
|
8
11
|
centerOffsetX: _macro_paintRectCenterOffset(w),
|
|
@@ -11,7 +11,7 @@ import { makeRectBinaryMaskOutline } from '../../Mask/BinaryMask/makeRectBinaryM
|
|
|
11
11
|
import { fillPixelDataBinaryMask } from '../../PixelData/fillPixelDataBinaryMask'
|
|
12
12
|
import { makeReusablePixelData } from '../../PixelData/ReusablePixelData'
|
|
13
13
|
import type { Rect } from '../../Rect/_rect-types'
|
|
14
|
-
import { type
|
|
14
|
+
import { type PaintBrush, PaintMaskOutline } from '../_paint-types'
|
|
15
15
|
|
|
16
16
|
export type PaintCursorRenderer = ReturnType<typeof makePaintCursorRenderer>
|
|
17
17
|
|
|
@@ -27,39 +27,42 @@ export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenC
|
|
|
27
27
|
let _color = packColor(0, 255, 255, 255)
|
|
28
28
|
let _scale = 1
|
|
29
29
|
|
|
30
|
-
let
|
|
31
|
-
type:
|
|
30
|
+
let currentBrush: PaintBrush = {
|
|
31
|
+
type: null,
|
|
32
32
|
outlineType: PaintMaskOutline.RECT,
|
|
33
33
|
w: 1,
|
|
34
34
|
h: 1,
|
|
35
35
|
centerOffsetX: _macro_paintRectCenterOffset(10),
|
|
36
36
|
centerOffsetY: _macro_paintRectCenterOffset(10),
|
|
37
|
-
|
|
37
|
+
data: null,
|
|
38
|
+
}
|
|
38
39
|
|
|
39
40
|
let outline: BinaryMask
|
|
40
41
|
|
|
41
|
-
function update(paintMask?:
|
|
42
|
-
|
|
42
|
+
function update(paintMask?: PaintBrush, scale?: number, color?: Color32, alphaThreshold = 127) {
|
|
43
|
+
currentBrush = paintMask ?? currentBrush
|
|
43
44
|
|
|
44
45
|
_scale = scale ?? _scale
|
|
45
46
|
_color = color ?? _color
|
|
46
47
|
|
|
47
48
|
updateBuffer(
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
currentBrush.w * _scale + 2 * _scale,
|
|
50
|
+
currentBrush.h * _scale + 2 * _scale,
|
|
50
51
|
)
|
|
51
52
|
|
|
52
|
-
if (
|
|
53
|
-
if (
|
|
54
|
-
outline = makeCircleBinaryMaskOutline(
|
|
55
|
-
} else if (
|
|
56
|
-
outline = makeRectBinaryMaskOutline(
|
|
57
|
-
} else if (
|
|
58
|
-
outline = makeBinaryMaskOutline(
|
|
53
|
+
if (currentBrush.type === MaskType.BINARY) {
|
|
54
|
+
if (currentBrush.outlineType === PaintMaskOutline.CIRCLE) {
|
|
55
|
+
outline = makeCircleBinaryMaskOutline(currentBrush.w, _scale)
|
|
56
|
+
} else if (currentBrush.outlineType === PaintMaskOutline.RECT) {
|
|
57
|
+
outline = makeRectBinaryMaskOutline(currentBrush.w, currentBrush.h, _scale)
|
|
58
|
+
} else if (currentBrush.outlineType === PaintMaskOutline.MASKED) {
|
|
59
|
+
outline = makeBinaryMaskOutline(currentBrush, _scale)
|
|
59
60
|
}
|
|
60
|
-
} else if (
|
|
61
|
-
const mask = makeBinaryMaskFromAlphaMask(
|
|
61
|
+
} else if (currentBrush.type === MaskType.ALPHA) {
|
|
62
|
+
const mask = makeBinaryMaskFromAlphaMask(currentBrush, alphaThreshold)
|
|
62
63
|
outline = makeBinaryMaskOutline(mask, _scale)
|
|
64
|
+
} else {
|
|
65
|
+
outline = makeRectBinaryMaskOutline(currentBrush.w, currentBrush.h, _scale)
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
const pixelData = getPixelData(outline.w, outline.h)
|
|
@@ -70,10 +73,10 @@ export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenC
|
|
|
70
73
|
const boundsScratch = { x: 0, y: 0, w: 0, h: 0 }
|
|
71
74
|
|
|
72
75
|
function getBounds(centerX: number, centerY: number): Rect {
|
|
73
|
-
boundsScratch.x = centerX +
|
|
74
|
-
boundsScratch.y = centerY +
|
|
75
|
-
boundsScratch.w =
|
|
76
|
-
boundsScratch.h =
|
|
76
|
+
boundsScratch.x = centerX + currentBrush.centerOffsetX
|
|
77
|
+
boundsScratch.y = centerY + currentBrush.centerOffsetY
|
|
78
|
+
boundsScratch.w = currentBrush.w
|
|
79
|
+
boundsScratch.h = currentBrush.h
|
|
77
80
|
|
|
78
81
|
return boundsScratch
|
|
79
82
|
}
|
|
@@ -81,10 +84,10 @@ export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenC
|
|
|
81
84
|
const boundsScaledScratch = { x: 0, y: 0, w: 0, h: 0 }
|
|
82
85
|
|
|
83
86
|
function getOutlineBoundsScaled(centerX: number, centerY: number): Rect {
|
|
84
|
-
boundsScaledScratch.x = centerX * _scale +
|
|
85
|
-
boundsScaledScratch.y = centerY * _scale +
|
|
86
|
-
boundsScaledScratch.w =
|
|
87
|
-
boundsScaledScratch.h =
|
|
87
|
+
boundsScaledScratch.x = centerX * _scale + currentBrush.centerOffsetX * _scale - 1
|
|
88
|
+
boundsScaledScratch.y = centerY * _scale + currentBrush.centerOffsetY * _scale - 1
|
|
89
|
+
boundsScaledScratch.w = currentBrush.w * _scale
|
|
90
|
+
boundsScaledScratch.h = currentBrush.h * _scale
|
|
88
91
|
|
|
89
92
|
return boundsScaledScratch
|
|
90
93
|
}
|
|
@@ -94,17 +97,25 @@ export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenC
|
|
|
94
97
|
centerX: number,
|
|
95
98
|
centerY: number,
|
|
96
99
|
) {
|
|
97
|
-
const dx = centerX * _scale +
|
|
98
|
-
const dy = centerY * _scale +
|
|
100
|
+
const dx = centerX * _scale + currentBrush.centerOffsetX * _scale - 1
|
|
101
|
+
const dy = centerY * _scale + currentBrush.centerOffsetY * _scale - 1
|
|
99
102
|
|
|
100
103
|
drawCtx.drawImage(canvas, Math.floor(dx), Math.floor(dy))
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
function drawRaw(
|
|
107
|
+
drawCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
|
108
|
+
x: number,
|
|
109
|
+
y: number,
|
|
110
|
+
) {
|
|
111
|
+
drawCtx.drawImage(canvas, Math.floor(x * _scale), Math.floor(y * _scale))
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
function getSettings() {
|
|
104
115
|
return {
|
|
105
116
|
color: _color,
|
|
106
117
|
scale: _scale,
|
|
107
|
-
|
|
118
|
+
currentBrush,
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
|
|
@@ -113,6 +124,7 @@ export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenC
|
|
|
113
124
|
getBounds,
|
|
114
125
|
getBoundsScaled: getOutlineBoundsScaled,
|
|
115
126
|
draw,
|
|
127
|
+
drawRaw,
|
|
116
128
|
getSettings,
|
|
117
129
|
}
|
|
118
130
|
}
|
|
@@ -20,9 +20,14 @@ export interface PaintBinaryMask<T extends PaintMaskOutline = PaintMaskOutline>
|
|
|
20
20
|
|
|
21
21
|
export type PaintMask = PaintAlphaMask<any> | PaintBinaryMask<any>
|
|
22
22
|
|
|
23
|
+
export type PaintBrush = PaintMask | PaintRect
|
|
24
|
+
|
|
23
25
|
export interface PaintRect {
|
|
26
|
+
type: null,
|
|
27
|
+
readonly outlineType: PaintMaskOutline.RECT
|
|
24
28
|
w: number,
|
|
25
29
|
h: number,
|
|
26
30
|
centerOffsetX: number,
|
|
27
31
|
centerOffsetY: number,
|
|
32
|
+
data: null,
|
|
28
33
|
}
|
|
@@ -15,3 +15,10 @@ export interface MutablePixelData32 {
|
|
|
15
15
|
export interface PixelData<T extends ImageDataLike = ImageData> extends PixelData32 {
|
|
16
16
|
readonly imageData: T
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
export interface MutablePixelData<T extends ImageDataLike = ImageData> extends PixelData32 {
|
|
20
|
+
imageData: T
|
|
21
|
+
data: Uint32Array
|
|
22
|
+
w: number
|
|
23
|
+
h: number
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MutablePixelData, PixelData, PixelData32 } from './_pixelData-types'
|
|
2
|
+
import { setPixelData } from './PixelData'
|
|
3
|
+
|
|
4
|
+
export function cropPixelData(src: PixelData32, x: number, y: number, w: number, h: number, out?: MutablePixelData): PixelData {
|
|
5
|
+
const cx = Math.max(x, 0)
|
|
6
|
+
const cy = Math.max(y, 0)
|
|
7
|
+
const cw = Math.min(x + w, src.w) - cx
|
|
8
|
+
const ch = Math.min(y + h, src.h) - cy
|
|
9
|
+
|
|
10
|
+
if (cw <= 0 || ch <= 0) {
|
|
11
|
+
throw new Error(`Crop [${x},${y} ${w}x${h}] does not overlap PixelData [${src.w}x${src.h}]`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const cropped = new ImageData(cw, ch)
|
|
15
|
+
|
|
16
|
+
let dst32: Uint32Array
|
|
17
|
+
if (out) {
|
|
18
|
+
setPixelData(out, cropped)
|
|
19
|
+
dst32 = out.data
|
|
20
|
+
} else {
|
|
21
|
+
dst32 = new Uint32Array(cropped.data.buffer)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (let row = 0; row < ch; row++) {
|
|
25
|
+
const srcOffset = ((cy + row) * src.w) + cx
|
|
26
|
+
const dstOffset = row * cw
|
|
27
|
+
dst32.set(src.data.subarray(srcOffset, srcOffset + cw), dstOffset)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return out ?? {
|
|
31
|
+
data: dst32,
|
|
32
|
+
imageData: cropped,
|
|
33
|
+
w: cw,
|
|
34
|
+
h: ch,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -47,16 +47,16 @@ export function fillPixelData(
|
|
|
47
47
|
let w: number
|
|
48
48
|
let h: number
|
|
49
49
|
|
|
50
|
-
if (typeof _x === '
|
|
51
|
-
x = _x.x ?? 0
|
|
52
|
-
y = _x.y ?? 0
|
|
53
|
-
w = _x.w ?? dstW
|
|
54
|
-
h = _x.h ?? dstH
|
|
55
|
-
} else if (typeof _x === 'number') {
|
|
50
|
+
if (typeof _x === 'number') {
|
|
56
51
|
x = _x
|
|
57
52
|
y = _y!
|
|
58
53
|
w = _w!
|
|
59
54
|
h = _h!
|
|
55
|
+
} else if (typeof _x === 'object') {
|
|
56
|
+
x = _x.x ?? 0
|
|
57
|
+
y = _x.y ?? 0
|
|
58
|
+
w = _x.w ?? dstW
|
|
59
|
+
h = _x.h ?? dstH
|
|
60
60
|
} else {
|
|
61
61
|
x = 0
|
|
62
62
|
y = 0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Rect } from '../Rect/_rect-types'
|
|
2
|
+
import type { MutablePixelData, PixelData, PixelData32 } from './_pixelData-types'
|
|
3
|
+
import { cropPixelData } from './cropPixelData'
|
|
4
|
+
|
|
5
|
+
export function getPixelDataTransparentTrimmedBounds(target: PixelData32): Rect | null {
|
|
6
|
+
let minX = target.w
|
|
7
|
+
let minY = target.h
|
|
8
|
+
let maxX = -1
|
|
9
|
+
let maxY = -1
|
|
10
|
+
|
|
11
|
+
for (let y = 0; y < target.h; y++) {
|
|
12
|
+
for (let x = 0; x < target.w; x++) {
|
|
13
|
+
const alpha = target.data[y * target.w + x] >>> 24
|
|
14
|
+
if (alpha !== 0) {
|
|
15
|
+
if (x < minX) minX = x
|
|
16
|
+
if (x > maxX) maxX = x
|
|
17
|
+
if (y < minY) minY = y
|
|
18
|
+
if (y > maxY) maxY = y
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (maxX === -1) return null
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
x: minX,
|
|
27
|
+
y: minY,
|
|
28
|
+
w: maxX - minX + 1,
|
|
29
|
+
h: maxY - minY + 1,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function trimTransparentPixelData(target: PixelData32): PixelData {
|
|
34
|
+
const r = getPixelDataTransparentTrimmedBounds(target)
|
|
35
|
+
if (!r) {
|
|
36
|
+
throw new Error('PixelData is fully transparent — no crop bounds found')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return cropPixelData(target, r.x, r.y, r.w, r.h)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function trimTransparentPixelDataInPlace(target: MutablePixelData) {
|
|
43
|
+
const r = getPixelDataTransparentTrimmedBounds(target)
|
|
44
|
+
if (!r) {
|
|
45
|
+
throw new Error('PixelData is fully transparent — no crop bounds found')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
cropPixelData(target, r.x, r.y, r.w, r.h, target)
|
|
49
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -152,6 +152,7 @@ export * from './PixelData/blendPixelDataMask'
|
|
|
152
152
|
export * from './PixelData/blendPixelDataPaintBuffer'
|
|
153
153
|
export * from './PixelData/clearPixelDataFast'
|
|
154
154
|
export * from './PixelData/copyPixelData'
|
|
155
|
+
export * from './PixelData/cropPixelData'
|
|
155
156
|
export * from './PixelData/extractPixelData'
|
|
156
157
|
export * from './PixelData/extractPixelDataBuffer'
|
|
157
158
|
export * from './PixelData/fillPixelData'
|
|
@@ -165,6 +166,7 @@ export * from './PixelData/resamplePixelData'
|
|
|
165
166
|
export * from './PixelData/resizePixelData'
|
|
166
167
|
export * from './PixelData/ReusablePixelData'
|
|
167
168
|
export * from './PixelData/rotatePixelData'
|
|
169
|
+
export * from './PixelData/trimPixelData'
|
|
168
170
|
export * from './PixelData/uInt32ArrayToPixelData'
|
|
169
171
|
export * from './PixelData/writePaintBufferToPixelData'
|
|
170
172
|
export * from './PixelData/writePixelData'
|