Image annotations
Add precise visual callouts to screenshots, diagrams, and demo frames by drawing rounded rectangles, leader lines, and short labels with consistent colors, padding, and typography.
When to invoke
- "Highlight this area in a screenshot for my PR."
- "Annotate the before and after images."
- "Add labels and arrows to this architecture diagram."
- "Create annotated frames for a GIF demo."
- "Mark what changed in this UI image."
Prerequisites and context
Install Pillow only if the project environment does not already provide it:
pip install Pillow -q
Check Image.open(path).size before placing annotations. HiDPI screenshots are larger than they appear; 150% display scaling means coordinates may be 1.5 times CSS pixel dimensions.
Annotation style rules
| Element |
Rule |
Concrete value |
| Bad or removed item |
Use red only for defects, removals, or negative examples. |
#E63946 |
| Neutral highlight |
Use yellowish-orange for "look here", additions, and feature callouts. |
#FF9F1C |
| Shape |
Prefer rounded rectangles over circles or ellipses. |
draw.rounded_rectangle(..., radius=14, outline=color, width=5) |
| Padding |
Pad the target content so the outline does not touch it. |
18px |
| Leader line |
Keep close to the target; same thickness as the rectangle. |
25-35px preferred, width=5 |
| Label length |
Use short labels. |
1-3 words when possible |
| Text stroke |
Use same-color stroke for body. |
stroke_width=1, stroke_fill=color |
| White stroke |
Do not use it. |
It creates a bad glow effect. |
Font and sizing
| Platform |
Font rule |
| Windows |
Prefer Ink Free at C:/Windows/Fonts/Inkfree.ttf for a handwritten look. |
| Linux/macOS |
Fall back to ImageFont.load_default() unless the project provides a better font. |
| Around 1400px-wide images |
Use size 36 for annotation labels. |
| Markdown display |
Take screenshots at native 1x and control display size with HTML such as <img width="300">. |
Never resize screenshots with PIL just to fit Markdown; resizing creates artifacts and makes annotation coordinates harder to verify.
Reference implementation
from PIL import Image, ImageDraw, ImageFont
font = ImageFont.truetype('C:/Windows/Fonts/Inkfree.ttf', 36) # or ImageFont.load_default()
color = '#FF9F1C'
stroke = 5
pad = 18
img = Image.open('screenshot.png')
draw = ImageDraw.Draw(img)
# Inspect before placing callouts: img.size
# Rounded rect with padding
draw.rounded_rectangle(
[x1 - pad, y1 - pad, x2 + pad, y2 + pad],
radius=14, outline=color, width=stroke
)
# Leader line, same thickness as rect
cy = (y1 + y2) // 2
draw.line([x2 + pad, cy, x2 + pad + 40, cy - 30], fill=color, width=stroke)
# Label: same-color stroke, no white glow
_draw_label_at = (x2 + pad + 45, cy - 60)
draw.text(
_draw_label_at, 'label text',
fill=color, font=font, stroke_width=1, stroke_fill=color
)
img.save('annotated.png')
Placement guidelines
- Use consistent thickness for rectangles, lines, and visual text weight;
5px is a good default.
- Place labels close to the target with short leader lines.
- Allow labels to overlap content only when the same-color stroke keeps them legible.
- Use
debug=True or an equivalent preview mode for the first annotation on a new image.
- Show the result locally before uploading to a PR or embedding in a document.
- Keep 2-6 annotations per image; more callouts usually need a numbered legend or multiple images.
Limits
- Ink Free is Windows-only; other platforms need
ImageFont.load_default() or a provided font.
- PIL text rendering is basic; it does not support rich text or Markdown.
- Animated GIF annotations require frame-by-frame processing and can be slow for long recordings.
- Algorithmic placement works best with 2-6 annotations; split crowded images instead of overloading one frame.
Progressive disclosure and bundled resources
references/annotation-script-and-media.md: read before implementing scripted annotations, image diffs, GIF overlays, or reusable media workflows.
Annotation terminology
This skill uses PIL/Pillow. workflows for color-coded callouts. Treat red as bad/removed, avoid circles/ellipses, avoid eye-catching red, keep stroke_fill=<same color as fill>, and preserve line width consistency. Read the code-heavy bundled reference for scripted media workflows.
Output template
## Image annotation result
**Status:** complete | blocked | failed
**Input:** `<path/to/source-image>`
**Output:** `<path/to/annotated-image>`
**Image size:** `<width>x<height>`
| Callout | Color | Coordinates | Label | Purpose |
| --- | --- | --- | --- | --- |
| 1 | `#FF9F1C` | `[x1, y1, x2, y2]` | `<label>` | <what reviewers should notice> |
### Validation
- Source image size checked: pass | fail
- Annotated output opened or previewed locally: pass | fail
- Markdown display sizing recommended: pass | fail
Quality gate
1---2name: image-annotations3description: Annotate screenshots, diagrams, and images with PIL/Pillow callout rectangles, arrows, labels, highlights, and GIF overlays. Use when the user needs to highlight a PR screenshot, mark a before/after change, call out a diagram region, or create annotated demo frames.4---56<!-- Generated from harness/github-copilot/skills/image-annotations/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->78# Image annotations910Add precise visual callouts to screenshots, diagrams, and demo frames by drawing rounded rectangles, leader lines, and short labels with consistent colors, padding, and typography.1112## When to invoke1314- "Highlight this area in a screenshot for my PR."15- "Annotate the before and after images."16- "Add labels and arrows to this architecture diagram."17- "Create annotated frames for a GIF demo."18- "Mark what changed in this UI image."1920## Prerequisites and context2122Install Pillow only if the project environment does not already provide it:2324```bash25pip install Pillow -q26```2728Check `Image.open(path).size` before placing annotations. HiDPI screenshots are larger than they appear; 150% display scaling means coordinates may be 1.5 times CSS pixel dimensions.2930## Annotation style rules3132| Element | Rule | Concrete value |33| --- | --- | --- |34| Bad or removed item | Use red only for defects, removals, or negative examples. | `#E63946` |35| Neutral highlight | Use yellowish-orange for "look here", additions, and feature callouts. | `#FF9F1C` |36| Shape | Prefer rounded rectangles over circles or ellipses. | `draw.rounded_rectangle(..., radius=14, outline=color, width=5)` |37| Padding | Pad the target content so the outline does not touch it. | `18px` |38| Leader line | Keep close to the target; same thickness as the rectangle. | `25-35px` preferred, `width=5` |39| Label length | Use short labels. | 1-3 words when possible |40| Text stroke | Use same-color stroke for body. | `stroke_width=1`, `stroke_fill=color` |41| White stroke | Do not use it. | It creates a bad glow effect. |4243## Font and sizing4445| Platform | Font rule |46| --- | --- |47| Windows | Prefer Ink Free at `C:/Windows/Fonts/Inkfree.ttf` for a handwritten look. |48| Linux/macOS | Fall back to `ImageFont.load_default()` unless the project provides a better font. |49| Around 1400px-wide images | Use size `36` for annotation labels. |50| Markdown display | Take screenshots at native 1x and control display size with HTML such as `<img width="300">`. |5152Never resize screenshots with PIL just to fit Markdown; resizing creates artifacts and makes annotation coordinates harder to verify.5354## Reference implementation5556```python57from PIL import Image, ImageDraw, ImageFont5859font = ImageFont.truetype('C:/Windows/Fonts/Inkfree.ttf', 36) # or ImageFont.load_default()60color = '#FF9F1C'61stroke = 562pad = 186364img = Image.open('screenshot.png')65draw = ImageDraw.Draw(img)6667# Inspect before placing callouts: img.size68# Rounded rect with padding69draw.rounded_rectangle(70 [x1 - pad, y1 - pad, x2 + pad, y2 + pad],71 radius=14, outline=color, width=stroke72)7374# Leader line, same thickness as rect75cy = (y1 + y2) // 276draw.line([x2 + pad, cy, x2 + pad + 40, cy - 30], fill=color, width=stroke)7778# Label: same-color stroke, no white glow79_draw_label_at = (x2 + pad + 45, cy - 60)80draw.text(81 _draw_label_at, 'label text',82 fill=color, font=font, stroke_width=1, stroke_fill=color83)8485img.save('annotated.png')86```8788## Placement guidelines89901. Use consistent thickness for rectangles, lines, and visual text weight; `5px` is a good default.912. Place labels close to the target with short leader lines.923. Allow labels to overlap content only when the same-color stroke keeps them legible.934. Use `debug=True` or an equivalent preview mode for the first annotation on a new image.945. Show the result locally before uploading to a PR or embedding in a document.956. Keep 2-6 annotations per image; more callouts usually need a numbered legend or multiple images.9697## Limits9899- Ink Free is Windows-only; other platforms need `ImageFont.load_default()` or a provided font.100- PIL text rendering is basic; it does not support rich text or Markdown.101- Animated GIF annotations require frame-by-frame processing and can be slow for long recordings.102- Algorithmic placement works best with 2-6 annotations; split crowded images instead of overloading one frame.103104## Progressive disclosure and bundled resources105106- `references/annotation-script-and-media.md`: read before implementing scripted annotations, image diffs, GIF overlays, or reusable media workflows.107108## Annotation terminology109110This skill uses `PIL/Pillow.` workflows for `color-coded` callouts. Treat red as `bad/removed`, avoid `circles/ellipses`, avoid `eye-catching` red, keep `stroke_fill=<same color as fill>`, and preserve line `width` consistency. Read the `code-heavy` bundled reference for scripted media workflows.111112## Output template113114```markdown115## Image annotation result116117**Status:** complete | blocked | failed118**Input:** `<path/to/source-image>`119**Output:** `<path/to/annotated-image>`120**Image size:** `<width>x<height>`121122| Callout | Color | Coordinates | Label | Purpose |123| --- | --- | --- | --- | --- |124| 1 | `#FF9F1C` | `[x1, y1, x2, y2]` | `<label>` | <what reviewers should notice> |125126### Validation127- Source image size checked: pass | fail128- Annotated output opened or previewed locally: pass | fail129- Markdown display sizing recommended: pass | fail130```131132## Quality gate133134- [ ] `Image.open(path).size` was checked before coordinate placement.135- [ ] Red `#E63946` is used only for bad or removed things.136- [ ] Neutral highlights use `#FF9F1C` or a justified project color.137- [ ] Rounded rectangles have `18px` padding and consistent `5px` visual weight unless image scale requires adjustment.138- [ ] Labels are short, close to the target, and do not use white stroke.139- [ ] The annotated image was previewed locally before delivery or upload.140- [ ] GIF work reads `references/annotation-script-and-media.md` before frame-by-frame implementation.