pixel-data-js 0.18.0 → 0.19.1
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 +6 -1
- package/dist/index.dev.cjs +2723 -1487
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +2690 -1481
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +2723 -1487
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +400 -246
- package/dist/index.prod.js +2690 -1481
- package/dist/index.prod.js.map +1 -1
- package/package.json +22 -7
- package/src/Algorithm/forEachLinePoint.ts +36 -0
- package/src/BlendModes/BlendModeRegistry.ts +2 -0
- package/src/BlendModes/blend-modes-fast.ts +2 -2
- package/src/BlendModes/blend-modes-perfect.ts +5 -4
- package/src/BlendModes/toBlendModeIndexAndName.ts +41 -0
- package/src/History/PixelAccumulator.ts +2 -2
- package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +30 -0
- package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +30 -0
- package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +23 -9
- package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +138 -0
- package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +59 -0
- package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +131 -0
- package/src/History/PixelMutator/mutatorApplyRectBrush.ts +20 -7
- package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +169 -0
- package/src/History/PixelMutator/mutatorApplyRectPencil.ts +62 -0
- package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +149 -0
- package/src/History/PixelMutator/mutatorBlendColor.ts +9 -4
- package/src/History/PixelMutator/mutatorBlendPixelData.ts +10 -5
- package/src/History/PixelMutator/mutatorClear.ts +27 -0
- package/src/History/PixelMutator/{mutatorFillPixelData.ts → mutatorFill.ts} +9 -3
- package/src/History/PixelMutator/mutatorInvert.ts +10 -3
- package/src/History/PixelMutator.ts +23 -3
- package/src/History/PixelPatchTiles.ts +2 -2
- package/src/History/PixelWriter.ts +3 -3
- package/src/ImageData/ImageDataLike.ts +13 -0
- package/src/ImageData/extractImageDataBuffer.ts +22 -15
- package/src/ImageData/serialization.ts +4 -4
- package/src/ImageData/uInt32ArrayToImageData.ts +29 -0
- package/src/ImageData/writeImageData.ts +26 -18
- package/src/ImageData/writeImageDataBuffer.ts +30 -18
- package/src/IndexedImage/indexedImageToAverageColor.ts +1 -1
- package/src/Internal/resolveClipping.ts +140 -0
- package/src/Mask/applyBinaryMaskToAlphaMask.ts +89 -0
- package/src/Mask/copyMask.ts +1 -3
- package/src/Mask/mergeAlphaMasks.ts +81 -0
- package/src/Mask/mergeBinaryMasks.ts +89 -0
- package/src/PixelData/PixelBuffer32.ts +28 -0
- package/src/PixelData/PixelData.ts +38 -33
- package/src/PixelData/applyAlphaMaskToPixelData.ts +119 -0
- package/src/PixelData/applyBinaryMaskToPixelData.ts +111 -0
- package/src/PixelData/applyCircleBrushToPixelData.ts +31 -56
- package/src/PixelData/applyRectBrushToPixelData.ts +39 -71
- package/src/PixelData/blendColorPixelData.ts +18 -111
- package/src/PixelData/blendColorPixelDataAlphaMask.ts +111 -0
- package/src/PixelData/blendColorPixelDataBinaryMask.ts +89 -0
- package/src/PixelData/blendPixelData.ts +19 -107
- package/src/PixelData/blendPixelDataAlphaMask.ts +149 -0
- package/src/PixelData/blendPixelDataBinaryMask.ts +133 -0
- package/src/PixelData/clearPixelData.ts +2 -3
- package/src/PixelData/extractPixelData.ts +4 -4
- package/src/PixelData/extractPixelDataBuffer.ts +38 -26
- package/src/PixelData/fillPixelData.ts +18 -20
- package/src/PixelData/invertPixelData.ts +13 -21
- package/src/PixelData/pixelDataToAlphaMask.ts +2 -3
- package/src/PixelData/reflectPixelData.ts +3 -3
- package/src/PixelData/resamplePixelData.ts +2 -6
- package/src/PixelData/writePixelDataBuffer.ts +34 -20
- package/src/Rect/getCircleBrushOrPencilBounds.ts +43 -0
- package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +24 -0
- package/src/Rect/getRectBrushOrPencilBounds.ts +38 -0
- package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +26 -0
- package/src/_types.ts +49 -33
- package/src/index.ts +47 -11
- package/src/History/PixelMutator/mutatorApplyMask.ts +0 -20
- package/src/Mask/mergeMasks.ts +0 -100
- package/src/PixelData/applyMaskToPixelData.ts +0 -129
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ImageDataLike, Rect } from '../_types'
|
|
2
|
+
import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
|
|
3
|
+
|
|
4
|
+
const SCRATCH_BLIT = makeClippedBlit()
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Extracts a specific rectangular region of pixels from a larger {@link ImageDataLike}
|
|
@@ -45,24 +48,28 @@ export function extractImageDataBuffer(
|
|
|
45
48
|
if (w <= 0 || h <= 0) return new Uint8ClampedArray(0)
|
|
46
49
|
const out = new Uint8ClampedArray(w * h * 4)
|
|
47
50
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const clip = resolveBlitClipping(
|
|
52
|
+
0,
|
|
53
|
+
0,
|
|
54
|
+
x,
|
|
55
|
+
y,
|
|
56
|
+
w,
|
|
57
|
+
h,
|
|
58
|
+
w,
|
|
59
|
+
h,
|
|
60
|
+
srcW,
|
|
61
|
+
srcH,
|
|
62
|
+
SCRATCH_BLIT,
|
|
63
|
+
)
|
|
52
64
|
|
|
53
|
-
|
|
54
|
-
if (x1 <= x0 || y1 <= y0) return out
|
|
65
|
+
if (!clip.inBounds) return out
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const srcRow = y0 + row
|
|
59
|
-
const srcStart = (srcRow * srcW + x0) * 4
|
|
60
|
-
const rowLen = (x1 - x0) * 4
|
|
67
|
+
const { x: dstX, y: dstY, sx: srcX, sy: srcY, w: copyW, h: copyH } = clip
|
|
68
|
+
const rowLen = copyW * 4
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const dstStart = (dstRow * w + dstCol) * 4
|
|
70
|
+
for (let row = 0; row < copyH; row++) {
|
|
71
|
+
const srcStart = ((srcY + row) * srcW + srcX) * 4
|
|
72
|
+
const dstStart = ((dstY + row) * w + dstX) * 4
|
|
66
73
|
|
|
67
74
|
// Perform the high-speed bulk copy
|
|
68
75
|
out.set(src.subarray(srcStart, srcStart + rowLen), dstStart)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Base64EncodedUInt8Array, ImageDataLike, SerializedImageData } from '../_types'
|
|
2
2
|
|
|
3
3
|
export function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array {
|
|
4
|
-
const uint8 = new Uint8Array(buffer)
|
|
5
|
-
const decoder = new TextDecoder('latin1')
|
|
6
|
-
const binary = decoder.decode(uint8)
|
|
4
|
+
const uint8 = new Uint8Array(buffer)
|
|
5
|
+
const decoder = new TextDecoder('latin1')
|
|
6
|
+
const binary = decoder.decode(uint8)
|
|
7
7
|
|
|
8
|
-
return btoa(binary) as Base64EncodedUInt8Array
|
|
8
|
+
return btoa(binary) as Base64EncodedUInt8Array
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray<ArrayBuffer> {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ImageDataLike } from '../_types'
|
|
2
|
+
|
|
3
|
+
export function uInt32ArrayToImageData(
|
|
4
|
+
data: Uint32Array,
|
|
5
|
+
width: number,
|
|
6
|
+
height: number,
|
|
7
|
+
): ImageData {
|
|
8
|
+
const buffer = data.buffer as ArrayBuffer
|
|
9
|
+
const byteOffset = data.byteOffset
|
|
10
|
+
const byteLength = data.byteLength
|
|
11
|
+
const clampedArray = new Uint8ClampedArray(buffer, byteOffset, byteLength)
|
|
12
|
+
return new ImageData(clampedArray, width, height)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function uInt32ArrayToImageDataLike(
|
|
16
|
+
data: Uint32Array,
|
|
17
|
+
width: number,
|
|
18
|
+
height: number,
|
|
19
|
+
): ImageDataLike {
|
|
20
|
+
const buffer = data.buffer
|
|
21
|
+
const byteOffset = data.byteOffset
|
|
22
|
+
const byteLength = data.byteLength
|
|
23
|
+
const clampedArray = new Uint8ClampedArray(buffer, byteOffset, byteLength)
|
|
24
|
+
return {
|
|
25
|
+
width,
|
|
26
|
+
height,
|
|
27
|
+
data: clampedArray,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { MaskType } from '../_types'
|
|
2
|
+
import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
|
|
3
|
+
|
|
4
|
+
const SCRATCH_BLIT = makeClippedBlit()
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Writes image data from a source to a target with support for clipping and alpha masking.
|
|
@@ -32,30 +35,35 @@ export function writeImageData(
|
|
|
32
35
|
const srcW = source.width
|
|
33
36
|
const srcData = source.data
|
|
34
37
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const clip = resolveBlitClipping(
|
|
39
|
+
x, y, sx, sy, sw, sh,
|
|
40
|
+
dstW, dstH, srcW, source.height,
|
|
41
|
+
SCRATCH_BLIT,
|
|
42
|
+
)
|
|
39
43
|
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
if (!clip.inBounds) return
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
x: dstX,
|
|
48
|
+
y: dstY,
|
|
49
|
+
sx: srcX,
|
|
50
|
+
sy: srcY,
|
|
51
|
+
w: copyW,
|
|
52
|
+
h: copyH,
|
|
53
|
+
} = clip
|
|
43
54
|
|
|
44
55
|
const useMask = !!mask
|
|
45
|
-
const rowCount = y1 - y0
|
|
46
|
-
const rowLenPixels = x1 - x0
|
|
47
56
|
|
|
48
|
-
for (let row = 0; row <
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const srcXBase = sx + (x0 - x)
|
|
57
|
+
for (let row = 0; row < copyH; row++) {
|
|
58
|
+
const currentDstY = dstY + row
|
|
59
|
+
const currentSrcY = srcY + row
|
|
52
60
|
|
|
53
|
-
const dstStart = (
|
|
54
|
-
const srcStart = (
|
|
61
|
+
const dstStart = (currentDstY * dstW + dstX) * 4
|
|
62
|
+
const srcStart = (currentSrcY * srcW + srcX) * 4
|
|
55
63
|
|
|
56
64
|
if (useMask && mask) {
|
|
57
|
-
for (let ix = 0; ix <
|
|
58
|
-
const mi =
|
|
65
|
+
for (let ix = 0; ix < copyW; ix++) {
|
|
66
|
+
const mi = currentSrcY * srcW + (srcX + ix)
|
|
59
67
|
const alpha = mask[mi]
|
|
60
68
|
|
|
61
69
|
if (alpha === 0) {
|
|
@@ -81,7 +89,7 @@ export function writeImageData(
|
|
|
81
89
|
}
|
|
82
90
|
}
|
|
83
91
|
} else {
|
|
84
|
-
const byteLen =
|
|
92
|
+
const byteLen = copyW * 4
|
|
85
93
|
const sub = srcData.subarray(srcStart, srcStart + byteLen)
|
|
86
94
|
dstData.set(sub, dstStart)
|
|
87
95
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Rect } from '../_types'
|
|
2
|
+
import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
|
|
3
|
+
|
|
4
|
+
const SCRATCH_BLIT = makeClippedBlit()
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Copies a pixel buffer into a specific region of an {@link ImageData} object.
|
|
@@ -46,28 +49,37 @@ export function writeImageDataBuffer(
|
|
|
46
49
|
|
|
47
50
|
const { width: dstW, height: dstH, data: dst } = imageData
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const clip = resolveBlitClipping(
|
|
53
|
+
x,
|
|
54
|
+
y,
|
|
55
|
+
0,
|
|
56
|
+
0,
|
|
57
|
+
w,
|
|
58
|
+
h,
|
|
59
|
+
dstW,
|
|
60
|
+
dstH,
|
|
61
|
+
w,
|
|
62
|
+
h,
|
|
63
|
+
SCRATCH_BLIT,
|
|
64
|
+
)
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
if (x1 <= x0 || y1 <= y0) return
|
|
66
|
+
if (!clip.inBounds) return
|
|
57
67
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
const {
|
|
69
|
+
x: dstX,
|
|
70
|
+
y: dstY,
|
|
71
|
+
sx: srcX,
|
|
72
|
+
sy: srcY,
|
|
73
|
+
w: copyW,
|
|
74
|
+
h: copyH,
|
|
75
|
+
} = clip
|
|
62
76
|
|
|
63
|
-
|
|
64
|
-
// Target index
|
|
65
|
-
const dstStart = ((y0 + row) * dstW + x0) * 4
|
|
77
|
+
const rowLen = copyW * 4
|
|
66
78
|
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
const
|
|
79
|
+
for (let row = 0; row < copyH; row++) {
|
|
80
|
+
const dstStart = ((dstY + row) * dstW + dstX) * 4
|
|
81
|
+
const srcStart = ((srcY + row) * w + srcX) * 4
|
|
70
82
|
|
|
71
|
-
dst.set(data.subarray(
|
|
83
|
+
dst.set(data.subarray(srcStart, srcStart + rowLen), dstStart)
|
|
72
84
|
}
|
|
73
85
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export type ClippedRect = {
|
|
2
|
+
x: number
|
|
3
|
+
y: number
|
|
4
|
+
w: number
|
|
5
|
+
h: number
|
|
6
|
+
inBounds: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ClippedBlit = {
|
|
10
|
+
x: number
|
|
11
|
+
y: number
|
|
12
|
+
sx: number
|
|
13
|
+
sy: number
|
|
14
|
+
w: number
|
|
15
|
+
h: number
|
|
16
|
+
inBounds: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// use factory functions when creating reusable objects ensure property order for JIT perf
|
|
20
|
+
export const makeClippedRect = (): ClippedRect => ({
|
|
21
|
+
x: 0,
|
|
22
|
+
y: 0,
|
|
23
|
+
w: 0,
|
|
24
|
+
h: 0,
|
|
25
|
+
inBounds: false,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const makeClippedBlit = (): ClippedBlit => ({
|
|
29
|
+
x: 0,
|
|
30
|
+
y: 0,
|
|
31
|
+
sx: 0,
|
|
32
|
+
sy: 0,
|
|
33
|
+
w: 0,
|
|
34
|
+
h: 0,
|
|
35
|
+
inBounds: false,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculates the intersection of a target rectangle and a bounding box (usually 0,0 -> width,height).
|
|
40
|
+
* Handles negative offsets by shrinking dimensions.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveRectClipping(
|
|
43
|
+
x: number,
|
|
44
|
+
y: number,
|
|
45
|
+
w: number,
|
|
46
|
+
h: number,
|
|
47
|
+
boundaryW: number,
|
|
48
|
+
boundaryH: number,
|
|
49
|
+
out: ClippedRect,
|
|
50
|
+
): ClippedRect {
|
|
51
|
+
// Destination Clipping (Top/Left)
|
|
52
|
+
if (x < 0) {
|
|
53
|
+
w += x
|
|
54
|
+
x = 0
|
|
55
|
+
}
|
|
56
|
+
if (y < 0) {
|
|
57
|
+
h += y
|
|
58
|
+
y = 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Destination Clipping (Bottom/Right)
|
|
62
|
+
const actualW = Math.min(w, boundaryW - x)
|
|
63
|
+
const actualH = Math.min(h, boundaryH - y)
|
|
64
|
+
|
|
65
|
+
if (actualW <= 0 || actualH <= 0) {
|
|
66
|
+
out.inBounds = false
|
|
67
|
+
return out
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
out.x = x
|
|
71
|
+
out.y = y
|
|
72
|
+
out.w = actualW
|
|
73
|
+
out.h = actualH
|
|
74
|
+
out.inBounds = true
|
|
75
|
+
|
|
76
|
+
return out
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Calculates the clipping for transferring data from a Source to a Destination.
|
|
81
|
+
* Handles cases where the source is out of bounds (shifting the destination target)
|
|
82
|
+
* AND cases where the destination is out of bounds (shifting the source target).
|
|
83
|
+
*/
|
|
84
|
+
export function resolveBlitClipping(
|
|
85
|
+
x: number,
|
|
86
|
+
y: number,
|
|
87
|
+
sx: number,
|
|
88
|
+
sy: number,
|
|
89
|
+
w: number,
|
|
90
|
+
h: number,
|
|
91
|
+
dstW: number,
|
|
92
|
+
dstH: number,
|
|
93
|
+
srcW: number,
|
|
94
|
+
srcH: number,
|
|
95
|
+
out: ClippedBlit,
|
|
96
|
+
): ClippedBlit {
|
|
97
|
+
// 1. Source Clipping: If reading from negative source, shift target right and shrink
|
|
98
|
+
if (sx < 0) {
|
|
99
|
+
x -= sx
|
|
100
|
+
w += sx
|
|
101
|
+
sx = 0
|
|
102
|
+
}
|
|
103
|
+
if (sy < 0) {
|
|
104
|
+
y -= sy
|
|
105
|
+
h += sy
|
|
106
|
+
sy = 0
|
|
107
|
+
}
|
|
108
|
+
w = Math.min(w, srcW - sx)
|
|
109
|
+
h = Math.min(h, srcH - sy)
|
|
110
|
+
|
|
111
|
+
// 2. Destination Clipping: If writing to negative dest, shift source right and shrink
|
|
112
|
+
if (x < 0) {
|
|
113
|
+
sx -= x
|
|
114
|
+
w += x
|
|
115
|
+
x = 0
|
|
116
|
+
}
|
|
117
|
+
if (y < 0) {
|
|
118
|
+
sy -= y
|
|
119
|
+
h += y
|
|
120
|
+
y = 0
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const actualW = Math.min(w, dstW - x)
|
|
124
|
+
const actualH = Math.min(h, dstH - y)
|
|
125
|
+
|
|
126
|
+
if (actualW <= 0 || actualH <= 0) {
|
|
127
|
+
out.inBounds = false
|
|
128
|
+
return out
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
out.x = x
|
|
132
|
+
out.y = y
|
|
133
|
+
out.sx = sx
|
|
134
|
+
out.sy = sy
|
|
135
|
+
out.w = actualW
|
|
136
|
+
out.h = actualH
|
|
137
|
+
out.inBounds = true
|
|
138
|
+
|
|
139
|
+
return out
|
|
140
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { AlphaMask, ApplyMaskToPixelDataOptions, BinaryMask } from '../_types'
|
|
2
|
+
|
|
3
|
+
export function applyBinaryMaskToAlphaMask(
|
|
4
|
+
alphaMaskDst: AlphaMask,
|
|
5
|
+
dstWidth: number,
|
|
6
|
+
binaryMaskSrc: BinaryMask,
|
|
7
|
+
srcWidth: number,
|
|
8
|
+
opts: ApplyMaskToPixelDataOptions = {},
|
|
9
|
+
): void {
|
|
10
|
+
const {
|
|
11
|
+
x: targetX = 0,
|
|
12
|
+
y: targetY = 0,
|
|
13
|
+
w: reqWidth = 0,
|
|
14
|
+
h: reqHeight = 0,
|
|
15
|
+
mx = 0,
|
|
16
|
+
my = 0,
|
|
17
|
+
invertMask = false,
|
|
18
|
+
} = opts
|
|
19
|
+
|
|
20
|
+
if (dstWidth <= 0) return
|
|
21
|
+
if (binaryMaskSrc.length === 0) return
|
|
22
|
+
if (srcWidth <= 0) return
|
|
23
|
+
|
|
24
|
+
const dstHeight = (alphaMaskDst.length / dstWidth) | 0
|
|
25
|
+
const srcHeight = (binaryMaskSrc.length / srcWidth) | 0
|
|
26
|
+
|
|
27
|
+
if (dstHeight <= 0) return
|
|
28
|
+
if (srcHeight <= 0) return
|
|
29
|
+
|
|
30
|
+
const dstX0 = Math.max(0, targetX)
|
|
31
|
+
const dstY0 = Math.max(0, targetY)
|
|
32
|
+
const dstX1 = reqWidth > 0 ? Math.min(dstWidth, targetX + reqWidth) : dstWidth
|
|
33
|
+
const dstY1 = reqHeight > 0 ? Math.min(dstHeight, targetY + reqHeight) : dstHeight
|
|
34
|
+
|
|
35
|
+
if (dstX0 >= dstX1) return
|
|
36
|
+
if (dstY0 >= dstY1) return
|
|
37
|
+
|
|
38
|
+
const srcX0 = mx + (dstX0 - targetX)
|
|
39
|
+
const srcY0 = my + (dstY0 - targetY)
|
|
40
|
+
|
|
41
|
+
if (srcX0 >= srcWidth) return
|
|
42
|
+
if (srcY0 >= srcHeight) return
|
|
43
|
+
if (srcX0 + (dstX1 - dstX0) <= 0) return
|
|
44
|
+
if (srcY0 + (dstY1 - dstY0) <= 0) return
|
|
45
|
+
|
|
46
|
+
const iterW = Math.min(dstX1 - dstX0, srcWidth - srcX0)
|
|
47
|
+
const iterH = Math.min(dstY1 - dstY0, srcHeight - srcY0)
|
|
48
|
+
|
|
49
|
+
let dstIdx = dstY0 * dstWidth + dstX0
|
|
50
|
+
let srcIdx = srcY0 * srcWidth + srcX0
|
|
51
|
+
|
|
52
|
+
if (invertMask) {
|
|
53
|
+
for (let row = 0; row < iterH; row++) {
|
|
54
|
+
const dstEnd = dstIdx + iterW
|
|
55
|
+
let d = dstIdx
|
|
56
|
+
let s = srcIdx
|
|
57
|
+
|
|
58
|
+
while (d < dstEnd) {
|
|
59
|
+
// inverted
|
|
60
|
+
if (binaryMaskSrc[s] !== 0) {
|
|
61
|
+
alphaMaskDst[d] = 0
|
|
62
|
+
}
|
|
63
|
+
d++
|
|
64
|
+
s++
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
dstIdx += dstWidth
|
|
68
|
+
srcIdx += srcWidth
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
for (let row = 0; row < iterH; row++) {
|
|
72
|
+
const dstEnd = dstIdx + iterW
|
|
73
|
+
let d = dstIdx
|
|
74
|
+
let s = srcIdx
|
|
75
|
+
|
|
76
|
+
while (d < dstEnd) {
|
|
77
|
+
// If binary mask is empty, clear the alpha pixel.
|
|
78
|
+
if (binaryMaskSrc[s] === 0) {
|
|
79
|
+
alphaMaskDst[d] = 0
|
|
80
|
+
}
|
|
81
|
+
d++
|
|
82
|
+
s++
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
dstIdx += dstWidth
|
|
86
|
+
srcIdx += srcWidth
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/Mask/copyMask.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import type { AnyMask } from '../index'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Creates a new copy of a mask.
|
|
5
3
|
* Uses the underlying buffer's slice method for high-performance memory copying.
|
|
6
4
|
*/
|
|
7
|
-
export function copyMask<T extends
|
|
5
|
+
export function copyMask<T extends Uint8Array>(src: T): T {
|
|
8
6
|
// Uint8Array.slice() is highly optimized at the engine level
|
|
9
7
|
return src.slice() as T
|
|
10
8
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type AlphaMask, type MergeAlphaMasksOptions } from '../_types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merges 2 alpha masks values are 0-255
|
|
5
|
+
*/
|
|
6
|
+
export function mergeAlphaMasks(
|
|
7
|
+
dst: AlphaMask,
|
|
8
|
+
dstWidth: number,
|
|
9
|
+
src: AlphaMask,
|
|
10
|
+
srcWidth: number,
|
|
11
|
+
opts: MergeAlphaMasksOptions,
|
|
12
|
+
): void {
|
|
13
|
+
const {
|
|
14
|
+
x: targetX = 0,
|
|
15
|
+
y: targetY = 0,
|
|
16
|
+
w: width = 0,
|
|
17
|
+
h: height = 0,
|
|
18
|
+
alpha: globalAlpha = 255,
|
|
19
|
+
mx = 0,
|
|
20
|
+
my = 0,
|
|
21
|
+
invertMask = false,
|
|
22
|
+
} = opts
|
|
23
|
+
const dstHeight = (dst.length / dstWidth) | 0
|
|
24
|
+
const srcHeight = (src.length / srcWidth) | 0
|
|
25
|
+
|
|
26
|
+
if (width <= 0) return
|
|
27
|
+
if (height <= 0) return
|
|
28
|
+
if (globalAlpha === 0) return
|
|
29
|
+
|
|
30
|
+
const startX = Math.max(0, -targetX, -mx)
|
|
31
|
+
const startY = Math.max(0, -targetY, -my)
|
|
32
|
+
|
|
33
|
+
const endX = Math.min(width, dstWidth - targetX, srcWidth - mx)
|
|
34
|
+
const endY = Math.min(height, dstHeight - targetY, srcHeight - my)
|
|
35
|
+
|
|
36
|
+
if (startX >= endX) return
|
|
37
|
+
if (startY >= endY) return
|
|
38
|
+
|
|
39
|
+
for (let iy = startY; iy < endY; iy++) {
|
|
40
|
+
const dy = targetY + iy
|
|
41
|
+
const sy = my + iy
|
|
42
|
+
|
|
43
|
+
let dIdx = dy * dstWidth + targetX + startX
|
|
44
|
+
let sIdx = sy * srcWidth + mx + startX
|
|
45
|
+
|
|
46
|
+
for (let ix = startX; ix < endX; ix++) {
|
|
47
|
+
const rawM = src[sIdx]
|
|
48
|
+
// Unified logic branch inside the hot path
|
|
49
|
+
const effectiveM = invertMask ? 255 - rawM : rawM
|
|
50
|
+
|
|
51
|
+
let weight = 0
|
|
52
|
+
|
|
53
|
+
if (effectiveM === 0) {
|
|
54
|
+
weight = 0
|
|
55
|
+
} else if (effectiveM === 255) {
|
|
56
|
+
weight = globalAlpha
|
|
57
|
+
} else if (globalAlpha === 255) {
|
|
58
|
+
weight = effectiveM
|
|
59
|
+
} else {
|
|
60
|
+
weight = (effectiveM * globalAlpha + 128) >> 8
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (weight !== 255) {
|
|
64
|
+
if (weight === 0) {
|
|
65
|
+
dst[dIdx] = 0
|
|
66
|
+
} else {
|
|
67
|
+
const da = dst[dIdx]
|
|
68
|
+
|
|
69
|
+
if (da === 255) {
|
|
70
|
+
dst[dIdx] = weight
|
|
71
|
+
} else if (da !== 0) {
|
|
72
|
+
dst[dIdx] = (da * weight + 128) >> 8
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
sIdx++
|
|
78
|
+
dIdx++
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { BinaryMask, MergeAlphaMasksOptions } from '../_types'
|
|
2
|
+
|
|
3
|
+
export function mergeBinaryMasks(
|
|
4
|
+
dst: BinaryMask,
|
|
5
|
+
dstWidth: number,
|
|
6
|
+
src: BinaryMask,
|
|
7
|
+
srcWidth: number,
|
|
8
|
+
opts: MergeAlphaMasksOptions,
|
|
9
|
+
): void {
|
|
10
|
+
const {
|
|
11
|
+
x: targetX = 0,
|
|
12
|
+
y: targetY = 0,
|
|
13
|
+
w: width = 0,
|
|
14
|
+
h: height = 0,
|
|
15
|
+
mx = 0,
|
|
16
|
+
my = 0,
|
|
17
|
+
invertMask = false,
|
|
18
|
+
} = opts
|
|
19
|
+
if (dstWidth <= 0) return
|
|
20
|
+
if (srcWidth <= 0) return
|
|
21
|
+
|
|
22
|
+
const dstHeight = (dst.length / dstWidth) | 0
|
|
23
|
+
const srcHeight = (src.length / srcWidth) | 0
|
|
24
|
+
|
|
25
|
+
// 1. Destination Clipping
|
|
26
|
+
let x = targetX
|
|
27
|
+
let y = targetY
|
|
28
|
+
let w = width
|
|
29
|
+
let h = height
|
|
30
|
+
|
|
31
|
+
if (x < 0) {
|
|
32
|
+
w += x
|
|
33
|
+
x = 0
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (y < 0) {
|
|
37
|
+
h += y
|
|
38
|
+
y = 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
w = Math.min(w, dstWidth - x)
|
|
42
|
+
h = Math.min(h, dstHeight - y)
|
|
43
|
+
|
|
44
|
+
if (w <= 0) return
|
|
45
|
+
if (h <= 0) return
|
|
46
|
+
|
|
47
|
+
// 2. Source Bounds Clipping (Double Clipping)
|
|
48
|
+
const startX = mx + (x - targetX)
|
|
49
|
+
const startY = my + (y - targetY)
|
|
50
|
+
|
|
51
|
+
const sX0 = Math.max(0, startX)
|
|
52
|
+
const sY0 = Math.max(0, startY)
|
|
53
|
+
const sX1 = Math.min(srcWidth, startX + w)
|
|
54
|
+
const sY1 = Math.min(srcHeight, startY + h)
|
|
55
|
+
|
|
56
|
+
const finalW = sX1 - sX0
|
|
57
|
+
const finalH = sY1 - sY0
|
|
58
|
+
|
|
59
|
+
if (finalW <= 0) return
|
|
60
|
+
if (finalH <= 0) return
|
|
61
|
+
|
|
62
|
+
// 3. Coordinate Alignment
|
|
63
|
+
const xShift = sX0 - startX
|
|
64
|
+
const yShift = sY0 - startY
|
|
65
|
+
|
|
66
|
+
const dStride = dstWidth - finalW
|
|
67
|
+
const sStride = srcWidth - finalW
|
|
68
|
+
|
|
69
|
+
let dIdx = (y + yShift) * dstWidth + (x + xShift)
|
|
70
|
+
let sIdx = sY0 * srcWidth + sX0
|
|
71
|
+
|
|
72
|
+
for (let iy = 0; iy < finalH; iy++) {
|
|
73
|
+
for (let ix = 0; ix < finalW; ix++) {
|
|
74
|
+
const mVal = src[sIdx]
|
|
75
|
+
// Determine if the source pixel effectively "clears" the destination
|
|
76
|
+
const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0
|
|
77
|
+
|
|
78
|
+
if (isMaskedOut) {
|
|
79
|
+
dst[dIdx] = 0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
dIdx++
|
|
83
|
+
sIdx++
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
dIdx += dStride
|
|
87
|
+
sIdx += sStride
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IPixelData } from '../_types'
|
|
2
|
+
|
|
3
|
+
export class PixelBuffer32 implements IPixelData {
|
|
4
|
+
readonly data32: Uint32Array
|
|
5
|
+
|
|
6
|
+
constructor(
|
|
7
|
+
readonly width: number,
|
|
8
|
+
readonly height: number,
|
|
9
|
+
data32?: Uint32Array,
|
|
10
|
+
) {
|
|
11
|
+
this.data32 = data32 ?? new Uint32Array(width * height)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
set(width: number, height: number, data32?: Uint32Array): void {
|
|
15
|
+
;(this as any).data32 = data32 ?? new Uint32Array(width * height)
|
|
16
|
+
;(this as any).width = width
|
|
17
|
+
;(this as any).height = height
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
copy(): PixelBuffer32 {
|
|
21
|
+
const newData32 = new Uint32Array(this.data32)
|
|
22
|
+
return new PixelBuffer32(
|
|
23
|
+
this.width,
|
|
24
|
+
this.height,
|
|
25
|
+
newData32,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
}
|