PixelWeaver v0.5.0 /server.src.pixelweaver.variant_palette
On this page

Byte-exact Python port of the client's culori-backed variant palette math: color conversions, palette extraction, palette swaps, and the seeded HSV randomizer.

#server.src.pixelweaver.variant_palette

#server.src.pixelweaver.variant_palette

Faithful Python port of the client variant palette math.

This is a BYTE-EXACT port of the browser's culori-backed color pipeline used by the variant subsystem:

- color conversions (src/lib/color/color-utils.ts): rgb<->hex, rgb<->hsv, and the OKLab lerpColor interpolation; - palette extraction (palette-extraction.ts); - palette swap application (palette-swap.ts) -- the baking primitive; - the seeded HSV randomizer (variant-randomizer.ts + seeded-rng.ts).

Parity is pinned by server/tests/test_variant_palette.py against golden fixtures generated by RUNNING the actual TypeScript (see src/lib/variants/golden-fixtures.gen.test.ts). Every float op mirrors culori's source (matrices, transfer functions, rounding) so the 8-bit output matches to the byte. The one place JS and Python diverge -- rounding -- is handled by _js_round (JS Math.round is half-up toward +Infinity, unlike Python's banker's rounding).

#_js_round

python
def _js_round(x: float) -> int

JS Math.round: round half up toward +Infinity (not banker's).

#_clamp_round

python
def _clamp_round(value: float, lo: float, hi: float) -> int

color-utils clampRound: clamp to [lo, hi] THEN JS-round.

#_fixup

python
def _fixup(c: float) -> int

culori serializeHex fixup: Math.round(clamp(c) * 255) on a 0-1 value.

#rgb_to_hex

python
def rgb_to_hex(r: float, g: float, b: float) -> str

RGB (0-255) -> uppercase #RRGGBB (mirror of rgbToHex).

#hex_to_rgb

python
def hex_to_rgb(hex_color: str) -> tuple[int, int, int]

#rgb / #rrggbb (optional #rrggbbaa) -> RGB ints (mirror of hexToRgb: culori parses to 0-1 then clampRound(*255), which for these hex forms is exactly the byte value).

#rgb_to_hsv

python
def rgb_to_hsv(r: float, g: float, b: float) -> tuple[int, int, int]

RGB (0-255) -> HSV (H 0-360, S 0-100, V 0-100) as clampRound ints.

#_normalize_hue

python
def _normalize_hue(h: float) -> float

culori normalizeHue: (h % 360) < 0 ? +360.

#hsv_to_rgb

python
def hsv_to_rgb(h: float, s: float, v: float) -> tuple[int, int, int]

HSV (H 0-360, S 0-100, V 0-100) -> RGB (0-255) as clampRound ints.

h may be a float (the randomizer feeds unrounded shifted hues); s/v are on the 0-100 scale (divided by 100 before the culori conversion).

#lerp_color

python
def lerp_color(color1: str, color2: str, t: float) -> str

Interpolate two hex colors in OKLab (mirror of lerpColor).

culori's 2-sample linear interpolator is a + t*(b-a) per channel.

#extract_buffer_palette

python
def extract_buffer_palette(data: bytes | bytearray | Iterable[int]) -> list[str]

Extract unique opaque colors (uppercase hex, insertion order) from an RGBA buffer. Transparent pixels (alpha == 0) are skipped (mirror of extractBufferPalette).

#apply_palette_swap

python
def apply_palette_swap(data: bytes | bytearray, color_map: dict[str, str]) -> bytes

Apply an exact-hex-match color swap to an RGBA buffer, preserving alpha.

Returns a new buffer (never mutates the input). Transparent pixels are skipped. An empty map returns an unchanged copy (mirror of applyPaletteSwap).

#interpolate_presets

python
def interpolate_presets(preset_a: _GroupOverrides, preset_b: _GroupOverrides, t: float) -> _GroupOverrides

Interpolate two presets' groupOverrides at factor t (mirror of interpolatePresets). A missing override in one endpoint is treated as the original color (identity).

#generate_interpolated_series

python
def generate_interpolated_series(preset_a: _GroupOverrides, preset_b: _GroupOverrides, count: int) -> list[_GroupOverrides]

N evenly-spaced interpolations (mirror of generateInterpolatedSeries).

#create_rng

python
def create_rng(seed: int)

xorshift32 PRNG (mirror of createRng): floats in [0, 1); zero seed coerced to 1. Every op is truncated to signed int32 like JS bitwise ops.

#generate_random_variant

python
def generate_random_variant(group_palettes: dict[str, list[str]], *, seed: int, hue_range: float=360, sat_range: float=30, val_range: float=20) -> _GroupOverrides

Seeded HSV randomization of each group's palette (mirror of generateRandomVariant with a required seed).

One uniform hue rotation per group (all colors shift together); per-color saturation + value jitter. The RNG stream order is: one draw for the group hue, then two draws (sat, val) per color -- exactly the client's loop order, so the same seed yields identical overrides.

Search