Python-Fu Scripting in GIMP - Automate with Python

Write Python scripts to batch-process hundreds of images, build custom GIMP workflows, and register your own menu commands - No Scheme knowledge required.

Intermediate–Advanced ~50 min Updated May 2026

Python-Fu vs Script-Fu - When to Choose Python

GIMP supports two scripting languages: Script-Fu (a dialect of Scheme/Lisp) and Python-Fu (Python 3.x). Both expose the same PDB (Procedure Database) of GIMP operations, but Python offers significant advantages for anything beyond simple single-image macros.

Choose Script-Fu when:

  • Writing a quick one-off macro
  • Following an existing Script-Fu tutorial
  • Working on a system without Python installed
  • The task is 5–20 lines of simple operations

Choose Python-Fu when:

  • Batch processing files or folders
  • Reading/writing CSV, JSON, or databases
  • Using standard library modules (os, pathlib, re)
  • Building complex, maintainable scripts
  • Integrating with other Python tools
GIMP 3.x Note: GIMP 3.x ships with Python 3 support via the python-fu plug-in. On Windows, GIMP 3.x bundles its own Python interpreter. On macOS and Linux, GIMP uses the system Python 3. Verify Python-Fu is available: if "Python-Fu" appears under Filters → Script-Fu (or as a separate Filters → Python-Fu menu), it is installed and working.

Opening the Python-Fu Console

The Python-Fu Console is an interactive Python REPL (Read-Eval-Print Loop) with the GIMP module pre-imported. It is the fastest way to test Python-Fu commands before writing a full script file.

  1. 1
    Open via menu: Filters → Script-Fu → Python-Fu Console. (In some GIMP builds it appears as Filters → Python-Fu → Console.) A window opens with an input field at the bottom and output area above.
  2. 2
    Test a basic command: Type the following in the input field and press Run:
image = gimp.image_list()[0]
print("Image width:", image.width, "Height:", image.height)

The output area should print the dimensions of the currently open image. If you see an error, ensure an image is open in GIMP first.

Python Basics for GIMP

Python-Fu exposes GIMP functionality through two main interfaces: the gimp module (object-oriented, Pythonic) and the pdb object (direct access to GIMP's PDB procedures, same as Script-Fu). You can manipulate layers, apply color adjustments, and export files - All from Python.

Core objects and their properties

# Access image list
images = gimp.image_list()
image = images[0]          # first open image

# Image properties
print(image.width)         # canvas width in pixels
print(image.height)        # canvas height in pixels
print(image.filename)      # full file path
print(image.colormap)      # color mode info

# Get layers
layers = image.layers      # list of all layers
active = image.active_layer  # currently selected layer

# Drawable (layer or channel) operations
drawable = image.active_drawable
print(drawable.name)       # layer name
print(drawable.opacity)    # 0.0 to 100.0

# PDB access (equivalent to Script-Fu pdb calls)
pdb.gimp_image_scale_full(image, 800, 600, INTERPOLATION_LINEAR)
pdb.gimp_displays_flush()

Useful built-in constants

# Interpolation types
INTERPOLATION_NONE     # no interpolation (pixelated)
INTERPOLATION_LINEAR   # bilinear - Good for photos
INTERPOLATION_CUBIC    # bicubic - Best quality for scaling
INTERPOLATION_NOHALO   # sharp resize, less halo

# Layer blend modes (common)
LAYER_MODE_NORMAL
LAYER_MODE_MULTIPLY
LAYER_MODE_SCREEN
LAYER_MODE_OVERLAY
LAYER_MODE_GRAIN_MERGE

# Export file types (for pdb.file_png_save etc.)
# Use pdb.file_jpeg_save or pdb.file_png_save

Your First Python Script - Open, Resize, Export

This script opens a single image, scales it to a maximum width of 1200 px while preserving aspect ratio, and exports it as a JPEG. It can be run from the Python-Fu Console or saved as a .py file.

# Single image: open, resize to max 1200px wide, export as JPEG
# Run in Filters -> Script-Fu -> Python-Fu Console
# Or save as a .py file and register as a menu item

import os

def resize_and_export(input_path, output_path, max_width=1200, quality=88):
    """
    Open an image, scale to max_width (preserving aspect ratio), export JPEG.
    """
    # Load the image
    image = pdb.gimp_file_load(RUN_NONINTERACTIVE, input_path, os.path.basename(input_path))
    drawable = pdb.gimp_image_get_active_drawable(image)[0]

    # Get current dimensions
    orig_width = image.width
    orig_height = image.height

    # Calculate new dimensions (only scale down, never up)
    if orig_width > max_width:
        scale_factor = max_width / orig_width
        new_width = max_width
        new_height = int(orig_height * scale_factor)
        pdb.gimp_image_scale_full(image, new_width, new_height, INTERPOLATION_CUBIC)

    # Flatten the image (required for JPEG export)
    flat_layer = pdb.gimp_image_flatten(image)[0]

    # Export as JPEG
    pdb.file_jpeg_save(
        RUN_NONINTERACTIVE,
        image,
        flat_layer,
        output_path,
        os.path.basename(output_path),
        quality / 100.0,  # GIMP uses 0.0-1.0 for JPEG quality
        0,     # smoothing
        1,     # optimize
        1,     # progressive
        '',    # comment
        0,     # subsmp (0=4:2:0, 1=4:2:2, 2=4:4:4)
        1,     # baseline-jpeg
        0,     # restart-markers
        0      # dct (0=int, 1=fixed, 2=float)
    )

    # Delete image from GIMP memory
    pdb.gimp_image_delete(image)
    return output_path

# Example usage from the console:
# resize_and_export("/Users/me/photos/input.jpg", "/Users/me/output/resized.jpg")

Batch Processing with Python (os.walk + GIMP Loop)

The following script walks an entire folder tree, finds all JPEG and PNG files, and applies the resize-and-export function to each. This is the core pattern for all batch GIMP automation. For a more detailed look at blend modes you can apply programmatically, see that dedicated guide.

# Batch resize all JPG/PNG files in a folder
# Paste into Python-Fu Console or save as a plug-in .py file

import os

INPUT_DIR  = "/Users/me/photos/originals"   # Change to your input folder
OUTPUT_DIR = "/Users/me/photos/resized"     # Change to your output folder
MAX_WIDTH  = 1200
QUALITY    = 88

# Supported extensions
EXTENSIONS = ('.jpg', '.jpeg', '.png', '.tif', '.tiff')

def batch_resize(input_dir, output_dir, max_width, quality):
    """
    Recursively process all images in input_dir, preserving subfolder structure.
    """
    os.makedirs(output_dir, exist_ok=True)
    processed = 0
    errors = 0

    for root, dirs, files in os.walk(input_dir):
        # Recreate subfolder structure in output dir
        rel_path = os.path.relpath(root, input_dir)
        out_root = os.path.join(output_dir, rel_path)
        os.makedirs(out_root, exist_ok=True)

        for filename in files:
            if not filename.lower().endswith(EXTENSIONS):
                continue

            in_path  = os.path.join(root, filename)
            # Output always as JPEG
            out_name = os.path.splitext(filename)[0] + '.jpg'
            out_path = os.path.join(out_root, out_name)

            try:
                image = pdb.gimp_file_load(
                    RUN_NONINTERACTIVE, in_path, filename)
                orig_width = image.width

                if orig_width > max_width:
                    scale = max_width / orig_width
                    pdb.gimp_image_scale_full(
                        image,
                        max_width,
                        int(image.height * scale),
                        INTERPOLATION_CUBIC)

                flat = pdb.gimp_image_flatten(image)[0]
                pdb.file_jpeg_save(
                    RUN_NONINTERACTIVE, image, flat,
                    out_path, out_name,
                    quality / 100.0, 0, 1, 1, '', 0, 1, 0, 0)
                pdb.gimp_image_delete(image)
                processed += 1
                print("OK:", out_path)

            except Exception as e:
                errors += 1
                print("ERROR:", in_path, str(e))

    print(f"\nDone. Processed: {processed}, Errors: {errors}")

# Run the batch
batch_resize(INPUT_DIR, OUTPUT_DIR, MAX_WIDTH, QUALITY)

Class-Based Script Template

For complex workflows, a class structure keeps configuration and logic together and makes scripts easier to maintain and extend.

# Class-based GIMP Python-Fu template
# Save as my_gimp_tool.py in GIMP plug-ins folder

import os
import gimp
from gimpfu import *

class GimpBatchTool:
    """Reusable base class for GIMP batch operations."""

    def __init__(self, input_dir, output_dir):
        self.input_dir  = input_dir
        self.output_dir = output_dir
        self.processed  = 0
        self.errors     = 0

    def process_image(self, image, drawable):
        """Override this method with your per-image operations."""
        raise NotImplementedError("Subclass must implement process_image()")

    def run(self):
        os.makedirs(self.output_dir, exist_ok=True)
        for filename in os.listdir(self.input_dir):
            if not filename.lower().endswith(('.jpg', '.jpeg', '.png')):
                continue
            in_path  = os.path.join(self.input_dir, filename)
            out_path = os.path.join(self.output_dir, filename)
            try:
                image    = pdb.gimp_file_load(RUN_NONINTERACTIVE, in_path, filename)
                drawable = pdb.gimp_image_get_active_drawable(image)[0]
                self.process_image(image, drawable)
                flat = pdb.gimp_image_flatten(image)[0]
                pdb.file_jpeg_save(RUN_NONINTERACTIVE, image, flat,
                    out_path, filename, 0.88, 0, 1, 1, '', 0, 1, 0, 0)
                pdb.gimp_image_delete(image)
                self.processed += 1
            except Exception as e:
                self.errors += 1
                gimp.message("Error: " + in_path + " - " + str(e))

        gimp.message(f"Batch complete. OK: {self.processed}, Errors: {self.errors}")


class WatermarkTool(GimpBatchTool):
    """Add a text watermark to every image."""

    def __init__(self, input_dir, output_dir, watermark_text="(c) My Name"):
        super().__init__(input_dir, output_dir)
        self.watermark_text = watermark_text

    def process_image(self, image, drawable):
        # Create text layer
        text_layer = pdb.gimp_text_fontname(
            image, None,
            image.width - 260, image.height - 40,
            self.watermark_text, 0, TRUE, 18, UNIT_PIXEL,
            "Sans Bold")
        # Set opacity and blend mode
        pdb.gimp_layer_set_opacity(text_layer, 60)
        pdb.gimp_layer_set_mode(text_layer, LAYER_MODE_NORMAL)

Key PDB Procedures for Python-Fu

All GIMP procedures are accessible via pdb.procedure-name() in Python-Fu, with hyphens replaced by underscores. The following are the most commonly used in batch scripts.

PDB Procedure Python-Fu Call Description
gimp-file-load pdb.gimp_file_load(run_mode, filename, raw_filename) Load any supported image file; returns image object
gimp-image-scale-full pdb.gimp_image_scale_full(image, w, h, interpolation) Scale image to exact pixel dimensions
gimp-image-flatten pdb.gimp_image_flatten(image) Merge all layers; required before JPEG export
file-png-save pdb.file_png_save(run_mode, image, drawable, filename, ...) Export as PNG with compression control
file-jpeg-save pdb.file_jpeg_save(run_mode, image, drawable, filename, ...) Export as JPEG; quality 0.0-1.0
gimp-image-delete pdb.gimp_image_delete(image) Free image from memory after processing
gimp-curves-spline pdb.gimp_curves_spline(drawable, channel, num_controlpoints, control_pts) Apply Curves adjustment programmatically
gimp-brightness-contrast pdb.gimp_brightness_contrast(drawable, brightness, contrast) Adjust brightness (-127 to 127) and contrast
plug-in-unsharp-mask pdb.plug_in_unsharp_mask(run_mode, image, drawable, radius, amount, threshold) Sharpening via unsharp mask filter
gimp-text-fontname pdb.gimp_text_fontname(image, drawable, x, y, text, border, antialias, size, size_type, fontname) Add text to an image programmatically

Lines of Code: Script-Fu vs Python-Fu for the Same Task

Debugging Python-Fu

Python-Fu errors appear in the Python-Fu Console output area or in GIMP's Error Console (Windows → Dockable Dialogs → Error Console).

# Enable verbose error output in Python-Fu Console
import traceback

try:
    image = pdb.gimp_file_load(RUN_NONINTERACTIVE, "/path/to/file.jpg", "file.jpg")
    # ... your operations ...
except Exception as e:
    gimp.message("Full traceback:\n" + traceback.format_exc())

# Use gimp.message() for status output visible in the GIMP GUI
gimp.message("Processing: " + filename)

# Use print() for output that appears in the Python-Fu Console output area
print("Image dimensions:", image.width, "x", image.height)

# Inspect PDB procedure signatures in the console:
# pdb.gimp_file_load.__doc__   (shows parameter list)
print(pdb.gimp_image_scale_full.__doc__)
  • ProcedureError: Usually means wrong number of arguments or wrong type. Print the procedure's .__doc__ to see the expected signature.
  • RuntimeError from GIMP: Often means the image or drawable no longer exists (deleted, or you called a procedure in the wrong order). Check that the image was loaded successfully with if image is not None.
  • Script does not appear in Filters menu: The register() function call must be correct and the file must be in the GIMP plug-ins folder. Check for Python syntax errors by running the script in a standalone Python interpreter first (outside GIMP).

Registering Scripts as Menu Items

Python-Fu scripts saved as .py files in the GIMP plug-ins folder with a register() call and main() call at the bottom appear as menu items in GIMP.

#!/usr/bin/env python3
# Save as: ~/.config/GIMP/3.0/plug-ins/my_watermark/my_watermark.py
# Make executable: chmod +x my_watermark.py (macOS/Linux)

from gimpfu import *

def python_watermark(image, drawable, text, opacity):
    """Add a text watermark to the active image."""
    pdb.gimp_image_undo_group_start(image)

    text_layer = pdb.gimp_text_fontname(
        image, None,
        image.width - 280, image.height - 50,
        text, 0, TRUE, 24, UNIT_PIXEL, "Sans Bold Italic")

    pdb.gimp_layer_set_opacity(text_layer, opacity)
    pdb.gimp_image_flatten(image)
    pdb.gimp_image_undo_group_end(image)
    pdb.gimp_displays_flush()


register(
    "python-fu-watermark",           # unique procedure name
    "Add text watermark",             # blurb (short description)
    "Adds a semi-transparent text watermark to the bottom-right corner.",
    "Your Name",                      # author
    "Your Name",                      # copyright
    "2026",                           # year
    "Add Watermark...",               # menu label
    "RGB*, GRAY*",                    # image type (RGB, RGBA, greyscale)
    [                                  # input parameters
        (PF_IMAGE,   "image",    "Input image",   None),
        (PF_DRAWABLE,"drawable", "Input drawable",None),
        (PF_STRING,  "text",     "Watermark text","(c) 2026"),
        (PF_SLIDER,  "opacity",  "Opacity",       50, (0, 100, 1)),
    ],
    [],                                # output parameters (none)
    python_watermark,                  # function to call
    menu="/Filters/My Scripts"  # menu location
)

main()

After saving the file and restarting GIMP (or refreshing plug-ins via Filters → Script-Fu → Refresh Scripts), the watermark script appears under Filters → My Scripts → Add Watermark.

Python-Fu vs CLI (gimp --batch-interpreter)

For fully automated, headless batch processing (on servers, in CI/CD pipelines, or overnight jobs), GIMP can be run from the command line without any GUI. Use the --batch-interpreter flag with Python-Fu.

# Run GIMP headless with Python-Fu batch command
# (macOS/Linux terminal - Adjust GIMP path as needed)

gimp-3.0 \
  --no-interface \
  --batch-interpreter python-fu-eval \
  --batch "
import os, sys
sys.path.insert(0, '/path/to/your/scripts')

def process(in_path, out_path):
    image = pdb.gimp_file_load(RUN_NONINTERACTIVE, in_path, os.path.basename(in_path))
    pdb.gimp_image_scale_full(image, 800, 600, INTERPOLATION_CUBIC)
    flat = pdb.gimp_image_flatten(image)[0]
    pdb.file_jpeg_save(RUN_NONINTERACTIVE, image, flat, out_path, os.path.basename(out_path), 0.88, 0, 1, 1, '', 0, 1, 0, 0)
    pdb.gimp_image_delete(image)

for f in os.listdir('/input/folder'):
    if f.endswith('.jpg'):
        process('/input/folder/' + f, '/output/folder/' + f)

pdb.gimp_quit(0)
"

# Windows equivalent (adjust path):
# "C:\Program Files\GIMP 3\bin\gimp-3.0.exe" --no-interface ...

The --no-interface flag suppresses the GUI completely. pdb.gimp_quit(0) at the end ensures GIMP exits cleanly after the batch completes.