Writing a Batch Image Processor Plugin for GIMP in Python

Python is not my first choice, except when Scheme is the second choice, and also when there aren’t any other choices.

Follow the steps below to create a batch image multiprocessor for GIMP in Python. As written, the plugin will perform a crop operation and a rotate operation on the image files in a specified directory (and, if so desired, its subdirectories), prepend/append prefixes/suffixes to the file names, and output the results to a different directory. It’s mostly a proof of concept at this point.

1. If you have not done so already, download and install GIMP, the “GNU Image Manipulation Program”. As of this writing, installation information for GIMP version 2.8 is available at http://www.gimp.org/downloads/.

2. Locate the “plug-ins” directory for GIMP. For this example, on a system running Windows 7, the directory is located at “C:\Users\[username]\.gimp 2.8\plug-ins”.

3. In the “plug-ins” directory, create a new text file named “batch-multi-processor.py”, containing the following text.

from gimpfu import *
import os

class TransformCrop:
    def __init__(self, size, offset):
    def applyToImage(self, image):
        sizeToUse = (self.size[0], self.size[1]);        
        if (sizeToUse[0] == 0 and sizeToUse[1] == 0):
            sizeToUse = (image.width, image.height)
        if (sizeToUse[0] > image.width):
            sizeToUse[0] = image.width
        if (sizeToUse[1] > image.height):
            sizeToUse[1] = image.height                    
        pdb.gimp_image_crop(image, sizeToUse[0], sizeToUse[1], self.offset[0], self.offset[1])

class TransformRotate:
    def __init__(self, timesToRotate):
        self.timesToRotate = timesToRotate
    def applyToImage(self, image):
        if (self.timesToRotate > 0):
            pdb.gimp_drawable_transform_rotate_simple(image.layers[0], self.timesToRotate - 1, TRUE, 0, 0, TRUE)

class TransformOutput:
    def __init__(self, directoryInputRoot, directoryOutputRoot, filePrefix, fileSuffix, fileExtension):
        self.directoryInputRoot = directoryInputRoot
        self.directoryOutputRoot = directoryOutputRoot
        self.filePrefix = filePrefix
        self.fileSuffix = fileSuffix
        self.fileExtension = fileExtension
    def applyToImage(self, image):
        filePathStemAndExtension = os.path.splitext(image.filename)
        directoryAndStem = os.path.split(filePathStemAndExtension[0])
        directory = directoryAndStem[0];
        directoryInputRootLength = len(self.directoryInputRoot);
        directoryMinusRoot = directory[directoryInputRootLength:]
        directoryOutputFull = self.directoryOutputRoot + directoryMinusRoot
        if (os.path.exists(directoryOutputFull) == FALSE):
        pathToSaveTo = directoryOutputFull + "/" + self.filePrefix + directoryAndStem[1] + self.fileSuffix + self.fileExtension
        print "saving processed image to " + pathToSaveTo
        pdb.gimp_file_save(image, image.layers[0], pathToSaveTo, pathToSaveTo)        

class ImageProcessor:
    def __init__(self):
    def processImageFile(self, fileToProcess, fileExtensionsToProcess, transforms):        
        if (fileExtensionsToProcess.find(os.path.splitext(fileToProcess)[1]) >= 0):
            image = pdb.gimp_file_load(fileToProcess, fileToProcess)
            layer = image.layers[0]            
            for transform in transforms:            
                if (transform != None):
    def processImagesInDirectory(self, directoryToProcess, isDirectoryRecursive, fileExtensionsToProcess, transforms):
        if (directoryToProcess.endswith("/") == FALSE):
            directoryToProcess = directoryToProcess + "/";
        filesystemItems = os.listdir(directoryToProcess)
        for filesystemItem in filesystemItems:
            pathToProcess = directoryToProcess + filesystemItem            
            print pathToProcess
            if (os.path.isfile(pathToProcess) == TRUE):                
                self.processImageFile(pathToProcess, fileExtensionsToProcess, transforms)
            elif (isDirectoryRecursive == TRUE and os.path.isdir(pathToProcess) == TRUE):                
                self.processImagesInDirectory(pathToProcess + "/", isDirectoryRecursive, fileExtensionsToProcess, transforms)

def python_fu_batch_multi_processor(directoryInputRoot, isDirectoryInputRecursive, fileExtensionsToProcess, cropSizeX, cropSizeY, cropOffsetX, cropOffsetY, timesToRotate, filePrefixOutput, fileSuffixOutput, fileTypeOutput, directoryOutputRoot):
    crop = TransformCrop([cropSizeX, cropSizeY], [cropOffsetX, cropOffsetY])
    rotate = TransformRotate(timesToRotate % 4)
    output = TransformOutput(directoryInputRoot, directoryOutputRoot, filePrefixOutput, fileSuffixOutput, fileTypeOutput)
    imageProcessor = ImageProcessor()
    imageProcessor.processImagesInDirectory(directoryInputRoot, isDirectoryInputRecursive, fileExtensionsToProcess,[ crop, rotate, output ] )

# for testing in console...
# python_fu_batch_multi_processor("C:/Temp/ImagesIn/", TRUE, ".gif;.jpg;.png;.xcf", 0, 0, 0, 0, 1, "", "-Processed", ".png", "C:/Temp/ImagesOut/")

    "Performs various transforms to multiple files and saves the results.",
    "Performs various transforms to multiple files and saves the results.",
    "Nobody Nooneson [https://thiscouldbebetter.wordpress.com]",
    "Nobody Nooneson [https://thiscouldbebetter.wordpress.com]",
    "Batch Multiprocessor...",
        (PF_STRING, "directoryInputRoot", "Input Directory", "C:/Temp/ImagesIn/"),        
        (PF_BOOL, "isDirectoryInputRecursive", "Include Subdirectories?", 0),    
        (PF_STRING, "fileExtensionsToProcess", "FileExtensionsToProcess", ".gif;.jpg;.png;.xcf"),
        (PF_INT, "cropSizeX", "Crop Width (0 for no crop)", 0),
        (PF_INT, "cropSizeY", "Crop Height (0 for no crop)", 0),
        (PF_INT, "cropOffsetX", "Crop Offset X", 0),
        (PF_INT, "cropOffsetY", "Crop Offset Y", 0),        
        (PF_INT, "timesToRotate", "Times To Rotate Clockwise 90 Degrees", 1),
        (PF_STRING, "filePrefixOutput", "Output File Prefix", ""),
        (PF_STRING, "fileSuffixOutput", "Output File Suffix", "-Processed"),
        (PF_STRING, "fileExtensionOutput", "Output File Extension:", ".png"),
        (PF_STRING, "directoryOutputRoot", "Output Directory", "C:/Temp/ImagesOut/"),        


4. Start GIMP.

5. In GIMP, select the “File – Create – Batch Multiprocessor…” item from the main menu. A dialog box will appear, and parameters for the batch operation plugin can be specified.

6. On the parameters dialog, in the an Input Directory text box, enter the path of a directory containing some image files to process. Then, in the Output Directory text box, enter the path of the destination directory.

7. Click the OK button.

8. Navigate to the specified output directory and verify that the expected output image files have been generated. Open one of the output files and verify that it has been rotated and cropped properly.


  • Python has significant whitespace, so if you put a wrong tab someplace, or if you indent something wrong, or if you mix spaces and tabs, or exhale sharply near it, your whole program will crash for no reason apparent to the naked eye. You’ll notice some freaky long lines in the code listing above–this is because I am literally afraid to do any formatting in Python. It’s quite simply the incorrect way to run a programming language, but the only alternative in GIMP right now is Scheme, which is like LISP without the charisma.
  • LISP does not have charisma.
  • If you make changes to a plugin, they won’t always show up until you quit GIMP, move the plugin .py out of the “plug-ins” directory, start GIMP, quit GIMP again immediately, move the .py file back into the plugins directory, and start GIMP yet again. I’m hoping there’s an easier way that I just don’t know about.
  • The script can be tested in the GIMP’s Python-Fu console by uncommenting the line beneath the comment that says “# for testing in console…”, commenting out everything below that, and then pasting it into the Python-Fu console window.
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s