PixelBuffer
Headless RGBA pixel buffer (no DOM dependency).
Maintains two arrays: a working buffer at the current size, and a master buffer pre-allocated at maxSize × maxSize. copyToMaster() commits working into master; resize() reads back from master, so shrinking and growing again doesn't lose content.
new PixelBuffer(options: PixelBufferOptions)
interface PixelBufferOptions {
size: Vec2;
/** @default { r: 255, g: 255, b: 255, a: 255 } */
defaultColor?: RGBA | ColorInput;
/** @default 2048 */
maxSize?: number;
}Pixel (0, 0) is always initialized fully transparent.
Methods
size() / resize(size)
size(): Vec2
resize(size: Vec2): voidReturns the current working size, or resizes it (content restored from master, clipped to maxSize).
pixels()
pixels(): Uint8ClampedArrayReturns the live working buffer (not a copy). Mutating it mutates the buffer directly.
replacePixels(pixels, size)
replacePixels(pixels: Uint8ClampedArray, size: Vec2): voidReplaces pixel data wholesale and resizes to match. Used for network snapshots and image decoding.
drawPixels(positions, color)
drawPixels(positions: Iterable<Vec2>, color: RGBA): voidStamps one color across multiple positions. Out-of-bounds positions are silently skipped.
drawRegion(rect, pixels)
drawRegion(rect: SelectionRect, pixels: RGBA[]): voidWrites a rectangular block of per-pixel colors (row-major, rect.width * rect.height entries). Out-of-bounds positions skipped.
drawMaskedRegion(rect, pixels, mask)
drawMaskedRegion(rect: SelectionRect, pixels: RGBA[], mask: boolean[]): voidSame as drawRegion, but skips cells where mask[i] is false. Used for non-rectangular (shape-selected) regions.
copyToMaster()
copyToMaster(): voidCommits the working buffer into master at (0, 0).
samplePixel(x, y)
samplePixel(x: number, y: number): [number, number, number, number]Returns [r, g, b, a] at (x, y). Out-of-bounds returns [0, 0, 0, 0].
samplePixels(positions)
samplePixels(positions: Vec2[]): RGBA[]Batch samplePixel. Out-of-bounds positions return { r: 0, g: 0, b: 0, a: 0 }.
UV Regions
readonly uvRegions: UVRegionCollectionServer-side UV region storage for this buffer. Included in PixelSyncServer.snapshot() so late-joining clients receive existing regions.
UVRegionCollection is an id-keyed map of UVRegions. Entries are always keyed by region.id and stored as copies.
uvRegions.get(id: string): UVRegion | undefined
uvRegions.set(region: UVRegion): void // upserts a copy by region.id
uvRegions.remove(id: string): voidImplements Symbol.iterator: use [...uvRegions] to get all regions (as PixelSyncServer.snapshot() does).
Hooks
The hook event shape is also the network command vocabulary - every event maps directly to a network payload.
| Action | Key fields | Notes |
|---|---|---|
"stroke" | color, positions | Covers a full paint stroke, not per-stamp |
"resized" | size | |
"texture-replaced" | size, pixels (base64) | |
"global-fill" | fromColor, toColor | No position list; peers recompute from their own buffer |
"select-edit" | positions, colors (per-pixel) | Unlike "stroke", colors are heterogeneous |
"uv-region-created" | region (full) | |
"uv-region-deleted" | id | |
"uv-region-moved" | id, rect |
All events carry an optional originTimestamp, set only during undo()/redo() replay so the network conflict policy (see PixelSyncServer) re-races the edit at its original time instead of treating it as new.
NOTE
"global-fill" bypasses per-pixel conflict resolution. Undoing it locally replays as a full-position "stroke" since exact undo requires knowing which pixels were touched.