gimp
GIMP Image Automation
Automate GIMP (GNU Image Manipulation Program) workflows using Python-Fu scripting, Script-Fu (Scheme), CLI batch mode, and the libreearth/gimp-mcp server for programmatic image editing.
Direct Control (CLI / API / Scripting)
CLI Batch Mode
# Basic batch processing
gimp -i -b '(batch-command)' -b '(gimp-quit 0)'
# Resize image
gimp -i -b '(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE "input.jpg" "input.jpg")))
(drawable (car (gimp-image-get-active-layer image))))
(gimp-image-scale image 800 600)
(gimp-file-save RUN-NONINTERACTIVE image drawable "output.jpg" "output.jpg")
(gimp-image-delete image))' -b '(gimp-quit 0)'
# Convert to grayscale
gimp -i -b '(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE "input.png" "input.png")))
(drawable (car (gimp-image-get-active-layer image))))
(gimp-image-convert-grayscale image)
(gimp-file-save RUN-NONINTERACTIVE image drawable "output.png" "output.png")
(gimp-image-delete image))' -b '(gimp-quit 0)'
# Batch process entire directory
for file in images/*.jpg; do
gimp -i -b "(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE \"$file\" \"$file\")))
(drawable (car (gimp-image-get-active-layer image))))
(gimp-image-scale image 1200 800)
(gimp-file-save RUN-NONINTERACTIVE image drawable \"processed/$file\" \"processed/$file\")
(gimp-image-delete image))" -b '(gimp-quit 0)'
done
Python-Fu Scripts
Save to ~/.config/GIMP/2.10/plug-ins/ or /usr/share/gimp/2.0/scripts/
#!/usr/bin/env python
# batch-watermark.py - Add watermark to images
from gimpfu import *
def add_watermark(image, drawable, watermark_text, position):
"""Add text watermark to image"""
# Get image dimensions
width = image.width
height = image.height
# Create text layer
text_layer = pdb.gimp_text_fontname(
image, None,
0, 0, # x, y coordinates
watermark_text,
10, # border
True, # antialias
72, # font size
PIXELS,
"Sans Bold"
)
# Position watermark
if position == "bottom-right":
x = width - text_layer.width - 20
y = height - text_layer.height - 20
elif position == "bottom-left":
x = 20
y = height - text_layer.height - 20
elif position == "center":
x = (width - text_layer.width) / 2
y = (height - text_layer.height) / 2
else: # top-right
x = width - text_layer.width - 20
y = 20
pdb.gimp_layer_set_offsets(text_layer, x, y)
# Set opacity
pdb.gimp_layer_set_opacity(text_layer, 70)
# Flatten image
merged = pdb.gimp_image_flatten(image)
return
register(
"python-fu-batch-watermark",
"Add watermark to image",
"Adds text watermark to image at specified position",
"Code Buddy",
"Code Buddy",
"2024",
"<Image>/Filters/Watermark/Add Text Watermark",
"*",
[
(PF_STRING, "watermark_text", "Watermark Text", "© 2024"),
(PF_OPTION, "position", "Position", 0, ["top-right", "bottom-right", "bottom-left", "center"])
],
[],
add_watermark
)
main()
Advanced Python-Fu: Batch Photo Enhancement
#!/usr/bin/env python
# auto-enhance.py - Automatic photo enhancement
from gimpfu import *
import os
def auto_enhance_photo(input_path, output_path):
"""Enhance photo: adjust levels, sharpen, reduce noise"""
# Load image
image = pdb.gimp_file_load(input_path, input_path)
drawable = pdb.gimp_image_get_active_layer(image)
# Auto levels
pdb.gimp_levels_stretch(drawable)
# Auto contrast
pdb.plug_in_c_astretch(image, drawable)
# Sharpen (unsharp mask)
pdb.plug_in_unsharp_mask(
image, drawable,
5.0, # radius
0.5, # amount
0 # threshold
)
# Reduce noise
pdb.plug_in_despeckle(
image, drawable,
3, # radius
1, # type (median)
-1, # black level
255 # white level
)
# Increase saturation slightly
pdb.gimp_hue_saturation(
drawable,
0, # hue-saturation mode (all)
0, # hue offset
0, # lightness
15 # saturation boost
)
# Save
pdb.gimp_file_save(image, drawable, output_path, output_path)
pdb.gimp_image_delete(image)
# Batch process directory
def batch_enhance(source_dir, dest_dir):
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
for filename in os.listdir(source_dir):
if filename.lower().endswith(('.jpg', '.jpeg', '.png')):
input_path = os.path.join(source_dir, filename)
output_path = os.path.join(dest_dir, filename)
print(f"Processing {filename}...")
auto_enhance_photo(input_path, output_path)
register(
"python-fu-auto-enhance",
"Auto enhance photos",
"Automatically enhance photos with levels, sharpening, and noise reduction",
"Code Buddy",
"Code Buddy",
"2024",
"<Image>/Filters/Enhance/Auto Enhance Photo",
"*",
[],
[],
lambda img, drw: auto_enhance_photo(img.filename, img.filename)
)
main()
Script-Fu Examples
; resize-and-save.scm
(define (batch-resize-image filename new-width new-height)
(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
(drawable (car (gimp-image-get-active-layer image))))
(gimp-image-scale image new-width new-height)
(gimp-file-save RUN-NONINTERACTIVE image drawable filename filename)
(gimp-image-delete image)))
; create-thumbnail.scm
(define (create-thumbnail input-file output-file thumb-size)
(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE input-file input-file)))
(drawable (car (gimp-image-get-active-layer image)))
(width (car (gimp-image-width image)))
(height (car (gimp-image-height image)))
(new-width thumb-size)
(new-height (* height (/ thumb-size width))))
(gimp-image-scale image new-width new-height)
(gimp-file-save RUN-NONINTERACTIVE image drawable output-file output-file)
(gimp-image-delete image)))
Python Direct API (via gimp-python)
# advanced-editing.py
import sys
sys.path.append('/usr/lib/gimp/2.0/python')
from gimpfu import *
def create_social_media_template(width, height, bg_color, text):
"""Create a social media post template"""
# Create new image
image = pdb.gimp_image_new(width, height, RGB)
# Create background layer
bg_layer = pdb.gimp_layer_new(
image, width, height,
RGB_IMAGE, "Background",
100, NORMAL_MODE
)
pdb.gimp_image_insert_layer(image, bg_layer, None, 0)
# Fill with color
pdb.gimp_context_set_foreground(bg_color)
pdb.gimp_drawable_fill(bg_layer, FOREGROUND_FILL)
# Add text
text_layer = pdb.gimp_text_fontname(
image, None,
width * 0.1, height * 0.4,
text, 10, True,
int(width * 0.08), PIXELS,
"Impact"
)
# Center text
pdb.gimp_layer_set_offsets(
text_layer,
(width - text_layer.width) / 2,
(height - text_layer.height) / 2
)
# Create display
display = pdb.gimp_display_new(image)
return image
# Run from CLI
if __name__ == "__main__":
gimp.main(None, None, create_social_media_template, (1080, 1080, (41, 128, 185), "Hello World"))
MCP Server Integration
The libreearth/gimp-mcp server provides tool-based access to GIMP through an MCP interface, enabling programmatic image manipulation.
Configuration (.codebuddy/mcp.json)
{
"mcpServers": {
"gimp": {
"command": "npx",
"args": ["-y", "@libreearth/gimp-mcp"],
"env": {
"GIMP_EXECUTABLE": "/usr/bin/gimp",
"GIMP_SCRIPTS_PATH": "${HOME}/.config/GIMP/2.10/plug-ins"
}
}
}
}
Available MCP Tools
-
open_image - Load an image file
- Input:
file_path(string) - Returns: Image ID and metadata (dimensions, color mode)
- Input:
-
save_image - Save image to file
- Input:
image_id(number),file_path(string),format(jpg/png/gif/xcf) - Returns: Success status
- Input:
-
resize_image - Scale image to new dimensions
- Input:
image_id(number),width(number),height(number),interpolation(none/linear/cubic) - Returns: Updated image metadata
- Input:
-
crop_image - Crop to specified rectangle
- Input:
image_id(number),x(number),y(number),width(number),height(number) - Returns: Cropped image metadata
- Input:
-
rotate_image - Rotate by degrees
- Input:
image_id(number),degrees(90/180/270 or any float),auto_crop(boolean) - Returns: Rotated image metadata
- Input:
-
adjust_brightness_contrast - Adjust image tone
- Input:
image_id(number),brightness(-100 to 100),contrast(-100 to 100) - Returns: Success status
- Input:
-
convert_grayscale - Convert to grayscale
- Input:
image_id(number) - Returns: Success status
- Input:
-
apply_blur - Apply Gaussian blur
- Input:
image_id(number),radius(number) - Returns: Success status
- Input:
-
add_text_layer - Add text to image
- Input:
image_id(number),text(string),x(number),y(number),font(string),size(number),color(hex) - Returns: Layer ID
- Input:
-
flatten_image - Merge all layers
- Input:
image_id(number) - Returns: Success status
- Input:
-
batch_process - Process multiple images with same operations
- Input:
input_dir(string),output_dir(string),operations(array) - Returns: Processed file count
- Input:
Common Workflows
1. Batch Resize Product Photos
#!/bin/bash
# resize-products.sh
INPUT_DIR="./product-photos"
OUTPUT_DIR="./resized"
TARGET_WIDTH=1200
mkdir -p "$OUTPUT_DIR"
for img in "$INPUT_DIR"/*.jpg; do
filename=$(basename "$img")
echo "Resizing $filename..."
gimp -i -d -f -b "
(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE \"$img\" \"$img\")))
(drawable (car (gimp-image-get-active-layer image)))
(width (car (gimp-image-width image)))
(height (car (gimp-image-height image)))
(new-height (* height (/ $TARGET_WIDTH width))))
(gimp-image-scale image $TARGET_WIDTH new-height)
(file-jpeg-save RUN-NONINTERACTIVE
image drawable
\"$OUTPUT_DIR/$filename\" \"$OUTPUT_DIR/$filename\"
0.95 0 1 0 \"\" 2 1 0 0)
(gimp-image-delete image))
" -b '(gimp-quit 0)'
done
echo "Done! Resized $(ls $OUTPUT_DIR | wc -l) images."
2. Watermark Batch Processing with Python-Fu
#!/usr/bin/env python
# batch-watermark-runner.py
import os
import subprocess
def apply_watermark_batch(input_dir, output_dir, watermark_text):
"""Apply watermark to all images in directory"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
for filename in os.listdir(input_dir):
if not filename.lower().endswith(('.jpg', '.png', '.jpeg')):
continue
input_path = os.path.join(input_dir, filename)
output_path = os.path.join(output_dir, filename)
# Run GIMP with Python-Fu script
cmd = [
'gimp', '-i', '-d', '-f',
'-b', f'''
(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE "{input_path}" "{input_path}")))
(drawable (car (gimp-image-get-active-layer image)))
(text-layer (car (gimp-text-fontname image drawable
10 10 "{watermark_text}" 10 TRUE 48 PIXELS "Sans Bold"))))
(gimp-layer-set-opacity text-layer 60)
(gimp-layer-set-offsets text-layer
(- (car (gimp-image-width image)) (car (gimp-drawable-width text-layer)) 20)
(- (car (gimp-image-height image)) (car (gimp-drawable-height text-layer)) 20))
(set! drawable (car (gimp-image-flatten image)))
(gimp-file-save RUN-NONINTERACTIVE image drawable "{output_path}" "{output_path}")
(gimp-image-delete image))
''',
'-b', '(gimp-quit 0)'
]
print(f"Processing {filename}...")
subprocess.run(cmd, check=True)
if __name__ == "__main__":
apply_watermark_batch("./originals", "./watermarked", "© 2024 MyCompany")
3. Create Image Variants (Thumbnail, Medium, Large)
#!/bin/bash
# create-variants.sh
INPUT_IMAGE=$1
BASENAME=$(basename "$INPUT_IMAGE" | sed 's/\.[^.]*$//')
# Thumbnail (300x300)
gimp -i -d -f -b "(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE \"$INPUT_IMAGE\" \"$INPUT_IMAGE\")))
(drawable (car (gimp-image-get-active-layer image))))
(gimp-image-scale-full image 300 300 INTERPOLATION-CUBIC)
(gimp-file-save RUN-NONINTERACTIVE image drawable \"${BASENAME}_thumb.jpg\" \"${BASENAME}_thumb.jpg\")
(gimp-image-delete image))" -b '(gimp-quit 0)'
# Medium (800x600)
gimp -i -d -f -b "(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE \"$INPUT_IMAGE\" \"$INPUT_IMAGE\")))
(drawable (car (gimp-image-get-active-layer image))))
(gimp-image-scale-full image 800 600 INTERPOLATION-CUBIC)
(gimp-file-save RUN-NONINTERACTIVE image drawable \"${BASENAME}_medium.jpg\" \"${BASENAME}_medium.jpg\")
(gimp-image-delete image))" -b '(gimp-quit 0)'
# Large (1920x1080)
gimp -i -d -f -b "(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE \"$INPUT_IMAGE\" \"$INPUT_IMAGE\")))
(drawable (car (gimp-image-get-active-layer image))))
(gimp-image-scale-full image 1920 1080 INTERPOLATION-CUBIC)
(gimp-file-save RUN-NONINTERACTIVE image drawable \"${BASENAME}_large.jpg\" \"${BASENAME}_large.jpg\")
(gimp-image-delete image))" -b '(gimp-quit 0)'
echo "Created 3 variants: ${BASENAME}_thumb.jpg, ${BASENAME}_medium.jpg, ${BASENAME}_large.jpg"
4. Photo Enhancement Pipeline via MCP
// Using Code Buddy with GIMP MCP
// Ask: "Enhance the photo at ./portrait.jpg - adjust brightness, reduce noise, and sharpen"
// Code Buddy will orchestrate:
async function enhancePortrait() {
// 1. Open image
const { image_id } = await mcp.call('gimp', 'open_image', {
file_path: './portrait.jpg'
});
// 2. Auto levels
await mcp.call('gimp', 'adjust_brightness_contrast', {
image_id,
brightness: 10,
contrast: 15
});
// 3. Reduce noise (via blur then sharpen)
await mcp.call('gimp', 'apply_blur', {
image_id,
radius: 1.5
});
// 4. Sharpen
await mcp.call('gimp', 'apply_unsharp_mask', {
image_id,
radius: 3,
amount: 0.7
});
// 5. Save
await mcp.call('gimp', 'save_image', {
image_id,
file_path: './portrait_enhanced.jpg',
format: 'jpg'
});
}
5. Automated Social Media Graphics Generation
#!/usr/bin/env python
# generate-social-posts.py
from gimpfu import *
import json
def create_social_post(config_file):
"""Generate social media graphics from JSON config"""
with open(config_file) as f:
config = json.load(f)
for post in config['posts']:
# Create image
image = pdb.gimp_image_new(post['width'], post['height'], RGB)
# Background layer
bg = pdb.gimp_layer_new(image, post['width'], post['height'],
RGB_IMAGE, "Background", 100, NORMAL_MODE)
pdb.gimp_image_insert_layer(image, bg, None, 0)
# Fill background
bg_color = tuple(post['background_color'])
pdb.gimp_context_set_foreground(bg_color)
pdb.gimp_drawable_fill(bg, FOREGROUND_FILL)
# Add logo if specified
if 'logo' in post:
logo = pdb.gimp_file_load_layer(image, post['logo'])
pdb.gimp_image_insert_layer(image, logo, None, 0)
pdb.gimp_layer_scale(logo, post['logo_width'], post['logo_height'], False)
pdb.gimp_layer_set_offsets(logo, post['logo_x'], post['logo_y'])
# Add headline text
headline = pdb.gimp_text_fontname(
image, None,
50, post['height'] * 0.4,
post['headline'],
10, True, post['font_size'],
PIXELS, post['font']
)
pdb.gimp_text_layer_set_color(headline, tuple(post['text_color']))
# Center headline
pdb.gimp_layer_set_offsets(
headline,
(post['width'] - headline.width) / 2,
post['headline_y']
)
# Flatten and save
merged = pdb.gimp_image_flatten(image)
pdb.gimp_file_save(image, merged, post['output'], post['output'])
pdb.gimp_image_delete(image)
print(f"Generated {post['output']}")
# Example config.json:
# {
# "posts": [
# {
# "width": 1200, "height": 630,
# "background_color": [41, 128, 185],
# "headline": "New Product Launch!",
# "headline_y": 250,
# "text_color": [255, 255, 255],
# "font": "Impact", "font_size": 72,
# "logo": "./logo.png",
# "logo_width": 200, "logo_height": 200,
# "logo_x": 50, "logo_y": 50,
# "output": "./social-post-1.jpg"
# }
# ]
# }
if __name__ == "__main__":
create_social_post("config.json")
More from phuetz/code-buddy
blender
Blender 3D modeling, animation, and rendering automation via Python bpy scripting and CLI
19figma
Automate Figma design workflows via REST API, Plugin API, and MCP integration
3github
Interact with GitHub using the gh CLI for issues, PRs, CI runs, releases, and API queries
3ableton-live
Ableton Live music production automation via OSC protocol, MIDI, and Max for Live
3gitlab
GitLab DevOps platform with CI/CD pipelines, API automation, and glab CLI control
3blog-watcher
Monitor blogs, RSS/Atom feeds, and websites for updates
3