---
title: "How to add a comic book with Claude (tested prompt and script)"
description: "At one second the shot is redrawn as a printed comic panel: the pastel backdrop becomes flat periwinkle blue and white, the bottle a bold purple with thick black outlines, and black halftone dots shade the glass over a fine grid of print dots. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/comic-book
---

# How to add a comic book with Claude

At one second the shot is redrawn as a printed comic panel: the pastel backdrop becomes flat periwinkle blue and white, the bottle a bold purple with thick black outlines, and black halftone dots shade the glass over a fine grid of print dots.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skills
   ```

3. ### Ask for the effect

   ```
   Add a comic book to input.mp4
   Second the shot turns into a comic panel: 1.0
   Number of flat fill colors: 6
   ```

## Several videos, several effects, one ask

Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.

Add a comic book to **@clip1.mp4**, put a sale badge on **@clip2.mp4**, give **@clip3.mp4** a VHS look, then join all three with crossfades.

## Settings you can change

Ask Claude to change any of these, or edit them at the top of the script.

| Setting                  | Default        | What it does                                                                               |
| ------------------------ | -------------- | ------------------------------------------------------------------------------------------ |
| `START`                  | `1.0`          | Second the shot turns into a comic panel.                                                  |
| `COLORS`                 | `6`            | Number of flat fill colors. Fewer gives a bolder, more poster-like panel.                  |
| `VIVID`                  | `3.0`          | How much more saturated the fills are than the footage.                                    |
| `LIFT`                   | `1.1`          | How much brighter the fills are than the footage.                                          |
| `CELL`                   | `8`            | Pixels between halftone dot centers. Bigger gives a coarser print.                         |
| `ANGLE`                  | `45`           | Angle of the dot rows in degrees.                                                          |
| `DOTS_FROM`              | `0.5`          | How dark (0 to 1) an area must be before black shading dots appear.                        |
| `PRINT_DOT, PRINT_SHADE` | `0.3, 0.75`    | Size (in cells) and shade of the fine print dots over every fill; a shade of 1 hides them. |
| `LINES`                  | `(20, 70)`     | Canny edge thresholds. Lower draws more outlines.                                          |
| `LINE`                   | `4`            | Outline thickness in pixels.                                                               |
| `INK`                    | `(15, 10, 20)` | Outline and shading dot color.                                                             |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Give input.mp4 (540x960) a comic book look from 1.0 seconds to the end; before 1.0 seconds leave it untouched. On the first frame at 1.0 s, smooth it with three passes of a bilateral filter (diameter 9, color sigma 50, space sigma 7) and pick 6 main colors with k-means; make those colors comic inks by multiplying their HSV saturation by 3.0 and their brightness by 1.1 (capped at full), and keep this palette for every later frame so it doesn't flicker. For each frame from 1.0 s: smooth it the same way, replace every pixel with the ink of its nearest k-means color and median-blur that with a 5 pixel window, giving flat fills. Over every fill print small dots of the same ink at 75% brightness: radius 2.4 pixels on a square grid with 8 pixels between dot centers, turned 45 degrees. Then add near-black (15, 10, 20) halftone shading on the same grid: blur the smoothed grayscale with a Gaussian of sigma 4, stretch it so the 2nd percentile is darkness 1 and the 98th darkness 0, and where darkness is above 0.5 draw a dot of radius 5.76 pixels times the square root of (darkness - 0.5) / 0.5, so the deepest shadows go solid. Finally draw near-black (15, 10, 20) outlines 4 pixels thick along Canny edges (thresholds 20 and 70) of the smoothed grayscale, softened with a 0.8 pixel blur. Use Python with OpenCV and numpy for the frames and ffmpeg to read and write the video (don't use ffmpeg's drawtext if you add any text), keep the size, 30fps, the 6 second length and the audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `comic-book.py` and run:

```
python3 comic-book.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Comic book: at one second the shot becomes a printed comic panel, with flat bright colors, thick black outlines, fine print dots and black halftone dots in the shadows.

Usage: python3 comic-book.py input.mp4 output.mp4
Needs ffmpeg, OpenCV and numpy (pip install opencv-python-headless numpy).
"""
import json
import subprocess
import sys

import cv2
import numpy as np

START = 1.0              # second the footage turns into a comic panel
COLORS = 6               # flat fill colors, picked once from the first comic frame so they don't flicker
VIVID = 3.0              # how much more saturated the fill colors are than the footage; 1 keeps them as they are
LIFT = 1.1               # how much brighter the fill colors are
CELL = 8                 # pixels between halftone dot centers
ANGLE = 45               # angle of the dot rows in degrees
DOTS_FROM = 0.5          # darkness (0 = brightest part of the frame, 1 = darkest) where black dots start
PRINT_DOT = 0.3          # radius, in cells, of the small dots of a deeper shade printed over every fill
PRINT_SHADE = 0.75       # how dark those print dots are next to the fill (1 = invisible)
LINES = (20, 70)         # Canny edge thresholds; lower draws more outlines
LINE = 4                 # outline thickness in pixels
INK = (15, 10, 20)       # outline and dot color, near black

def smoothed(img):
    """Iron out texture and grain but keep hard edges, so colors and outlines come out clean."""
    for _ in range(3):
        img = cv2.bilateralFilter(img, 9, 50, 7)
    return img

def comic(img):
    """One comic frame (h, w, 3 uint8): flat ink fills with fine print dots, black halftone shading, black outlines."""
    smooth = smoothed(img)
    pixels = smooth.reshape(-1, 3).astype(np.float32)
    nearest = ((pixels[:, None] - palette[None]) ** 2).sum(axis=2).argmin(axis=1)
    flat = cv2.medianBlur(vivid[nearest].astype(np.uint8).reshape(img.shape), 5).astype(np.float32)

    luma = cv2.GaussianBlur(cv2.cvtColor(smooth, cv2.COLOR_RGB2GRAY), (0, 0), CELL / 2).astype(np.float32)
    lo, hi = np.percentile(luma, (2, 98))
    dark = 1 - np.clip((luma - lo) / max(hi - lo, 1), 0, 1)
    shade = np.clip((dark - DOTS_FROM) / (1 - DOTS_FROM), 0, 1)
    cx, cy, dist = dot_grid
    radius = CELL * 0.72 * np.sqrt(shade[cy, cx])                 # 0.72 cells lets the darkest dots merge
    dots = np.clip(radius - dist + 0.5, 0, 1)                     # 1px soft edge
    printed = np.clip(CELL * PRINT_DOT - dist + 0.5, 0, 1)[..., None]
    flat *= 1 - printed * (1 - PRINT_SHADE)

    edges = cv2.Canny(cv2.cvtColor(smooth, cv2.COLOR_RGB2GRAY), *LINES)
    lines = cv2.GaussianBlur(cv2.dilate(edges, np.ones((LINE, LINE), np.uint8)), (0, 0), 0.8) / 255.0
    ink = np.maximum(dots, lines)[..., None]
    return flat * (1 - ink) + np.array(INK, np.float32) * ink

def pick_palette(img):
    """The frame's main colors, and the same colors pushed to bright comic-book inks."""
    pixels = smoothed(img).reshape(-1, 3).astype(np.float32)
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)
    cv2.setRNGSeed(1)
    centers = cv2.kmeans(pixels, COLORS, None, criteria, 3, cv2.KMEANS_PP_CENTERS)[2]
    hsv = cv2.cvtColor(centers.reshape(1, -1, 3).astype(np.uint8), cv2.COLOR_RGB2HSV).astype(np.float32)
    hsv[..., 1] = np.minimum(hsv[..., 1] * VIVID, 255)                  # richer color
    hsv[..., 2] = np.minimum(hsv[..., 2] * LIFT, 255)                   # brighter ink
    return centers, cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2RGB).reshape(-1, 3).astype(np.float32)

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height,r_frame_rate", "-of", "json", path]))
    s = info["streams"][0]
    num, den = s["r_frame_rate"].split("/")
    return s["width"], s["height"], s["r_frame_rate"], float(num) / float(den)

src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
y, x = np.mgrid[0:h, 0:w].astype(np.float32)
cos, sin = np.cos(np.radians(ANGLE)), np.sin(np.radians(ANGLE))
u, v = x * cos - y * sin, x * sin + y * cos                         # pixel positions in the turned dot grid
cu, cv = (np.floor(u / CELL) + 0.5) * CELL, (np.floor(v / CELL) + 0.5) * CELL
dot_grid = (np.clip(np.round(cu * cos + cv * sin), 0, w - 1).astype(int),   # each pixel's dot center, x
            np.clip(np.round(-cu * sin + cv * cos), 0, h - 1).astype(int),  # and y
            np.hypot(u - cu, v - cv))                                      # distance from it
palette = vivid = None

reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                          stdout=subprocess.PIPE)
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
                           "-r", rate, "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?",
                           "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                          stdin=subprocess.PIPE)
i = 0
while raw := reader.stdout.read(w * h * 3):
    if i / fps >= START:
        img = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
        if palette is None:
            palette, vivid = pick_palette(img)
        raw = np.clip(comic(img), 0, 255).astype(np.uint8).tobytes()
    writer.stdin.write(raw)
    i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
    sys.exit("ffmpeg failed")
```

## More stylize and art

- [Anime style **Anime style**](https://www.recharm.com/effects/anime-style)
- [ASCII art **ASCII art**](https://www.recharm.com/effects/ascii-art)
- [Blueprint **Blueprint**](https://www.recharm.com/effects/blueprint)
- [Charcoal drawing **Charcoal drawing**](https://www.recharm.com/effects/charcoal-drawing)
- [Claymation style **Claymation style**](https://www.recharm.com/effects/claymation-style)

## Find 3x _winning ads_

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

[Start 14-day free trial](https://www.recharm.com/get-started)
