#!/usr/bin/env python3 """Generate a 1-bit book icon bitmap as a C header for PlaceholderCoverGenerator. The icon is a simplified closed book with a spine on the left and 3 text lines. Output format matches Logo120.h: MSB-first packed 1-bit, 0=black, 1=white. """ from PIL import Image, ImageDraw import sys def generate_book_icon(size=48): """Create a book icon at the given size.""" img = Image.new("1", (size, size), 1) # White background draw = ImageDraw.Draw(img) # Scale helper s = size / 48.0 # Book body (main rectangle, leaving room for spine and pages) body_left = int(6 * s) body_top = int(2 * s) body_right = int(42 * s) body_bottom = int(40 * s) # Draw book body outline (2px thick) for i in range(int(2 * s)): draw.rectangle( [body_left + i, body_top + i, body_right - i, body_bottom - i], outline=0 ) # Spine (thicker left edge) spine_width = int(4 * s) draw.rectangle([body_left, body_top, body_left + spine_width, body_bottom], fill=0) # Pages at the bottom (slight offset from body) pages_top = body_bottom pages_bottom = int(44 * s) draw.rectangle( [body_left + int(2 * s), pages_top, body_right - int(1 * s), pages_bottom], outline=0, ) # Page edges (a few lines) for i in range(3): y = pages_top + int((i + 1) * 1 * s) if y < pages_bottom: draw.line( [body_left + int(3 * s), y, body_right - int(2 * s), y], fill=0 ) # Text lines on the book cover text_left = body_left + spine_width + int(4 * s) text_right = body_right - int(4 * s) line_thickness = max(1, int(1.5 * s)) text_lines_y = [int(12 * s), int(18 * s), int(24 * s)] text_widths = [1.0, 0.7, 0.85] # Relative widths for visual interest for y, w_ratio in zip(text_lines_y, text_widths): line_right = text_left + int((text_right - text_left) * w_ratio) for t in range(line_thickness): draw.line([text_left, y + t, line_right, y + t], fill=0) return img def image_to_c_array(img, name="BookIcon"): """Convert a 1-bit PIL image to a C header array.""" width, height = img.size pixels = img.load() bytes_per_row = width // 8 data = [] for y in range(height): for bx in range(bytes_per_row): byte = 0 for bit in range(8): x = bx * 8 + bit if x < width: # 1 = white, 0 = black (matching Logo120.h convention) if pixels[x, y]: byte |= 1 << (7 - bit) data.append(byte) # Format as C header lines = [] lines.append("#pragma once") lines.append("#include ") lines.append("") lines.append(f"// Book icon: {width}x{height}, 1-bit packed (MSB first)") lines.append(f"// 0 = black, 1 = white (same format as Logo120.h)") lines.append(f"static constexpr int BOOK_ICON_WIDTH = {width};") lines.append(f"static constexpr int BOOK_ICON_HEIGHT = {height};") lines.append(f"static const uint8_t {name}[] = {{") # Format data in rows of 16 bytes for i in range(0, len(data), 16): chunk = data[i : i + 16] hex_str = ", ".join(f"0x{b:02x}" for b in chunk) lines.append(f" {hex_str},") lines.append("};") lines.append("") return "\n".join(lines) if __name__ == "__main__": size = int(sys.argv[1]) if len(sys.argv) > 1 else 48 img = generate_book_icon(size) # Save preview PNG preview_path = f"mod/book_icon_{size}x{size}.png" img.resize((size * 4, size * 4), Image.NEAREST).save(preview_path) print(f"Preview saved to {preview_path}", file=sys.stderr) # Generate C header header = image_to_c_array(img, "BookIcon") output_path = "lib/PlaceholderCover/BookIcon.h" with open(output_path, "w") as f: f.write(header) print(f"C header saved to {output_path}", file=sys.stderr)