#!/usr/bin/env python3 """ Generate a top-to-bottom linear gradient PNG from three hex colors. Matches: linear-gradient(#2a1a5e, #8b4789, #f4a261) Usage: python gradient_to_png.py --width 1920 --height 1080 --out bg.png """ import argparse from PIL import Image def hex_to_rgb(h: str): """Convert #rrggbb or rrggbb to (r,g,b).""" h = h.strip() if h.startswith("#"): h = h[1:] if len(h) != 6: raise ValueError("Hex color must be 6 hex digits, got: " + h) return tuple(int(h[i:i+2], 16) for i in (0, 2, 4)) def lerp(a: int, b: int, t: float) -> int: """Linear interpolation between integers a and b, t in [0,1].""" return int(round(a + (b - a) * t)) def mix_color(c1, c2, t: float): """Interpolate between two RGB colors (tuple of 3 ints).""" return (lerp(c1[0], c2[0], t), lerp(c1[1], c2[1], t), lerp(c1[2], c2[2], t)) def make_three_stop_vertical_gradient(width: int, height: int, hex1: str, hex2: str, hex3: str) -> Image.Image: """ Create a Pillow Image with a vertical gradient going through three colors evenly: - color1 at top (0%) - color2 at 50% - color3 at bottom (100%) This mirrors `linear-gradient(color1, color2, color3)` in CSS with no explicit stops. """ c1 = hex_to_rgb(hex1) c2 = hex_to_rgb(hex2) c3 = hex_to_rgb(hex3) img = Image.new("RGB", (width, height)) pixels = img.load() # For each row compute color: for y in range(height): t = y / float(max(1, height - 1)) # 0.0..1.0 if t <= 0.5: # interpolate between c1 and c2 (0.0..0.5 -> 0.0..1.0) local_t = t / 0.5 color = mix_color(c1, c2, local_t) else: # interpolate between c2 and c3 (0.5..1.0 -> 0.0..1.0) local_t = (t - 0.5) / 0.5 color = mix_color(c2, c3, local_t) # fill the row with the computed color for x in range(width): pixels[x, y] = color return img def main(): parser = argparse.ArgumentParser(description="Render a 3-stop vertical CSS-like gradient to PNG") parser.add_argument("--width", "-W", type=int, default=1920, help="Output image width in pixels") parser.add_argument("--height", "-H", type=int, default=1080, help="Output image height in pixels") parser.add_argument("--out", "-o", default="gradient.png", help="Output PNG filename") parser.add_argument("--c1", default="#2a1a5e", help="Top color (hex)") parser.add_argument("--c2", default="#8b4789", help="Middle color (hex)") parser.add_argument("--c3", default="#f4a261", help="Bottom color (hex)") args = parser.parse_args() img = make_three_stop_vertical_gradient(args.width, args.height, args.c1, args.c2, args.c3) img.save(args.out, "PNG") print(f"Saved {args.out} ({args.width}x{args.height})") if __name__ == "__main__": main()