GitHub Copilot
Fix: Add Warm-Up Atom (Harmonic Resonance) to Baker to fix black image
594d19b
"""
logos/baker.py - The SPCW Encoder (Fractal Compression)
"""
import numpy as np
from .fractal_engine import ContextAction
class BreadBaker:
"""
Encodes image tiles into Prime Atoms using Quadtree Decomposition.
"""
def __init__(self, variance_threshold=30.0, min_block_size=8):
self.variance_threshold = variance_threshold
self.min_block_size = min_block_size
def bake(self, tile: np.ndarray, prefix_path: list = None) -> list:
"""
Compress a tile into atom definitions.
Returns list of dicts: {'path_bits': [], 'payload': bytes}
"""
self.atoms = []
if prefix_path is None:
prefix_path = []
self._recursive_decompose(tile, level=0, path_bits=prefix_path)
return self.atoms
def _recursive_decompose(self, block: np.ndarray, level: int, path_bits: list):
h, w = block.shape[:2]
if h == 0 or w == 0: return
mean_val = np.mean(block, axis=(0, 1))
variance = np.var(block)
# Stop condition: Low variance OR too small OR max depth
# Max local depth 5 (512 -> 16px blocks)
is_leaf = (variance < self.variance_threshold) or (w <= self.min_block_size) or (level >= 5)
if is_leaf:
self._emit_atom(mean_val, path_bits)
else:
# Subdivide
half_h, half_w = h // 2, w // 2
# TL, TR, BL, BR
self._recursive_decompose(block[:half_h, :half_w], level + 1, path_bits + [0b00])
self._recursive_decompose(block[:half_h, half_w:], level + 1, path_bits + [0b01])
self._recursive_decompose(block[half_h:, :half_w], level + 1, path_bits + [0b10])
self._recursive_decompose(block[half_h:, half_w:], level + 1, path_bits + [0b11])
def _emit_atom(self, rgb_mean, path_bits):
r, g, b = int(rgb_mean[0]), int(rgb_mean[1]), int(rgb_mean[2])
matrix_key = (b << 16) | (g << 8) | r
# --- ATOM 1: WARM UP (Harmonic Resonance) ---
# We must raise heat_state from 0 to 1 so that GF4 multiplication (Dissolution)
# has a non-zero scalar. 0 * Input = 0 (No Change).
# ContextAction.PERSIST (00) increments heat.
warm_action = ContextAction.PERSIST
warm_ctrl = (warm_action.value << 6) & 0xC0
warm_payload = bytes([warm_ctrl])
self.atoms.append({
"path_bits": path_bits,
"payload": warm_payload
})
# --- ATOM 2: STRUCTURE (Matrix Key) ---
# Pack payload compatible with LogoFractalEngine vector decoder
# We pack matrix_key (RGB) into the vector slots
action = ContextAction.CHANGE_1
control_byte = (action.value << 6) & 0xC0
payload_bytes = bytearray()
payload_bytes.append(control_byte)
# Convert RGB integer to 2-bit vector sequence
vector = [(matrix_key >> (i*2)) & 0b11 for i in range(16)]
# Pack vector into 4 bytes
# Note: reconstruction expects this exact packing to retrieve matrix_key
# Byte order matters. We pack vector[0..3] into first byte, etc.
# This aligns with `_payload_to_vector` in fractal_engine.
# Re-verify _payload_to_vector in fractal_engine:
# for byte in payload[:8]:
# append((byte >> 6) & 0b11) ...
# So we pack vector[0] into MSB of byte 0?
# No, `_payload_to_vector` extracts MSB first: `(byte >> 6) & 0b11`.
# So we should pack vector[0] at MSB.
current_vector_idx = 0
for _ in range(4): # 4 bytes to hold 16 pairs
b_val = 0
if current_vector_idx < 16: b_val |= (vector[current_vector_idx] & 0b11) << 6
if current_vector_idx+1 < 16: b_val |= (vector[current_vector_idx+1] & 0b11) << 4
if current_vector_idx+2 < 16: b_val |= (vector[current_vector_idx+2] & 0b11) << 2
if current_vector_idx+3 < 16: b_val |= (vector[current_vector_idx+3] & 0b11)
payload_bytes.append(b_val)
current_vector_idx += 4
self.atoms.append({
"path_bits": path_bits,
"payload": bytes(payload_bytes)
})