$npx -y skills add krzysztofsurdy/code-virtuoso --skill image-annotateMark up an existing image with shapes, arrows, text, numbered steps, highlights, blur, or solid redaction. Use this skill whenever the user wants to annotate a screenshot, add arrows or boxes to a picture, write labels on an image, draw numbered callouts for a tutorial, highlight
| 1 | # Image Annotate |
| 2 | |
| 3 | Mark up an existing image with shapes, text, numbered steps, blur, or solid-color redaction. Single CLI, one-off flags for simple marks, JSON spec file for compositions. Output is a flat raster image with every mark burned in - the original is never modified in place. |
| 4 | |
| 5 | ## Core Principles |
| 6 | |
| 7 | | Principle | Meaning | |
| 8 | |---|---| |
| 9 | | **Never modify in place** | Always write to a new output file. Annotation is destructive - the user must keep the original to redo or refine later. | |
| 10 | | **Blur is not redaction** | Pixelation and blur can be reversed for text content. For passwords, API keys, SSNs, payment numbers, and other security-critical strings, use solid-color redaction. Blur is acceptable only for casual privacy (faces in marketing shots, peripheral background detail). See `references/redaction-safety.md`. | |
| 11 | | **Marks should read on any background** | Use stroke + fill colors that contrast with the surrounding pixels, or add a contrasting outline to text. Yellow on a yellow page is invisible. The default style stacks a thick coloured stroke over the underlying pixels, never relies on transparency alone. | |
| 12 | | **Coordinate origin is top-left** | All `(x, y)` coordinates start from the top-left corner of the image. Y increases downward. Match the convention to whatever the source tool reports - browser DevTools and most screenshot tools agree on this. | |
| 13 | | **One operation, one purpose** | A single annotation does one thing. To layer marks, repeat flags or use a JSON spec - do not try to overload one operation. | |
| 14 | | **Burn into a flat image** | The output is a single-layer PNG or JPEG. No editable layers, no SVG re-edit path. If the user needs to iterate, they re-run the script with a new spec. | |
| 15 | |
| 16 | --- |
| 17 | |
| 18 | ## Quick Start |
| 19 | |
| 20 | ### Install (one-time, ask user first) |
| 21 | |
| 22 | ```bash |
| 23 | pip install --user Pillow |
| 24 | ``` |
| 25 | |
| 26 | Pillow is the imaging library. Cross-platform, pure-Python install. Check first: |
| 27 | |
| 28 | ```bash |
| 29 | python -c "import PIL; print(PIL.__version__)" |
| 30 | ``` |
| 31 | |
| 32 | ### Simple one-off: rectangle + arrow + label |
| 33 | |
| 34 | ```bash |
| 35 | python scripts/annotate.py input.png output.png \ |
| 36 | --rect 100,100,500,300 --rect-color red --rect-width 5 \ |
| 37 | --arrow 600,250,520,200 --arrow-color yellow \ |
| 38 | --text 600,260 "Click this button" --text-color white --text-bg black |
| 39 | ``` |
| 40 | |
| 41 | That produces `output.png` with a red box around the region, a yellow arrow pointing into it, and a labelled callout next to the arrow. |
| 42 | |
| 43 | ### Composition via JSON spec |
| 44 | |
| 45 | For three or more marks, prefer a spec file: |
| 46 | |
| 47 | ```json |
| 48 | [ |
| 49 | {"op": "rect", "xy": [100, 100, 500, 300], "color": "red", "width": 5}, |
| 50 | {"op": "arrow", "from": [600, 250], "to": [520, 200], "color": "yellow"}, |
| 51 | {"op": "text", "xy": [600, 260], "value": "Click this button", "color": "white", "bg": "black"}, |
| 52 | {"op": "step", "n": 1, "xy": [150, 150]}, |
| 53 | {"op": "step", "n": 2, "xy": [350, 250]}, |
| 54 | {"op": "redact", "xy": [50, 600, 400, 640]} |
| 55 | ] |
| 56 | ``` |
| 57 | |
| 58 | ```bash |
| 59 | python scripts/annotate.py input.png output.png --spec marks.json |
| 60 | ``` |
| 61 | |
| 62 | Operations apply in order - later marks overlay earlier ones. |
| 63 | |
| 64 | --- |
| 65 | |
| 66 | ## Operations Reference |
| 67 | |
| 68 | ### Rectangle - `rect` |
| 69 | |
| 70 | Bounding box around an area. Outline only by default (no fill). |
| 71 | |
| 72 | | CLI flag | JSON key | Default | |
| 73 | |---|---|---| |
| 74 | | `--rect x1,y1,x2,y2` | `{"op": "rect", "xy": [...]}` | required | |
| 75 | | `--rect-color NAME` | `"color": "red"` | red | |
| 76 | | `--rect-width N` | `"width": 5` | 4 | |
| 77 | | `--rect-fill NAME` | `"fill": null` | none (outline only) | |
| 78 | |
| 79 | ### Arrow - `arrow` |
| 80 | |
| 81 | Line with a triangular arrowhead at the end point. Points from `(x1,y1)` to `(x2,y2)` - the head is at `(x2,y2)`. |
| 82 | |
| 83 | | CLI flag | JSON key | Default | |
| 84 | |---|---|---| |
| 85 | | `--arrow x1,y1,x2,y2` | `{"op": "arrow", "from": [...], "to": [...]}` | required | |
| 86 | | `--arrow-color NAME` | `"color": "yellow"` | yellow | |
| 87 | | `--arrow-width N` | `"width": 6` | 6 | |
| 88 | | `--arrow-head N` | `"head": 20` | 20 (pixels, half-width of the head triangle) | |
| 89 | |
| 90 | ### Text label - `text` |
| 91 | |
| 92 | Text with an optional filled background box for legibility on any underlying image. The position is the top-left of the text. |
| 93 | |
| 94 | | CLI flag | JSON key | Default | |
| 95 | |---|---|---| |
| 96 | | `--text x,y "STRING"` | `{"op": "text", "xy": [...], "value": "..."}` | required | |
| 97 | | `--text-color NAME` | `"color": "white"` | white | |
| 98 | | `--text-bg NAME` | `"bg": "black"` | black (set |