import sys import os from PIL import Image import cairosvg import io import xml.etree.ElementTree as ET threshold = 128 def parse_svg_intrinsic_size(svg_data): try: root = ET.fromstring(svg_data) except ET.ParseError: return None, None viewbox = root.get("viewBox") or root.get("viewbox") if viewbox: parts = viewbox.replace(",", " ").split() if len(parts) == 4: try: vb_w = float(parts[2]) vb_h = float(parts[3]) if vb_w > 0 and vb_h > 0: return vb_w, vb_h except ValueError: pass def parse_len(value): if not value: return None cleaned = "".join(ch for ch in value if ch.isdigit() or ch in ".-") if not cleaned: return None try: parsed = float(cleaned) return parsed if parsed > 0 else None except ValueError: return None w = parse_len(root.get("width")) h = parse_len(root.get("height")) return w, h def fit_inside_canvas(src_w, src_h, dst_w, dst_h): if src_w <= 0 or src_h <= 0 or dst_w <= 0 or dst_h <= 0: return dst_w, dst_h src_ratio = src_w / src_h dst_ratio = dst_w / dst_h if src_ratio >= dst_ratio: fit_w = dst_w fit_h = max(1, int(round(fit_w / src_ratio))) else: fit_h = dst_h fit_w = max(1, int(round(fit_h * src_ratio))) return fit_w, fit_h def svg_to_png_bytes(svg_path, width, height): with open(svg_path, "rb") as f: svg_data = f.read() src_w, src_h = parse_svg_intrinsic_size(svg_data) render_w, render_h = fit_inside_canvas( src_w or width, src_h or height, width, height ) png_bytes = cairosvg.svg2png( bytestring=svg_data, output_width=render_w, output_height=render_h ) return png_bytes def center_on_canvas(img, width, height): if img.mode != "RGBA": img = img.convert("RGBA") canvas = Image.new("RGBA", (width, height), (255, 255, 255, 255)) x = (width - img.width) // 2 y = (height - img.height) // 2 canvas.paste(img, (x, y), img) return canvas def load_image(path, width, height): ext = os.path.splitext(path)[1].lower() if ext == ".svg": png_bytes = svg_to_png_bytes(path, width, height) img = Image.open(io.BytesIO(png_bytes)).convert("RGBA") img = center_on_canvas(img, width, height) else: img = Image.open(path).convert("RGBA") # Keep source aspect ratio and fit inside requested canvas. fit = img.copy() fit.thumbnail((width, height), Image.LANCZOS) img = center_on_canvas(fit, width, height) # Flatten alpha: paste on white background background = Image.new("RGBA", img.size, (255, 255, 255, 255)) background.paste(img, mask=img.split()[3]) img = background # Rotate 90 degrees counterclockwise img = img.rotate(90, expand=True) return img def image_to_c_array(img, array_name): # Convert to grayscale, then threshold to get white=1, black=0 # Convert to grayscale img = img.convert("L") width, height = img.size pixels = list(img.getdata()) packed = [] for y in range(height): for x in range(0, width, 8): byte = 0 for b in range(8): if x + b < width: v = pixels[y * width + x + b] # 1 for white, 0 for black bit = 1 if v >= threshold else 0 byte |= bit << (7 - b) packed.append(byte) # Format as C array c = f"#pragma once\n#include \n\n" c += f"// size: {width}x{height}\n" c += f"static const uint8_t {array_name}[] = {{\n " for i, v in enumerate(packed): c += f"0x{v:02X}, " if (i + 1) % 16 == 0: c += "\n " c = c.rstrip(", \n") + "\n};\n" return c def main(): if len(sys.argv) < 5: print("Usage: python convert_image.py input.png output_name width height") sys.exit(1) input_path, output_name, width, height = sys.argv[1:5] array_name = output_name.capitalize() + "Icon" width, height = int(width), int(height) img = load_image(input_path, width, height) c_array = image_to_c_array(img, array_name) # Always save to src/components/icons/[output_name].h relative to project root project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) output_dir = os.path.join(project_root, "src", "components", "icons") os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, f"{output_name}.h") with open(output_path, "w") as f: f.write(c_array) print(f"Wrote {output_path}") if __name__ == "__main__": main()