gimp/agent-harness/GIMP.md
GIMP (GNU Image Manipulation Program) is a GTK-based raster image editor built on the GEGL (Generic Graphics Library) processing engine and Babl color management.
┌──────────────────────────────────────────────┐
│ GIMP GUI │
│ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ Canvas │ │ Layers │ │ Filters │ │
│ │ (GTK) │ │ (GTK) │ │ (GTK) │ │
│ └────┬──────┘ └────┬─────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌────┴─────────────┴──────────────┴───────┐ │
│ │ PDB (Procedure Database) │ │
│ │ 500+ registered procedures for all │ │
│ │ image operations, filters, I/O │ │
│ └─────────────────┬───────────────────────┘ │
│ │ │
│ ┌─────────────────┴───────────────────────┐ │
│ │ GEGL Processing Engine │ │
│ │ DAG-based image processing pipeline │ │
│ │ 70+ built-in operations │ │
│ └─────────────────┬───────────────────────┘ │
└────────────────────┼─────────────────────────┘
│
┌───────────┴──────────┐
│ Babl (color mgmt) │
│ + GEGL operations │
│ + File format I/O │
└──────────────────────┘
Unlike Shotcut (which manipulates XML project files), GIMP's native .xcf format is a complex binary format. Our strategy:
gegl command for advanced operations.gimp is installed, use gimp -i -b for XCF
operations and advanced filters via Script-Fu/Python-Fu.XCF is a tile-based binary format with compression, layers, channels, paths, and GEGL filter graphs. Parsing it from scratch is extremely complex (5000+ lines of C in GIMP's xcf-load.c). Instead:
Since we can't easily manipulate XCF directly, we use a JSON project format:
{
"version": "1.0",
"name": "my_project",
"canvas": {
"width": 1920,
"height": 1080,
"color_mode": "RGB",
"background": "#ffffff",
"dpi": 300
},
"layers": [
{
"id": 0,
"name": "Background",
"type": "image",
"source": "/path/to/image.png",
"visible": true,
"opacity": 1.0,
"blend_mode": "normal",
"offset_x": 0,
"offset_y": 0,
"filters": [
{"name": "brightness", "params": {"factor": 1.2}},
{"name": "gaussian_blur", "params": {"radius": 3}}
]
},
{
"id": 1,
"name": "Text Layer",
"type": "text",
"text": "Hello World",
"font": "Arial",
"font_size": 48,
"color": "#000000",
"visible": true,
"opacity": 0.8,
"blend_mode": "normal",
"offset_x": 100,
"offset_y": 50,
"filters": []
}
],
"selection": null,
"guides": [],
"metadata": {}
}
| Operation | Pillow API |
|---|---|
| Open image | Image.open(path) |
| Save image | image.save(path, format) |
| Create blank | Image.new(mode, (w,h), color) |
| Convert mode | image.convert("RGB"/"L"/"RGBA") |
| Resize | image.resize((w,h), resample) |
| Crop | image.crop((l, t, r, b)) |
| Rotate | image.rotate(angle, expand=True) |
| Flip | image.transpose(Image.FLIP_LEFT_RIGHT) |
| Operation | Pillow API |
|---|---|
| Brightness | ImageEnhance.Brightness(img).enhance(factor) |
| Contrast | ImageEnhance.Contrast(img).enhance(factor) |
| Saturation | ImageEnhance.Color(img).enhance(factor) |
| Sharpness | ImageEnhance.Sharpness(img).enhance(factor) |
| Gaussian blur | image.filter(ImageFilter.GaussianBlur(radius)) |
| Box blur | image.filter(ImageFilter.BoxBlur(radius)) |
| Unsharp mask | image.filter(ImageFilter.UnsharpMask(radius, percent, threshold)) |
| Find edges | image.filter(ImageFilter.FIND_EDGES) |
| Emboss | image.filter(ImageFilter.EMBOSS) |
| Contour | image.filter(ImageFilter.CONTOUR) |
| Detail | image.filter(ImageFilter.DETAIL) |
| Smooth | image.filter(ImageFilter.SMOOTH_MORE) |
| Grayscale | ImageOps.grayscale(image) |
| Invert | ImageOps.invert(image) |
| Posterize | ImageOps.posterize(image, bits) |
| Solarize | ImageOps.solarize(image, threshold) |
| Autocontrast | ImageOps.autocontrast(image) |
| Equalize | ImageOps.equalize(image) |
| Sepia | Custom kernel via ImageOps.colorize() |
| Operation | Pillow API |
|---|---|
| Paste layer | Image.alpha_composite(base, overlay) |
| Blend modes | Custom implementations (multiply, screen, overlay, etc.) |
| Draw rectangle | ImageDraw.rectangle(xy, fill, outline) |
| Draw ellipse | ImageDraw.ellipse(xy, fill, outline) |
| Draw text | ImageDraw.text(xy, text, font, fill) |
| Draw line | ImageDraw.line(xy, fill, width) |
Pillow doesn't natively support Photoshop/GIMP blend modes. We implement the most common ones using NumPy-style pixel math:
| Mode | Formula |
|---|---|
| Normal | top (with alpha compositing) |
| Multiply | base * top / 255 |
| Screen | 255 - (255-base)*(255-top)/255 |
| Overlay | if base < 128: 2*base*top/255 else: 255 - 2*(255-base)*(255-top)/255 |
| Soft Light | Photoshop-style formula |
| Hard Light | Overlay with base/top swapped |
| Difference | abs(base - top) |
| Darken | min(base, top) |
| Lighten | max(base, top) |
| Color Dodge | base / (255 - top) * 255 |
| Color Burn | 255 - (255-base) / top * 255 |
| GUI Action | CLI Command |
|---|---|
| File -> New | project new --width 1920 --height 1080 [--mode RGB] |
| File -> Open | project open <path> |
| File -> Save | project save [path] |
| File -> Export As | export render <output> [--format png] [--quality 95] |
| Image -> Canvas Size | canvas resize --width W --height H |
| Image -> Scale Image | canvas scale --width W --height H |
| Image -> Crop to Selection | canvas crop --left L --top T --right R --bottom B |
| Image -> Mode -> RGB | canvas mode RGB |
| Layer -> New Layer | layer new [--name "Layer"] [--width W] [--height H] |
| Layer -> Duplicate | layer duplicate <index> |
| Layer -> Delete | layer remove <index> |
| Layer -> Flatten Image | layer flatten |
| Layer -> Merge Down | layer merge-down <index> |
| Move layer | layer move <index> --to <position> |
| Set layer opacity | layer set <index> opacity <value> |
| Set blend mode | layer set <index> mode <mode> |
| Toggle visibility | layer set <index> visible <true/false> |
| Layer -> Add from File | layer add-from-file <path> [--name N] [--position P] |
| Filters -> Blur -> Gaussian | filter add gaussian_blur --layer L --param radius=5 |
| Colors -> Brightness-Contrast | filter add brightness --layer L --param factor=1.2 |
| Colors -> Hue-Saturation | filter add saturation --layer L --param factor=1.3 |
| Colors -> Invert | filter add invert --layer L |
| Draw text on layer | draw text --layer L --text "Hi" --x 10 --y 10 --font Arial --size 24 |
| Draw rectangle | draw rect --layer L --x1 0 --y1 0 --x2 100 --y2 100 --fill "#ff0000" |
| View layers | layer list |
| View project info | project info |
| Undo | session undo |
| Redo | session redo |
| CLI Name | Pillow Implementation | Key Parameters |
|---|---|---|
brightness | ImageEnhance.Brightness | factor (1.0 = neutral, >1 = brighter) |
contrast | ImageEnhance.Contrast | factor (1.0 = neutral) |
saturation | ImageEnhance.Color | factor (1.0 = neutral, 0 = grayscale) |
sharpness | ImageEnhance.Sharpness | factor (1.0 = neutral, >1 = sharper) |
autocontrast | ImageOps.autocontrast | cutoff (0-49, percent to clip) |
equalize | ImageOps.equalize | (no params) |
invert | ImageOps.invert | (no params) |
posterize | ImageOps.posterize | bits (1-8) |
solarize | ImageOps.solarize | threshold (0-255) |
grayscale | ImageOps.grayscale | (no params) |
sepia | Custom colorize | strength (0.0-1.0) |
| CLI Name | Pillow Implementation | Key Parameters |
|---|---|---|
gaussian_blur | ImageFilter.GaussianBlur | radius (pixels) |
box_blur | ImageFilter.BoxBlur | radius (pixels) |
unsharp_mask | ImageFilter.UnsharpMask | radius, percent, threshold |
smooth | ImageFilter.SMOOTH_MORE | (no params) |
| CLI Name | Pillow Implementation | Key Parameters |
|---|---|---|
find_edges | ImageFilter.FIND_EDGES | (no params) |
emboss | ImageFilter.EMBOSS | (no params) |
contour | ImageFilter.CONTOUR | (no params) |
detail | ImageFilter.DETAIL | (no params) |
| CLI Name | Pillow Implementation | Key Parameters |
|---|---|---|
rotate | Image.rotate | angle (degrees), expand (bool) |
flip_h | Image.transpose(FLIP_LEFT_RIGHT) | (no params) |
flip_v | Image.transpose(FLIP_TOP_BOTTOM) | (no params) |
resize | Image.resize | width, height, resample (nearest/bilinear/bicubic/lanczos) |
crop | Image.crop | left, top, right, bottom |
| Format | Extension | Quality Param | Notes |
|---|---|---|---|
| PNG | .png | compress_level (0-9) | Lossless, supports alpha |
| JPEG | .jpg/.jpeg | quality (1-95) | Lossy, no alpha |
| WebP | .webp | quality (1-100) | Both lossy/lossless |
| TIFF | .tiff | compression (none/lzw/jpeg) | Professional |
| BMP | .bmp | (none) | Uncompressed |
| GIF | .gif | (none) | 256 colors max |
| ICO | .ico | (none) | Icon format |
| (none) | Multi-page possible |
For GIMP CLI, "rendering" means flattening the layer stack with all filters applied and exporting to a target format.
Unit tests (test_core.py): Synthetic data, no real images needed
E2E tests (test_full_e2e.py): Real images