If I'm using the same track templates again and again (e.g. if studying one or two synths), I'll add a toolbar icon to insert a track. To make icons for these, I got Gemini to knock up the following script. Defaults go in `mkicon.json`. ```json { "bg_color": "#000000", "fg_color": "#FFFFFF", "font": "/path/to/font", "size": 30 } ``` Usage: ```bash ./mkicon --fg-color "#F00" --bg-color "#050" -o icon_name.png Xyz ``` Source: ```py #!/usr/bin/env python3 import argparse import json import os import sys from PIL import Image, ImageDraw, ImageEnhance, ImageFont CONFIG_FILE = "mkicon.json" def load_config() -> dict: """Loads default configuration from mkicon.json if present.""" if os.path.exists(CONFIG_FILE): try: with open(CONFIG_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception as e: print( f"Warning: Failed to load {CONFIG_FILE}: {e}", file=sys.stderr ) return {} def make_reaper_icon( text: str, output_path: str = "toolbar_icon.png", bg_color: str = "#000000", fg_color: str = "#FFFFFF", bg_image_path: str | None = None, icon_size: int = 30, font_path: str | None = "arialbd.ttf", ): """Generates a 3-state horizontal Reaper toolbar icon PNG (90x30).""" # 1. Base Image Setup if bg_image_path: try: img = Image.open(bg_image_path).convert("RGBA") except FileNotFoundError: print( f"Error: Background image file '{bg_image_path}' not found.", file=sys.stderr, ) sys.exit(1) width, height = img.size # Center-crop to square min_dim = min(width, height) left = (width - min_dim) // 2 top = (height - min_dim) // 2 img = img.crop((left, top, left + min_dim, top + min_dim)) # Resize to target icon size base_icon = img.resize( (icon_size, icon_size), Image.Resampling.LANCZOS ) else: base_icon = Image.new("RGBA", (icon_size, icon_size), color=bg_color) # 2. Add Centered Text draw = ImageDraw.Draw(base_icon) # Auto-adjust font size based on text length font_size = int(icon_size * (0.65 if len(text) <= 2 else 0.45)) font = None # Try requested/default font paths font_candidates = [font_path] if font_path else [] font_candidates.extend(["arialbd.ttf", "Arial Bold.ttf", "arial.ttf"]) for candidate in font_candidates: if candidate: try: font = ImageFont.truetype(candidate, font_size) break except OSError: continue if font is None: font = ImageFont.load_default() # Precise center alignment bbox = draw.textbbox((0, 0), text, font=font) text_w = bbox[2] - bbox[0] text_h = bbox[3] - bbox[1] x = (icon_size - text_w) / 2 - bbox[0] y = (icon_size - text_h) / 2 - bbox[1] draw.text((x, y), text, fill=fg_color, font=font) # 3. Create Hover and Pressed States normal_state = base_icon hover_state = ImageEnhance.Brightness(normal_state).enhance(1.2) pressed_state = ImageEnhance.Brightness(normal_state).enhance(0.8) # 4. Stack Horizontally into 3x1 Reaper Format (90x30) reaper_icon = Image.new("RGBA", (icon_size * 3, icon_size)) reaper_icon.paste(normal_state, (0, 0)) reaper_icon.paste(hover_state, (icon_size, 0)) reaper_icon.paste(pressed_state, (icon_size * 2, 0)) reaper_icon.save(output_path, "PNG") print( f"Successfully generated Reaper icon: {output_path} ({icon_size * 3}x{icon_size}px)" ) def main(): config = load_config() parser = argparse.ArgumentParser( description="Generate a horizontal 3-state Reaper toolbar icon PNG (90x30)." ) # Required Argument parser.add_argument( "text", type=str, help="Text to display on the icon (e.g., 'HZ', 'Z3', 'Sp')", ) # Optional Arguments (defaults pulled from mkicon.json if present) parser.add_argument( "-o", "--output", type=str, default=config.get("output", "toolbar_icon.png"), help="Output PNG filepath (default: toolbar_icon.png)", ) parser.add_argument( "--bg-color", type=str, default=config.get("bg_color", "#000000"), help="Background hex color or name (default: #000000)", ) parser.add_argument( "--fg-color", type=str, default=config.get("fg_color", "#FFFFFF"), help="Foreground text hex color or name (default: #FFFFFF)", ) parser.add_argument( "--bg-image", type=str, default=config.get("bg_image", None), help="Optional path to a background image (will be center-cropped to square)", ) parser.add_argument( "--size", type=int, default=config.get("size", 30), help="Width/height of a single state in pixels (default: 30, yields 90x30 image)", ) parser.add_argument( "--font", type=str, default=config.get("font", "arialbd.ttf"), help="Path to a custom TTF/OTF font file (default: Arial Bold)", ) args = parser.parse_args() make_reaper_icon( text=args.text, output_path=args.output, bg_color=args.bg_color, fg_color=args.fg_color, bg_image_path=args.bg_image, icon_size=args.size, font_path=args.font, ) if __name__ == "__main__": main() ```