On this page
Authoritative RGBA source-over layer compositing and buffer merging, a byte-exact port of the client compositor so server-side flatten and merge match the browser.
#server.src.pixelweaver.layer_composite
#server.src.pixelweaver.layer_composite
Authoritative layer compositing -- a faithful port of the client compositor.
Line-for-line mirror of the RGBA source-over math in src/lib/layers/compositor.ts (compositePixel / compositeLayer / mergePixelBuffers) so a server-side merge/flatten produces the exact same bytes the browser showed. The one deliberate difference is the opacity scale: the server model stores layer opacity on a 0.0-1.0 scale (see state.py and part_export), whereas the client's Layer.opacity is 0-100; these functions take the server-native 0.0-1.0 value directly (no /100).
Integer channel math and JS Math.round (round-half-up) are reproduced exactly so golden pixels match across the two implementations.
#_blend_fn
def _blend_fn(mode: str)Resolve a blend function; unknown modes are a hard error (the client vocabulary is fixed to these four).
#_js_round
def _js_round(x: float) -> intJS Math.round: round half up toward +infinity (not banker's).
#_composite_pixel
def _composite_pixel(dst: bytearray, di: int, src_r: int, src_g: int, src_b: int, src_a: int, blend_fn) -> NoneSource-over composite one src pixel (src_a already opacity-adjusted, 0-255) onto dst at byte offset di. Mutates dst in place.
#composite_layer
def composite_layer(dst: bytearray, src: bytes, width: int, height: int, opacity: float, blend_mode: str, dx: int=0, dy: int=0) -> NoneComposite src (RGBA bytes, widthheight4) onto dst in place, optionally translated by (dx, dy) whole pixels (clip, no wrap).
opacity is the server-native 0.0-1.0 scale (used directly as the alpha factor). dst and src share dimensions.
#composite_tree
def composite_tree(layers: list[dict], pixel_data: dict[str, bytes], width: int, height: int) -> bytesRecursively composite an RGBA layer subtree into a single buffer.
Faithful port of the client compositeTree (compositor.ts) for the rgba-only case: walks bottom-to-top, honoring each layer's visibility, persistent offset (offsetX/offsetY), opacity, and blend mode; a group is composited into an intermediate buffer then blended onto the result with the group's own opacity/blend/offset. Layer opacity is the server-native 0.0-1.0 scale (used directly). Used by flatten_group.
Role-kind leaves are a hard error here -- flatten_group role-guards its descendants before compositing, so hitting one means corrupted input.
#merge_pixel_buffers
def merge_pixel_buffers(bottom: bytes, top: bytes, width: int, height: int, top_opacity: float, top_blend_mode: str) -> bytesReturn a NEW buffer: top composited over bottom (source-over with the top layer's opacity/blend). Inputs are not mutated. Mirror of the client mergePixelBuffers used by merge_down / flatten_image.