๐Ÿ“„ composition.md

โ† Vault

Composition & Brightness Reference

The composable system is the core of visual complexity. It operates at three levels: pixel-level blend modes, multi-grid composition, and adaptive brightness management. This document covers all three, plus the masking/stencil system for spatial control.

> See also: architecture.md ยท effects.md ยท scenes.md ยท shaders.md ยท troubleshooting.md

Pixel-Level Blend Modes

The blend_canvas() Function

All blending operates on full pixel canvases (uint8 H,W,3). Internally converts to float32 [0,1] for precision, blends, lerps by opacity, converts back.

`python

def blend_canvas(base, top, mode="normal", opacity=1.0):

af = base.astype(np.float32) / 255.0

bf = top.astype(np.float32) / 255.0

fn = BLEND_MODES.get(mode, BLEND_MODES["normal"])

result = fn(af, bf)

if opacity < 1.0:

result = af (1 - opacity) + result opacity

return np.clip(result * 255, 0, 255).astype(np.uint8)

`

20 Blend Modes

`python

BLEND_MODES = {

# Basic arithmetic

"normal": lambda a, b: b,

"add": lambda a, b: np.clip(a + b, 0, 1),

"subtract": lambda a, b: np.clip(a - b, 0, 1),

"multiply": lambda a, b: a * b,

"screen": lambda a, b: 1 - (1 - a) * (1 - b),

# Contrast

"overlay": lambda a, b: np.where(a < 0.5, 2ab, 1 - 2(1-a)(1-b)),

"softlight": lambda a, b: (1 - 2b)aa + 2b*a,

"hardlight": lambda a, b: np.where(b < 0.5, 2ab, 1 - 2(1-a)(1-b)),

# Difference

"difference": lambda a, b: np.abs(a - b),

"exclusion": lambda a, b: a + b - 2ab,

# Dodge / burn

"colordodge": lambda a, b: np.clip(a / (1 - b + 1e-6), 0, 1),

"colorburn": lambda a, b: np.clip(1 - (1 - a) / (b + 1e-6), 0, 1),

# Light

"linearlight": lambda a, b: np.clip(a + 2*b - 1, 0, 1),

"vividlight": lambda a, b: np.where(b < 0.5,

np.clip(1 - (1-a)/(2*b + 1e-6), 0, 1),

np.clip(a / (2*(1-b) + 1e-6), 0, 1)),

"pin_light": lambda a, b: np.where(b < 0.5,

np.minimum(a, 2b), np.maximum(a, 2b - 1)),

"hard_mix": lambda a, b: np.where(a + b >= 1.0, 1.0, 0.0),

# Compare

"lighten": lambda a, b: np.maximum(a, b),

"darken": lambda a, b: np.minimum(a, b),

# Grain

"grain_extract": lambda a, b: np.clip(a - b + 0.5, 0, 1),

"grain_merge": lambda a, b: np.clip(a + b - 0.5, 0, 1),

}

`

Blend Mode Selection Guide

Modes that brighten (safe for dark inputs):

Running the oracle

Use Playwright to run the layout engine in a headless browser:

`javascript

// extract.mjs

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });

const page = await browser.newPage();

await page.goto(file://${oraclePath});

await page.waitForFunction(() => window.__ORACLE_DONE__ === true, null, { timeout: 60000 });

const result = await page.evaluate(() => window.__ORACLE_RESULT__);

writeFileSync('layouts.json', JSON.stringify(result));

await browser.close();

`

Consuming in Python

`python

In the renderer, map pixel positions to the canvas:

for glyph in frame_data['glyphs']:

char, px, py = glyph['char'], glyph['x'], glyph['y']

alpha = glyph.get('alpha', 1.0)

# Render using PIL draw.text() at exact pixel position

draw.text((px, py), char, fill=(int(255alpha),)3, font=font)

`

Obstacles from the JSON can also be rendered as glowing ASCII shapes (circles, rectangles) to visualize the reflow zones.