#!/usr/bin/env python3 """ Generate Kindred Create splash screen and about images. This script creates branded splash screens with: - Rounded dark rectangle background (Catppuccin Mocha base #1e1e2e) - Kindred logo centered - "Kindred Create" title text - Version string Requirements: pip install Pillow cairosvg Usage: python generate-splash.py [--version VERSION] [--freecad-version FREECAD_VERSION] """ import argparse import os import sys from pathlib import Path try: from PIL import Image, ImageDraw, ImageFont except ImportError: print("Error: Pillow is required. Install with: pip install Pillow") sys.exit(1) try: import cairosvg except ImportError: print("Warning: cairosvg not found. Will try alternative SVG conversion.") cairosvg = None # Catppuccin Mocha colors COLORS = { "mantle": "#181825", "base": "#1e1e2e", "surface0": "#313244", "text": "#cdd6f4", "subtext0": "#a6adc8", "blue": "#89b4fa", "lavender": "#b4befe", } def hex_to_rgb(hex_color): """Convert hex color to RGB tuple.""" hex_color = hex_color.lstrip("#") return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) def create_rounded_rectangle(draw, bbox, radius, fill): """Draw a rounded rectangle.""" x1, y1, x2, y2 = bbox draw.rounded_rectangle(bbox, radius=radius, fill=fill) def load_svg_as_image(svg_path, width, height): """Load an SVG file and convert to PIL Image at specified size.""" if cairosvg: import io png_data = cairosvg.svg2png(url=str(svg_path), output_width=width, output_height=height) return Image.open(io.BytesIO(png_data)).convert("RGBA") else: # Fallback: try using inkscape command line import subprocess import tempfile with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: tmp_path = tmp.name try: subprocess.run( ["inkscape", "-w", str(width), "-h", str(height), str(svg_path), "-o", tmp_path], check=True, capture_output=True, ) img = Image.open(tmp_path).convert("RGBA") return img finally: if os.path.exists(tmp_path): os.unlink(tmp_path) def get_font(size, bold=False): """Get a font, falling back to default if not available.""" font_names = [ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/TTF/DejaVuSans.ttf", ] for font_name in font_names: if os.path.exists(font_name): return ImageFont.truetype(font_name, size) # Fallback to default return ImageFont.load_default() def create_splash(output_path, logo_path, width, height, version, freecad_version, scale=1): """Create a splash screen image.""" # Scale dimensions w = int(width * scale) h = int(height * scale) radius = int(16 * scale) padding = int(30 * scale) # Create image with transparent background img = Image.new("RGBA", (w, h), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) # Draw rounded rectangle background bg_color = hex_to_rgb(COLORS["mantle"]) create_rounded_rectangle(draw, (padding, padding, w - padding, h - padding), radius, bg_color) # Load and place logo logo_max_width = int(300 * scale) logo_max_height = int(150 * scale) try: logo = load_svg_as_image(logo_path, logo_max_width, logo_max_height) # Center logo horizontally, place in upper portion logo_x = (w - logo.width) // 2 logo_y = int(80 * scale) img.paste(logo, (logo_x, logo_y), logo) except Exception as e: print(f"Warning: Could not load logo: {e}") logo_y = int(80 * scale) # Draw "Kindred Create" title title_font = get_font(int(24 * scale), bold=True) title = "Kindred Create" title_bbox = draw.textbbox((0, 0), title, font=title_font) title_width = title_bbox[2] - title_bbox[0] title_x = (w - title_width) // 2 title_y = int(250 * scale) draw.text((title_x, title_y), title, fill=hex_to_rgb(COLORS["text"]), font=title_font) # Draw version string version_font = get_font(int(12 * scale)) version_str = f"v{version} (FreeCAD {freecad_version})" version_bbox = draw.textbbox((0, 0), version_str, font=version_font) version_width = version_bbox[2] - version_bbox[0] version_x = (w - version_width) // 2 version_y = title_y + int(35 * scale) draw.text( (version_x, version_y), version_str, fill=hex_to_rgb(COLORS["subtext0"]), font=version_font ) # Save img.save(output_path, "PNG") print(f"Created: {output_path}") def create_about(output_path, logo_path, width, height, scale=1): """Create an about dialog image.""" # Scale dimensions w = int(width * scale) h = int(height * scale) # Create image img = Image.new("RGBA", (w, h), hex_to_rgb(COLORS["base"]) + (255,)) draw = ImageDraw.Draw(img) # Load and place logo logo_max_width = int(200 * scale) logo_max_height = int(100 * scale) try: logo = load_svg_as_image(logo_path, logo_max_width, logo_max_height) logo_x = (w - logo.width) // 2 logo_y = int(30 * scale) img.paste(logo, (logo_x, logo_y), logo) except Exception as e: print(f"Warning: Could not load logo: {e}") # Save img.save(output_path, "PNG") print(f"Created: {output_path}") def main(): parser = argparse.ArgumentParser(description="Generate Kindred Create splash screens") parser.add_argument("--version", default="0.1.0", help="Kindred Create version") parser.add_argument("--freecad-version", default="1.0.0", help="FreeCAD version") args = parser.parse_args() script_dir = Path(__file__).parent logo_path = script_dir / "kindred-logo.svg" icons_dir = script_dir.parent.parent / "src" / "Gui" / "Icons" if not logo_path.exists(): print(f"Error: Logo not found at {logo_path}") sys.exit(1) # Create splash screens (600x400 as per spec) create_splash( icons_dir / "kindredcreatesplash.png", logo_path, 600, 400, args.version, args.freecad_version, scale=1, ) # Create 2x version for HiDPI create_splash( icons_dir / "kindredcreatesplash_2x.png", logo_path, 600, 400, args.version, args.freecad_version, scale=2, ) # Create about image create_about(icons_dir / "kindredcreateabout.png", logo_path, 400, 200, scale=1) print("\nSplash screen generation complete!") if __name__ == "__main__": main()