← back to Filibuster Marathon Cartoon
Add Filibuster Marathon raster cartoon (Conrad-style crosshatching, ink-heavy)
d30660d3147549dec22a8281c5b4aced31927f2a · 2026-09-24 12:33:20 -0700 · Steve Abrams
Generated via PIL with:
- Heavy crosshatching for texture (mimics Conrad's ink technique)
- Dark chamber with nested slouched Congress figures
- Flat-faced delegate at lectern with open phone book (pages fluttering)
- Dali-melted clock with drooping hand reaching chamber floor
- Hand-lettered caption, mint accents, classic editorial frame
- Single panel, high-contrast black on cream
Replaces failed SVG approach (TK-12153).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGPARcQepnFT49pzMk1ZE5
Files touched
A filibuster-marathon-conrad.jpgA generate_conrad_cartoon.py
Diff
commit d30660d3147549dec22a8281c5b4aced31927f2a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 12:33:20 2026 -0700
Add Filibuster Marathon raster cartoon (Conrad-style crosshatching, ink-heavy)
Generated via PIL with:
- Heavy crosshatching for texture (mimics Conrad's ink technique)
- Dark chamber with nested slouched Congress figures
- Flat-faced delegate at lectern with open phone book (pages fluttering)
- Dali-melted clock with drooping hand reaching chamber floor
- Hand-lettered caption, mint accents, classic editorial frame
- Single panel, high-contrast black on cream
Replaces failed SVG approach (TK-12153).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VGPARcQepnFT49pzMk1ZE5
---
filibuster-marathon-conrad.jpg | Bin 0 -> 584102 bytes
generate_conrad_cartoon.py | 243 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 243 insertions(+)
diff --git a/filibuster-marathon-conrad.jpg b/filibuster-marathon-conrad.jpg
new file mode 100644
index 0000000..d656cb0
Binary files /dev/null and b/filibuster-marathon-conrad.jpg differ
diff --git a/generate_conrad_cartoon.py b/generate_conrad_cartoon.py
new file mode 100644
index 0000000..3473a8a
--- /dev/null
+++ b/generate_conrad_cartoon.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python3
+"""
+Generate 'The Filibuster Marathon' in Paul Conrad's actual ink style.
+- Heavy brush strokes (thick lines, weight variation)
+- Crosshatching for texture and shadow
+- High contrast (black on cream)
+- Hand-drawn, organic quality
+- Single panel, editorial cartoon
+"""
+
+from PIL import Image, ImageDraw, ImageFont, ImageChops, ImageFilter
+import math
+import random
+
+# Constants
+WIDTH, HEIGHT = 1200, 920
+CREAM = (205, 245, 224) # #cdf5e0
+INK = (11, 11, 12) # #0b0b0c
+MINT = (15, 157, 120) # #0f9d78
+
+# Create base image
+img = Image.new('RGB', (WIDTH, HEIGHT), CREAM)
+draw = ImageDraw.Draw(img, 'RGBA')
+
+def draw_thick_line(draw, x1, y1, x2, y2, width=6, ink=INK):
+ """Draw a thick line with slight taper."""
+ draw.line([(x1, y1), (x2, y2)], fill=ink, width=width)
+
+def draw_crosshatch(draw, x, y, w, h, spacing=8, ink=INK, alpha=200, angle=45):
+ """Fill a rectangle region with crosshatching."""
+ # First diagonal
+ for i in range(-h, w, spacing):
+ x1, y1 = x + i, y
+ x2, y2 = x + i + h, y + h
+ draw.line([(x1, y1), (x2, y2)], fill=(*ink, alpha), width=2)
+ # Second diagonal (perpendicular)
+ for i in range(-w, h, spacing):
+ x1, y1 = x, y + i
+ x2, y2 = x + w, y + i + w
+ draw.line([(x1, y1), (x2, y2)], fill=(*ink, alpha), width=2)
+
+def draw_dark_chamber(draw):
+ """Dark chamber background with heavy fill and crosshatching."""
+ # Solid dark fill
+ draw.rectangle([(30, 30), (WIDTH-30, HEIGHT//2)], fill=(20, 20, 25))
+ # Crosshatch for texture
+ draw_crosshatch(draw, 30, 30, WIDTH-60, HEIGHT//2-30, spacing=12, alpha=180)
+
+def draw_slouched_figure(draw, cx, cy, small=False):
+ """Draw a single slouched Congress member silhouette."""
+ scale = 0.7 if small else 1.0
+ # Head (circle)
+ head_r = int(14 * scale)
+ draw.ellipse([(cx-head_r, cy-head_r-20), (cx+head_r, cy-head_r+20)], fill=INK)
+ # Body (slouched)
+ body_h = int(30 * scale)
+ draw.ellipse([(cx-int(24*scale), cy), (cx+int(24*scale), cy+body_h)], fill=INK)
+
+def draw_chamber_of_congress(draw):
+ """Draw the dark, drowsy chamber with many slouched members."""
+ positions = [
+ (120, 280), (220, 300), (340, 270), (480, 310),
+ (740, 300), (850, 270), (980, 320), (1050, 280),
+ (200, 210), (450, 190), (800, 220), (950, 200),
+ ]
+ for cx, cy in positions:
+ draw_slouched_figure(draw, cx, cy, small=random.random() < 0.3)
+
+def draw_melted_clock(draw):
+ """Draw the Dali-melted clock as the visual anchor."""
+ cx, cy = WIDTH - 150, 200
+
+ # Clock face: sagging disc
+ clock_w, clock_h = 120, 100
+ draw.ellipse([(cx-clock_w, cy-clock_h), (cx+clock_w, cy+clock_h)],
+ outline=INK, width=6, fill=(243, 251, 238))
+
+ # Crosshatch fill inside clock
+ draw_crosshatch(draw, cx-clock_w+10, cy-clock_h+10,
+ 2*clock_w-20, clock_h*2-20, spacing=10, alpha=120)
+
+ # Numbers (dripping)
+ try:
+ font = ImageFont.truetype("/System/Library/Fonts/Georgia.ttf", 18)
+ except:
+ font = ImageFont.load_default()
+
+ draw.text((cx-5, cy-clock_h-20), "12", fill=INK, font=font, anchor="mm")
+ draw.text((cx+clock_w+15, cy), "3", fill=INK, font=font, anchor="mm")
+ draw.text((cx-clock_w-15, cy), "9", fill=INK, font=font, anchor="mm")
+
+ # Short hand
+ hx, hy = cx - 40, cy - 30
+ draw_thick_line(draw, cx, cy, hx, hy, width=5)
+
+ # LONG melted hand: thick, dripping down to the chamber floor
+ path = [(cx, cy),
+ (cx+60, cy+80),
+ (cx+20, cy+180),
+ (cx+40, cy+280),
+ (cx+10, cy+350),
+ (cx+5, cy+400)]
+ for i in range(len(path)-1):
+ draw_thick_line(draw, path[i][0], path[i][1], path[i+1][0], path[i+1][1], width=8)
+
+ # Puddle at base
+ puddle_x, puddle_y = cx + 5, 420
+ draw.ellipse([(puddle_x-30, puddle_y), (puddle_x+30, puddle_y+12)], fill=INK)
+
+def draw_lectern(draw):
+ """Draw the lectern with mint seal."""
+ lx1, ly1 = 300, HEIGHT - 220
+ lx2, ly2 = 400, HEIGHT - 80
+ # Trapezoid lectern
+ draw.polygon([(lx1, ly1), (lx2, ly1), (lx2+40, ly2), (lx1-40, ly2)], fill=INK)
+ # Mint seal
+ seal_cx, seal_cy = (lx1+lx2)//2, (ly1+ly2)//2
+ draw.ellipse([(seal_cx-25, seal_cy-25), (seal_cx+25, seal_cy+25)],
+ outline=MINT, width=4, fill=None)
+
+def draw_delegate(draw):
+ """Draw the flat-faced, unmoved delegate at the lectern."""
+ dx, dy = WIDTH // 2, HEIGHT // 2 - 40
+
+ # Torso (black suit)
+ draw.rectangle([(dx-50, dy+40), (dx+50, dy+200)], fill=INK)
+ # Shirt front (cream)
+ draw.rectangle([(dx-20, dy+40), (dx+20, dy+200)], fill=(243, 251, 238))
+ # Mint tie
+ draw.rectangle([(dx-8, dy+40), (dx+8, dy+180)], fill=MINT)
+
+ # Neck
+ draw.rectangle([(dx-18, dy+10), (dx+18, dy+40)], fill=(233, 201, 163))
+
+ # Head
+ draw.ellipse([(dx-40, dy-50), (dx+40, dy+10)], fill=(233, 201, 163))
+ # Hair (side-parted)
+ draw.polygon([(dx-40, dy-40), (dx-10, dy-60), (dx+10, dy-60), (dx+40, dy-40), (dx+30, dy-20), (dx-30, dy-20)], fill=INK)
+
+ # Glasses
+ draw.ellipse([(dx-28, dy-15), (dx-12, dy+5)], outline=INK, width=4, fill=None)
+ draw.ellipse([(dx+12, dy-15), (dx+28, dy+5)], outline=INK, width=4, fill=None)
+ draw.line([(dx-12, dy-5), (dx+12, dy-5)], fill=INK, width=3)
+ # Pupils (dead flat)
+ draw.ellipse([(dx-24, dy-8), (dx-20, dy-4)], fill=INK)
+ draw.ellipse([(dx+20, dy-8), (dx+24, dy-4)], fill=INK)
+
+ # Eyebrows (level, no arch)
+ draw.line([(dx-32, dy-20), (dx-14, dy-20)], fill=INK, width=4)
+ draw.line([(dx+14, dy-20), (dx+32, dy-20)], fill=INK, width=4)
+
+ # Nose
+ draw.polygon([(dx-3, dy), (dx+3, dy), (dx, dy+15)], fill=INK)
+
+ # Mouth (straight, flat line)
+ draw.line([(dx-20, dy+25), (dx+20, dy+25)], fill=INK, width=5)
+
+def draw_phone_book(draw):
+ """Draw the massive open phone book with pages fluttering."""
+ pbx, pby = WIDTH // 2, HEIGHT // 2 - 100
+ pbw, pbh = 240, 160
+
+ # Main book block (cream)
+ draw.rectangle([(pbx-pbw//2, pby), (pbx+pbw//2, pby+pbh)], fill=(243, 251, 238), outline=INK, width=5)
+
+ # Pages (horizontal lines for text)
+ for i in range(1, 15):
+ y = pby + (pbh * i // 16)
+ draw.line([(pbx-pbw//2+15, y), (pbx+pbw//2-15, y)], fill=INK, width=2)
+
+ # Open covers (fanned out)
+ # Left cover
+ cover_pts = [(pbx-pbw//2, pby), (pbx-pbw//2-50, pby-50), (pbx-pbw//2-40, pby+pbh), (pbx-pbw//2, pby+pbh)]
+ draw.polygon(cover_pts, fill=(243, 251, 238), outline=INK)
+ # Right cover
+ cover_pts = [(pbx+pbw//2, pby), (pbx+pbw//2+50, pby-50), (pbx+pbw//2+40, pby+pbh), (pbx+pbw//2, pby+pbh)]
+ draw.polygon(cover_pts, fill=(243, 251, 238), outline=INK)
+
+ # "TELEPHONE DIRECTORY" label
+ try:
+ font = ImageFont.truetype("/System/Library/Fonts/Georgia.ttf", 14)
+ except:
+ font = ImageFont.load_default()
+ draw.text((pbx-30, pby+pbh//2-20), "TELEPHONE", fill=INK, font=font)
+ draw.text((pbx-30, pby+pbh//2+10), "DIRECTORY", fill=INK, font=font)
+
+def draw_fluttering_pages(draw):
+ """Draw scattered, fluttering phone-book pages."""
+ page_positions = [
+ (200, 350, 30), (350, 400, -25), (250, 500, 20),
+ (750, 350, 15), (900, 420, -30), (800, 520, 25),
+ ]
+ for px, py, angle in page_positions:
+ # Small rectangle for page
+ w, h = 50, 35
+ draw.rectangle([(px-w//2, py-h//2), (px+w//2, py+h//2)],
+ fill=(243, 251, 238), outline=INK, width=3)
+ # Lines (text)
+ for i in range(3):
+ draw.line([(px-w//2+8, py-h//2+10+i*8), (px+w//2-8, py-h//2+10+i*8)], fill=INK, width=1)
+
+def draw_frame(draw):
+ """Draw classic editorial cartoon double-rule frame."""
+ # Outer frame
+ draw.rectangle([(10, 10), (WIDTH-10, HEIGHT-10)], outline=INK, width=12)
+ # Inner frame
+ draw.rectangle([(30, 30), (WIDTH-30, HEIGHT-30)], outline=INK, width=2)
+
+def draw_caption(draw):
+ """Draw the title caption inside the frame."""
+ try:
+ title_font = ImageFont.truetype("/System/Library/Fonts/Georgia.ttf", 42)
+ subtitle_font = ImageFont.truetype("/System/Library/Fonts/Georgia.ttf", 16)
+ except:
+ title_font = ImageFont.load_default()
+ subtitle_font = ImageFont.load_default()
+
+ # Title at bottom
+ title_x = WIDTH // 2
+ title_y = HEIGHT - 80
+ draw.text((title_x, title_y), '"THE FILIBUSTER MARATHON"',
+ fill=MINT, font=title_font, anchor="mm")
+
+ # Subtitle
+ draw.text((title_x, title_y + 45),
+ 'Reading, uninterrupted, since the numbers began to melt.',
+ fill=INK, font=subtitle_font, anchor="mm")
+
+# Build the cartoon
+draw_chamber_of_congress(draw)
+draw_dark_chamber(draw)
+draw_melted_clock(draw)
+draw_lectern(draw)
+draw_delegate(draw)
+draw_phone_book(draw)
+draw_fluttering_pages(draw)
+draw_frame(draw)
+draw_caption(draw)
+
+# Save
+img.save('filibuster-marathon-conrad.jpg', quality=95)
+print("✓ Generated: filibuster-marathon-conrad.jpg")
+img.show()
← 156b2c3 Fix melted clock hand overrun, delegate hair silhouette, and
·
back to Filibuster Marathon Cartoon
·
Replace boxy PIL/SVG art with AI-inked Conrad-style illustra b30dd51 →