screenshot-markup
Draw on a screenshot — circles, rectangles, arrows, captions, spotlights, blur, cursors, zoomed insets — to mark up exactly what the user asked about. Most commonly used right after a Playwright MCP screenshot, but works on any local image.
This skill is deterministic rendering only. The intelligence — interpreting the user's prompt, locating the focus region in the screenshot, and choosing the right style — lives in you, the calling model. This script just turns your decisions into pixels.
When to use this skill
Trigger when the user wants visual emphasis on part of a screenshot. Examples:
- "Highlight the Subscribe button in this screenshot."
- "Make the pricing table stand out, blur the rest."
- "Point out the error banner with an arrow."
- "Circle the timezone dropdown — it's tiny, also zoom in on it."
- "Make a tutorial-style image showing where to click next."
- After Playwright MCP returns a screenshot, when the user references a specific UI element they want to draw attention to.
Do NOT use this skill for: cropping, OCR, generic image editing, or composing multiple screenshots together.
The four styles
Pick the style that best fits the user's intent. If they didn't specify, infer from these heuristics:
| Style | Best for | Heuristic |
|---|---|---|
spotlight |
Drawing strong focus on one area, tutorial feel | User says "focus on", "make X stand out", or wants a clean teaching image |
blur |
Softer focus, preserves layout context | User says "blur the rest" or wants the surrounding UI still legible-ish |
shape |
Pointing at a specific element with explanation | User wants a caption, arrow, or Skitch-style call-out |
cursor-zoom |
Tiny UI (checkbox, icon) or "click here" instruction | Target is small, or user says "click", "tap", "show where to press" |
You can also chain styles by running the script multiple times (e.g., spotlight then add a caption with shape).
Locating the focus region
You have two ways to tell the script where to focus:
- Area —
--region "x,y,w,h"(pixel coordinates, top-left origin). Best for buttons, tables, panels, banners. - Point —
--point "x,y"plus optional--radius N. Best for "click here" or pinpointing an icon.
How to obtain coordinates:
- From Playwright MCP: if the user identified the element by selector, ask Playwright to return the element's bounding box (
element.boundingBox()), then pass it as--region. This is the most reliable path — prefer it. - By visual reasoning: if no bbox is available, open the screenshot, identify the element visually, and estimate its bounding box. Coordinates are in image pixels (not CSS pixels — they match the rendered PNG).
- Ask the user if you're unsure and the screenshot is dense.
CLI reference
Script path: scripts/annotate.py (run with python3). Always pass absolute paths for --input and --output.
python3 scripts/annotate.py \
--input /abs/path/to/shot.png \
--output /abs/path/to/out.png \
--style {spotlight|blur|shape|cursor-zoom} \
[--region "x,y,w,h" | --point "x,y" [--radius 80]] \
[--caption "Text label"] \
[--color "#E53935"]
Style-specific flags:
spotlight:--dim-opacity 0.65— how dark the outside gets (0.0–1.0). Default 0.65.
blur:--blur-radius 8— Gaussian radius. 6–12 is typical.
shape:--shape {rect|circle}— defaultrectfor areas; circle is auto-used for points.--stroke 4— outline width in pixels.--arrow-from {tl|tr|bl|br}— optional arrow pointing AT the shape from the given direction. The arrow originates ~140px from the shape edge in that direction (NOT from the page corner) and terminates at the shape edge with the arrowhead just outside the box.--arrow-length 140— distance in pixels from the shape edge to the arrow tail.
cursor-zoom:--zoom-factor 2.0— magnification of the inset.--zoom-corner {tl|tr|bl|br}— optional override. By default the inset is placed adjacent to the target on the side with the most free space, which keeps the visual connection tight. Pass--zoom-corneronly when the surrounding UI is crowded and pinning to a page corner reads cleaner.--no-cursor— skip the macOS cursor overlay (zoom only).--no-zoom— skip the inset (cursor only).--cursor-color {auto|dark|light}— cursor color. Defaultautosamples the background around the target and picks the variant with better contrast (dark cursor on light UIs, light cursor on dark UIs). Override withdark(also acceptsblack) orlight(also acceptswhite).--cursor-size 56— cursor size in pixels (square). Default 56. The script auto-shrinks when the target is smaller than the cursor so the cursor doesn't swallow the UI element it's pointing at.
⚠️ --caption is opt-in
--caption writes a text label onto the image itself. Only pass it when the user explicitly asks for a text label — phrases like "label it X", "add the text Y", "write 'Z' on it", or "make a tutorial card with a caption".
For pure pointing requests like "highlight the Send button", "circle the headline", "point at the dropdown", or "draw on the toggle" — omit --caption entirely. The annotated image will be shown to the user with a description underneath it (in chat, in a viewer, in a doc); duplicating that as text on the image is visual noise.
Worked examples
Area highlight with spotlight + caption:
python3 scripts/annotate.py \
--input /tmp/shot.png --output /tmp/out.png \
--style spotlight \
--region "120,300,420,180" \
--caption "Pricing tiers"
Click target with cursor + zoomed inset (auto cursor color):
python3 scripts/annotate.py \
--input /tmp/shot.png --output /tmp/out.png \
--style cursor-zoom \
--point "640,512" --radius 30 \
--zoom-corner br --zoom-factor 2.5 \
--caption "Click 'Subscribe'"
Forcing white cursor on a dark UI screenshot:
python3 scripts/annotate.py \
--input /tmp/dark-shot.png --output /tmp/out.png \
--style cursor-zoom \
--region "1015,715,90,45" \
--cursor-color white
Zoom inset only (no cursor) — magnify a tiny element:
python3 scripts/annotate.py \
--input /tmp/shot.png --output /tmp/out.png \
--style cursor-zoom \
--region "1180,40,32,32" --zoom-factor 3.5 \
--no-cursor
Cursor only (no inset) — "click here" pointer without magnification:
python3 scripts/annotate.py \
--input /tmp/shot.png --output /tmp/out.png \
--style cursor-zoom \
--region "1015,715,90,45" \
--no-zoom
Tutorial-style red rectangle with arrow and caption:
python3 scripts/annotate.py \
--input /tmp/shot.png --output /tmp/out.png \
--style shape \
--region "200,200,300,80" \
--shape rect --color "#E53935" \
--arrow-from tr \
--caption "Enter your email here"
Soft-focus blur on a panel:
python3 scripts/annotate.py \
--input /tmp/shot.png --output /tmp/out.png \
--style blur \
--region "60,80,520,300" \
--blur-radius 10
Decision flow you should follow
- Confirm the user has a screenshot and a focus target (ask if either is unclear).
- Get the region or point coordinates — prefer Playwright
boundingBox()over visual estimation. - Choose a style using the heuristic table above, or honor the user's explicit choice.
- Run the script. Show the output path to the user.
- If the user wants a different style or a tweak (e.g., different color, more dim), re-run — it's cheap and deterministic.
Notes
- The script writes PNG and accepts most common input formats (PNG/JPEG/WebP).
- All coordinates are clamped to the image; out-of-bounds regions won't crash.
- Cursor assets live in
assets/. SVG files are the canonical source of truth (versioned for the repo); PNG files are pre-rendered at 4× supersampling and are what the script actually loads at runtime. Two variants are bundled:macos-cursor.png(dark cursor, used on light UIs) andmacos-cursor-white.png(light cursor, used on dark UIs). The variant is auto-selected based on background brightness; if neither asset is present, the script falls back to a generated arrow cursor in the chosen color. - Pillow ≥ 9 is required (uses
rounded_rectangle,textbbox).