pixel-data-js 0.30.0 → 0.32.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 +473 -312
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +130 -55
- package/dist/index.prod.js +469 -308
- package/dist/index.prod.js.map +1 -1
- package/package.json +1 -1
- package/src/Control/BatchedQueue.ts +76 -0
- package/src/Control/RenderQueue.ts +49 -0
- package/src/History/PixelWriter.ts +5 -9
- package/src/ImageData/extractImageData.ts +55 -0
- package/src/ImageData/extractImageDataBuffer.ts +57 -28
- package/src/ImageData/writeImageData.ts +45 -66
- package/src/ImageData/writeImageDataBuffer.ts +79 -41
- package/src/PixelData/extractPixelDataBuffer.ts +53 -40
- package/src/PixelData/fillPixelData.ts +46 -28
- package/src/PixelData/fillPixelDataBinaryMask.ts +43 -38
- package/src/PixelData/fillPixelDataFast.ts +26 -16
- package/src/PixelData/invertPixelData.ts +38 -21
- package/src/PixelData/resizePixelData.ts +75 -0
- package/src/PixelData/writePixelDataBuffer.ts +53 -38
- package/src/index.ts +5 -1
- package/src/Rect/resolveClipping.ts +0 -140
package/package.json
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type BatchedQueueFn = (fn: () => void) => void
|
|
2
|
+
|
|
3
|
+
export type BatchedQueue = ReturnType<typeof makeBatchedQueue>
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a high-performance, zero-allocation batching queue.
|
|
7
|
+
* This utility collects items marked as "dirty" and flushes them in a single batch.
|
|
8
|
+
* * **⚠️ CRITICAL: Synchronous Processing Required**
|
|
9
|
+
* Because the internal sets are reused, the `Set` passed to the `processor` is instantly
|
|
10
|
+
* cleared the moment the processor function returns. If you need to process the items
|
|
11
|
+
* asynchronously, you **must** manually clone the set inside your processor.
|
|
12
|
+
* @template T - The type of items being batched.
|
|
13
|
+
* @param processor - The callback executed when the batch flushes. Receives a `Set` of all batched items.
|
|
14
|
+
* @param queue - The scheduling function used to defer the flush. Defaults to `queueMicrotask`.
|
|
15
|
+
* @returns An object containing methods to mark items as dirty.
|
|
16
|
+
* @example
|
|
17
|
+
* * @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { nextTick } from 'vue'
|
|
20
|
+
* let bq = makeBatchedQueue<string>(
|
|
21
|
+
* (items) => drawSomething(items),
|
|
22
|
+
* nextTick,
|
|
23
|
+
* )
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function makeBatchedQueue<T>(
|
|
27
|
+
processor: (items: Set<T>) => void,
|
|
28
|
+
queue: BatchedQueueFn,
|
|
29
|
+
) {
|
|
30
|
+
let activeSet = new Set<T>()
|
|
31
|
+
let processingSet = new Set<T>()
|
|
32
|
+
let scheduled = false
|
|
33
|
+
|
|
34
|
+
const flush = () => {
|
|
35
|
+
// swap sets
|
|
36
|
+
const current = activeSet
|
|
37
|
+
activeSet = processingSet
|
|
38
|
+
processingSet = current
|
|
39
|
+
|
|
40
|
+
scheduled = false
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
processor(processingSet)
|
|
44
|
+
} finally {
|
|
45
|
+
processingSet.clear()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function markDirty(item: T) {
|
|
50
|
+
activeSet.add(item)
|
|
51
|
+
|
|
52
|
+
if (!scheduled) {
|
|
53
|
+
scheduled = true
|
|
54
|
+
queue(flush)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function markMultipleDirty(items: T[]) {
|
|
59
|
+
let len = items.length
|
|
60
|
+
if (len === 0) return
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < len; i++) {
|
|
63
|
+
activeSet.add(items[i])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!scheduled) {
|
|
67
|
+
scheduled = true
|
|
68
|
+
queue(flush)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
markDirty,
|
|
74
|
+
markMultipleDirty,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a debounced render queue using `requestAnimationFrame`.
|
|
3
|
+
* This utility ensures that a callback is executed exactly once right before
|
|
4
|
+
* the next visual frame, regardless of how many times the trigger is called
|
|
5
|
+
* synchronously. It safely prevents layout thrashing and redundant computations.
|
|
6
|
+
* @param cb - The function to execute on the next animation frame.
|
|
7
|
+
* @returns A trigger function that schedules the callback. It includes a `.cancel()` method to abort the pending frame.
|
|
8
|
+
* * @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* let renderQueue = makeRenderQueue(() => {
|
|
11
|
+
* console.log('DOM updated!')
|
|
12
|
+
* })
|
|
13
|
+
* * // Calling this multiple times synchronously...
|
|
14
|
+
* renderQueue()
|
|
15
|
+
* renderQueue()
|
|
16
|
+
* renderQueue()
|
|
17
|
+
* * // ...will only result in one 'DOM updated!' log on the next frame.
|
|
18
|
+
* ```
|
|
19
|
+
* * @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* // Canceling a scheduled render (e.g., when a component unmounts)
|
|
22
|
+
* let trigger = makeRenderQueue(updateLayout)
|
|
23
|
+
* trigger()
|
|
24
|
+
* trigger.cancel() // The callback will not execute
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function makeRenderQueue(cb: () => void) {
|
|
28
|
+
let needsRender = false
|
|
29
|
+
let frameId = 0
|
|
30
|
+
|
|
31
|
+
const trigger = () => {
|
|
32
|
+
if (needsRender) return
|
|
33
|
+
needsRender = true
|
|
34
|
+
|
|
35
|
+
frameId = requestAnimationFrame(() => {
|
|
36
|
+
needsRender = false
|
|
37
|
+
cb()
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
trigger.cancel = () => {
|
|
42
|
+
if (needsRender) {
|
|
43
|
+
cancelAnimationFrame(frameId)
|
|
44
|
+
needsRender = false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return trigger
|
|
49
|
+
}
|
|
@@ -8,6 +8,7 @@ import { type HistoryActionFactory, makeHistoryAction } from './HistoryAction'
|
|
|
8
8
|
import { HistoryManager } from './HistoryManager'
|
|
9
9
|
import { PixelAccumulator } from './PixelAccumulator'
|
|
10
10
|
import { PixelEngineConfig } from './PixelEngineConfig'
|
|
11
|
+
import type { PixelPatchTiles } from './PixelPatchTiles'
|
|
11
12
|
|
|
12
13
|
export interface PixelWriterOptions {
|
|
13
14
|
maxHistorySteps?: number
|
|
@@ -72,15 +73,13 @@ export class PixelWriter<M> {
|
|
|
72
73
|
* throw immediately to prevent silent data loss from a nested extractPatch.
|
|
73
74
|
*
|
|
74
75
|
* @param transaction Callback to be executed inside the transaction.
|
|
75
|
-
* @param
|
|
76
|
-
* @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
|
|
76
|
+
* @param afterUndo Called after undo only.
|
|
77
77
|
* @param afterRedo Called after redo only.
|
|
78
78
|
*/
|
|
79
79
|
withHistory(
|
|
80
80
|
transaction: (mutator: M) => void,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
afterRedo?: () => void,
|
|
81
|
+
afterUndo?: (patch: PixelPatchTiles) => void,
|
|
82
|
+
afterRedo?: (patch: PixelPatchTiles) => void,
|
|
84
83
|
): void {
|
|
85
84
|
if (this._inProgress) {
|
|
86
85
|
throw new Error('withHistory is not re-entrant — commit or rollback the current operation first')
|
|
@@ -100,7 +99,7 @@ export class PixelWriter<M> {
|
|
|
100
99
|
if (this.accumulator.beforeTiles.length === 0) return
|
|
101
100
|
|
|
102
101
|
const patch = this.accumulator.extractPatch()
|
|
103
|
-
const action = this.historyActionFactory(this.config, this.accumulator, patch,
|
|
102
|
+
const action = this.historyActionFactory(this.config, this.accumulator, patch, afterUndo, afterRedo)
|
|
104
103
|
|
|
105
104
|
this.historyManager.commit(action)
|
|
106
105
|
}
|
|
@@ -110,7 +109,6 @@ export class PixelWriter<M> {
|
|
|
110
109
|
newHeight: number,
|
|
111
110
|
offsetX = 0,
|
|
112
111
|
offsetY = 0,
|
|
113
|
-
after?: (target: ImageData) => void,
|
|
114
112
|
afterUndo?: (target: ImageData) => void,
|
|
115
113
|
afterRedo?: (target: ImageData) => void,
|
|
116
114
|
resizeImageDataFn = resizeImageData,
|
|
@@ -134,12 +132,10 @@ export class PixelWriter<M> {
|
|
|
134
132
|
undo: () => {
|
|
135
133
|
setPixelData(target, beforeImageData)
|
|
136
134
|
afterUndo?.(beforeImageData)
|
|
137
|
-
after?.(beforeImageData)
|
|
138
135
|
},
|
|
139
136
|
redo: () => {
|
|
140
137
|
setPixelData(target, afterImageData)
|
|
141
138
|
afterRedo?.(afterImageData)
|
|
142
|
-
after?.(afterImageData)
|
|
143
139
|
},
|
|
144
140
|
})
|
|
145
141
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Rect } from '../Rect/_rect-types'
|
|
2
|
+
import type { ImageDataLike } from './_ImageData-types'
|
|
3
|
+
import { extractImageDataBuffer } from './extractImageDataBuffer'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts a specific rectangular region of pixels from a larger {@link ImageDataLike}
|
|
7
|
+
* source into a new {@link Uint8ClampedArray}.
|
|
8
|
+
*
|
|
9
|
+
* This is a "read-only" operation that returns a copy of the pixel data.
|
|
10
|
+
*
|
|
11
|
+
* @param imageData - The source image data to read from.
|
|
12
|
+
* @param rect - A rect defining the region to extract.
|
|
13
|
+
* @returns A buffer containing the RGBA pixel data of the region.
|
|
14
|
+
*/
|
|
15
|
+
export function extractImageData(
|
|
16
|
+
imageData: ImageDataLike,
|
|
17
|
+
rect: Rect,
|
|
18
|
+
): ImageData | null
|
|
19
|
+
/**
|
|
20
|
+
* @param imageData - The source image data to read from.
|
|
21
|
+
* @param x - The starting horizontal coordinate.
|
|
22
|
+
* @param y - The starting vertical coordinate.
|
|
23
|
+
* @param w - The width of the region to extract.
|
|
24
|
+
* @param h - The height of the region to extract.
|
|
25
|
+
* @returns A buffer containing the RGBA pixel data of the region.
|
|
26
|
+
*/
|
|
27
|
+
export function extractImageData(
|
|
28
|
+
imageData: ImageDataLike,
|
|
29
|
+
x: number,
|
|
30
|
+
y: number,
|
|
31
|
+
w: number,
|
|
32
|
+
h: number,
|
|
33
|
+
): ImageData | null
|
|
34
|
+
export function extractImageData(
|
|
35
|
+
imageData: ImageDataLike,
|
|
36
|
+
_x: Rect | number,
|
|
37
|
+
_y?: number,
|
|
38
|
+
_w?: number,
|
|
39
|
+
_h?: number,
|
|
40
|
+
): ImageData | null {
|
|
41
|
+
const { x, y, w, h } = typeof _x === 'object'
|
|
42
|
+
? _x
|
|
43
|
+
: { x: _x, y: _y!, w: _w!, h: _h! }
|
|
44
|
+
|
|
45
|
+
if (w <= 0) return null
|
|
46
|
+
if (h <= 0) return null
|
|
47
|
+
|
|
48
|
+
const result = new ImageData(w, h)
|
|
49
|
+
|
|
50
|
+
const buffer = extractImageDataBuffer(imageData, x, y, w, h)
|
|
51
|
+
result.data.set(buffer)
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { Rect } from '../Rect/_rect-types'
|
|
2
|
-
import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
|
|
3
2
|
import type { ImageDataLike } from './_ImageData-types'
|
|
4
3
|
|
|
5
|
-
const SCRATCH_BLIT = makeClippedBlit()
|
|
6
|
-
|
|
7
4
|
/**
|
|
8
5
|
* Extracts a specific rectangular region of pixels from a larger {@link ImageDataLike}
|
|
9
6
|
* source into a new {@link Uint8ClampedArray}.
|
|
@@ -43,37 +40,69 @@ export function extractImageDataBuffer(
|
|
|
43
40
|
const { x, y, w, h } = typeof _x === 'object'
|
|
44
41
|
? _x
|
|
45
42
|
: { x: _x, y: _y!, w: _w!, h: _h! }
|
|
43
|
+
if (w <= 0) return new Uint8ClampedArray(0)
|
|
44
|
+
if (h <= 0) return new Uint8ClampedArray(0)
|
|
45
|
+
|
|
46
|
+
const srcW = imageData.width
|
|
47
|
+
const srcH = imageData.height
|
|
48
|
+
const src = imageData.data
|
|
49
|
+
|
|
50
|
+
const outLen = w * h * 4
|
|
51
|
+
const out = new Uint8ClampedArray(outLen)
|
|
52
|
+
|
|
53
|
+
let srcX = x
|
|
54
|
+
let srcY = y
|
|
55
|
+
let dstX = 0
|
|
56
|
+
let dstY = 0
|
|
57
|
+
let copyW = w
|
|
58
|
+
let copyH = h
|
|
59
|
+
|
|
60
|
+
if (srcX < 0) {
|
|
61
|
+
dstX = -srcX
|
|
62
|
+
copyW += srcX
|
|
63
|
+
srcX = 0
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (srcY < 0) {
|
|
67
|
+
dstY = -srcY
|
|
68
|
+
copyH += srcY
|
|
69
|
+
srcY = 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
copyW = Math.min(copyW, srcW - srcX)
|
|
73
|
+
copyH = Math.min(copyH, srcH - srcY)
|
|
74
|
+
|
|
75
|
+
if (copyW <= 0) return out
|
|
76
|
+
if (copyH <= 0) return out
|
|
46
77
|
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
const
|
|
78
|
+
// 2. Perform high-speed block copy
|
|
79
|
+
// Attempt to use a 32-bit view if the buffer is memory-aligned.
|
|
80
|
+
// This reduces loop iterations and arithmetic by 4x.
|
|
81
|
+
const isAligned = src.byteOffset % 4 === 0
|
|
51
82
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
y,
|
|
57
|
-
w,
|
|
58
|
-
h,
|
|
59
|
-
w,
|
|
60
|
-
h,
|
|
61
|
-
srcW,
|
|
62
|
-
srcH,
|
|
63
|
-
SCRATCH_BLIT,
|
|
64
|
-
)
|
|
83
|
+
if (isAligned) {
|
|
84
|
+
const srcLen32 = src.byteLength / 4
|
|
85
|
+
const src32 = new Uint32Array(src.buffer, src.byteOffset, srcLen32)
|
|
86
|
+
const out32 = new Uint32Array(out.buffer)
|
|
65
87
|
|
|
66
|
-
|
|
88
|
+
for (let row = 0; row < copyH; row++) {
|
|
89
|
+
const srcStart = (srcY + row) * srcW + srcX
|
|
90
|
+
const dstStart = (dstY + row) * w + dstX
|
|
91
|
+
const chunk = src32.subarray(srcStart, srcStart + copyW)
|
|
67
92
|
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
out32.set(chunk, dstStart)
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
// Fallback for unaligned data
|
|
97
|
+
const rowLen = copyW * 4
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
99
|
+
for (let row = 0; row < copyH; row++) {
|
|
100
|
+
const srcStart = ((srcY + row) * srcW + srcX) * 4
|
|
101
|
+
const dstStart = ((dstY + row) * w + dstX) * 4
|
|
102
|
+
const chunk = src.subarray(srcStart, srcStart + rowLen)
|
|
74
103
|
|
|
75
|
-
|
|
76
|
-
|
|
104
|
+
out.set(chunk, dstStart)
|
|
105
|
+
}
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
return out
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import { MaskType } from '../Mask/_mask-types'
|
|
2
|
-
import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
|
|
3
|
-
|
|
4
|
-
const SCRATCH_BLIT = makeClippedBlit()
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* Writes image data from a source to a target with support for clipping and alpha masking.
|
|
8
3
|
*
|
|
@@ -10,88 +5,72 @@ const SCRATCH_BLIT = makeClippedBlit()
|
|
|
10
5
|
* @param source - The source ImageData to read from.
|
|
11
6
|
* @param x - The x-coordinate in the target where drawing starts.
|
|
12
7
|
* @param y - The y-coordinate in the target where drawing starts.
|
|
13
|
-
* @param sx - The x-coordinate in the source to start copying from.
|
|
14
|
-
* @param sy - The y-coordinate in the source to start copying from.
|
|
15
|
-
* @param sw - The width of the rectangle to copy.
|
|
16
|
-
* @param sh - The height of the rectangle to copy.
|
|
17
|
-
* @param mask - An optional Uint8Array mask (0-255). 0 is transparent, 255 is opaque.
|
|
18
|
-
* @param maskType - type of mask
|
|
19
8
|
*/
|
|
20
9
|
export function writeImageData(
|
|
21
10
|
target: ImageData,
|
|
22
11
|
source: ImageData,
|
|
23
12
|
x: number,
|
|
24
13
|
y: number,
|
|
25
|
-
sx: number = 0,
|
|
26
|
-
sy: number = 0,
|
|
27
|
-
sw: number = source.width,
|
|
28
|
-
sh: number = source.height,
|
|
29
|
-
mask: Uint8Array | null = null,
|
|
30
|
-
maskType: MaskType = MaskType.BINARY,
|
|
31
14
|
): void {
|
|
32
15
|
const dstW = target.width
|
|
33
16
|
const dstH = target.height
|
|
34
|
-
const
|
|
17
|
+
const dst = target.data
|
|
18
|
+
|
|
35
19
|
const srcW = source.width
|
|
36
|
-
const
|
|
20
|
+
const srcH = source.height
|
|
21
|
+
const src = source.data
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
23
|
+
let dstX = x
|
|
24
|
+
let dstY = y
|
|
25
|
+
let srcX = 0
|
|
26
|
+
let srcY = 0
|
|
27
|
+
let copyW = srcW
|
|
28
|
+
let copyH = srcH
|
|
43
29
|
|
|
44
|
-
if (
|
|
30
|
+
if (dstX < 0) {
|
|
31
|
+
srcX = -dstX
|
|
32
|
+
copyW += dstX
|
|
33
|
+
dstX = 0
|
|
34
|
+
}
|
|
45
35
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
36
|
+
if (dstY < 0) {
|
|
37
|
+
srcY = -dstY
|
|
38
|
+
copyH += dstY
|
|
39
|
+
dstY = 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
copyW = Math.min(copyW, dstW - dstX)
|
|
43
|
+
copyH = Math.min(copyH, dstH - dstY)
|
|
54
44
|
|
|
55
|
-
|
|
45
|
+
if (copyW <= 0) return
|
|
46
|
+
if (copyH <= 0) return
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const currentSrcY = srcY + row
|
|
48
|
+
const isDstAligned = dst.byteOffset % 4 === 0
|
|
49
|
+
const isSrcAligned = src.byteOffset % 4 === 0
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
const
|
|
51
|
+
if (isDstAligned && isSrcAligned) {
|
|
52
|
+
const dstLen32 = dst.byteLength / 4
|
|
53
|
+
const dst32 = new Uint32Array(dst.buffer, dst.byteOffset, dstLen32)
|
|
63
54
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const mi = currentSrcY * srcW + (srcX + ix)
|
|
67
|
-
const alpha = mask[mi]
|
|
55
|
+
const srcLen32 = src.byteLength / 4
|
|
56
|
+
const src32 = new Uint32Array(src.buffer, src.byteOffset, srcLen32)
|
|
68
57
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
for (let row = 0; row < copyH; row++) {
|
|
59
|
+
const dstStart = (dstY + row) * dstW + dstX
|
|
60
|
+
const srcStart = (srcY + row) * srcW + srcX
|
|
61
|
+
const chunk = src32.subarray(srcStart, srcStart + copyW)
|
|
72
62
|
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
dst32.set(chunk, dstStart)
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
const rowLen = copyW * 4
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
dstData[di + 3] = srcData[si + 3]
|
|
81
|
-
} else {
|
|
82
|
-
const a = alpha / 255
|
|
83
|
-
const invA = 1 - a
|
|
68
|
+
for (let row = 0; row < copyH; row++) {
|
|
69
|
+
const dstStart = ((dstY + row) * dstW + dstX) * 4
|
|
70
|
+
const srcStart = ((srcY + row) * srcW + srcX) * 4
|
|
71
|
+
const chunk = src.subarray(srcStart, srcStart + rowLen)
|
|
84
72
|
|
|
85
|
-
|
|
86
|
-
dstData[di + 1] = srcData[si + 1] * a + dstData[di + 1] * invA
|
|
87
|
-
dstData[di + 2] = srcData[si + 2] * a + dstData[di + 2] * invA
|
|
88
|
-
dstData[di + 3] = srcData[si + 3] * a + dstData[di + 3] * invA
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
const byteLen = copyW * 4
|
|
93
|
-
const sub = srcData.subarray(srcStart, srcStart + byteLen)
|
|
94
|
-
dstData.set(sub, dstStart)
|
|
73
|
+
dst.set(chunk, dstStart)
|
|
95
74
|
}
|
|
96
75
|
}
|
|
97
76
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import type { Rect } from '../Rect/_rect-types'
|
|
2
|
-
import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
|
|
3
|
-
|
|
4
|
-
const SCRATCH_BLIT = makeClippedBlit()
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* Copies a pixel buffer into a specific region of an {@link ImageData} object.
|
|
@@ -43,43 +40,84 @@ export function writeImageDataBuffer(
|
|
|
43
40
|
_w?: number,
|
|
44
41
|
_h?: number,
|
|
45
42
|
): void {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
43
|
+
let x: number
|
|
44
|
+
let y: number
|
|
45
|
+
let w: number
|
|
46
|
+
let h: number
|
|
47
|
+
|
|
48
|
+
if (typeof _x === 'object') {
|
|
49
|
+
x = _x.x
|
|
50
|
+
y = _x.y
|
|
51
|
+
w = _x.w
|
|
52
|
+
h = _x.h
|
|
53
|
+
} else {
|
|
54
|
+
x = _x
|
|
55
|
+
y = _y!
|
|
56
|
+
w = _w!
|
|
57
|
+
h = _h!
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (w <= 0) return
|
|
61
|
+
if (h <= 0) return
|
|
62
|
+
|
|
63
|
+
const dstW = target.width
|
|
64
|
+
const dstH = target.height
|
|
65
|
+
const dst = target.data
|
|
66
|
+
|
|
67
|
+
// Inline clipping logic for destination boundaries
|
|
68
|
+
let dstX = x
|
|
69
|
+
let dstY = y
|
|
70
|
+
let srcX = 0
|
|
71
|
+
let srcY = 0
|
|
72
|
+
let copyW = w
|
|
73
|
+
let copyH = h
|
|
74
|
+
|
|
75
|
+
if (dstX < 0) {
|
|
76
|
+
srcX = -dstX
|
|
77
|
+
copyW += dstX
|
|
78
|
+
dstX = 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (dstY < 0) {
|
|
82
|
+
srcY = -dstY
|
|
83
|
+
copyH += dstY
|
|
84
|
+
dstY = 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
copyW = Math.min(copyW, dstW - dstX)
|
|
88
|
+
copyH = Math.min(copyH, dstH - dstY)
|
|
89
|
+
|
|
90
|
+
if (copyW <= 0) return
|
|
91
|
+
if (copyH <= 0) return
|
|
92
|
+
|
|
93
|
+
// Fast-path: Both arrays must be 4-byte aligned to use Uint32Array safely
|
|
94
|
+
const isDstAligned = dst.byteOffset % 4 === 0
|
|
95
|
+
const isSrcAligned = data.byteOffset % 4 === 0
|
|
96
|
+
|
|
97
|
+
if (isDstAligned && isSrcAligned) {
|
|
98
|
+
const dstLen32 = dst.byteLength / 4
|
|
99
|
+
const dst32 = new Uint32Array(dst.buffer, dst.byteOffset, dstLen32)
|
|
100
|
+
|
|
101
|
+
const srcLen32 = data.byteLength / 4
|
|
102
|
+
const src32 = new Uint32Array(data.buffer, data.byteOffset, srcLen32)
|
|
103
|
+
|
|
104
|
+
for (let row = 0; row < copyH; row++) {
|
|
105
|
+
const dstStart = (dstY + row) * dstW + dstX
|
|
106
|
+
const srcStart = (srcY + row) * w + srcX
|
|
107
|
+
const chunk = src32.subarray(srcStart, srcStart + copyW)
|
|
108
|
+
|
|
109
|
+
dst32.set(chunk, dstStart)
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// Fallback for unaligned data arrays
|
|
113
|
+
const rowLen = copyW * 4
|
|
114
|
+
|
|
115
|
+
for (let row = 0; row < copyH; row++) {
|
|
116
|
+
const dstStart = ((dstY + row) * dstW + dstX) * 4
|
|
117
|
+
const srcStart = ((srcY + row) * w + srcX) * 4
|
|
118
|
+
const chunk = data.subarray(srcStart, srcStart + rowLen)
|
|
119
|
+
|
|
120
|
+
dst.set(chunk, dstStart)
|
|
121
|
+
}
|
|
84
122
|
}
|
|
85
123
|
}
|