Automate Sprite Slicing and Packing in GIMP Python

This article demonstrates how to automate the slicing and packing of 2D character animation frames using GIMP's built-in Python-Fu environment. By scripting this workflow, you can programmatically divide animation sheets into uniform cells, trim excess transparent pixels, and repack the frames into optimized texture layouts. This pipeline eliminates tedious manual alignment and ensures consistent frame dimensions for game development.

Understanding GIMP Python-Fu

GIMP provides a built-in Python 2.7 scripting interface known as Python-Fu, which interacts directly with the Procedural Database (PDB). This allows scripts to execute almost any native action available in the UI, such as selecting regions, cropping, copying layers, and transforming canvases.

Scripts must be placed inside your local GIMP plug-ins directory:

  • Windows: C:\Users\<Username>\AppData\Roaming\GIMP\2.10\plug-ins
  • Linux: ~/.config/GIMP/2.10/plug-ins
  • macOS: ~/Library/Application Support/GIMP/2.10/plug-ins

Ensure script files have executable permissions (chmod +x script_name.py on Linux and macOS).

The Slicing and Packing Logic

Automating the process requires three steps:

  1. Slicing: Iterating through a grid-based source image using known frame widths and heights, then extracting each cell into its own layer.
  2. Trimming (Optional): Removing transparent margins around the character while storing offsets to keep registration intact.
  3. Packing: Calculating the dimensions required for a new destination canvas (such as a horizontal strip or a square power-of-two texture) and rendering each frame onto it sequentially.

The Automation Script

The following script defines a Python plugin that slices a character sheet based on specified rows and columns, then repacks all extracted frames horizontally into a new sprite sheet.

#!/usr/bin/env python
from gimpfu import *

def slice_and_pack_frames(image, drawable, frame_width, frame_height, cols, rows):
    gimp.context_push()
    image.undo_group_start()

    total_frames = cols * rows
    frames = []

    # 1. Slice frames from the source image
    for r in range(rows):
        for c in range(cols):
            x = c * frame_width
            y = r * frame_height

            # Select frame region
            pdb.gimp_image_select_rectangle(image, CHANNEL_OP_REPLACE, x, y, frame_width, frame_height)
            
            # Copy the selection
            if pdb.gimp_edit_copy(drawable):
                # Paste into a temporary floating layer
                floating_sel = pdb.gimp_edit_paste(drawable, True)
                # Create a new layer from selection
                new_layer = pdb.gimp_layer_new_from_visible(image, image, "Frame_{}_{}".format(r, c))
                image.add_layer(new_layer, 0)
                pdb.gimp_floating_sel_remove(floating_sel)
                frames.append(new_layer)

    pdb.gimp_selection_none(image)

    # 2. Create the target canvas for repacking (horizontal strip)
    packed_width = frame_width * total_frames
    packed_height = frame_height
    packed_image = pdb.gimp_image_new(packed_width, packed_height, RGB)
    
    # 3. Pack frames into the new image
    for index, frame in enumerate(frames):
        # Copy layer to packed image
        packed_layer = pdb.gimp_layer_new(packed_image, frame_width, frame_height, 
                                            RGBA_IMAGE, "Frame_{}".format(index), 100, NORMAL_MODE)
        pdb.gimp_image_insert_layer(packed_image, packed_layer, None, 0)
        
        # Position layer
        offset_x = index * frame_width
        pdb.gimp_layer_set_offsets(packed_layer, offset_x, 0)

        # Copy pixel data from original frame
        pdb.gimp_edit_copy(frame)
        floating = pdb.gimp_edit_paste(packed_layer, True)
        pdb.gimp_floating_sel_anchor(floating)

        # Remove temporary layer from original image
        image.remove_layer(frame)

    # Display the packed sprite sheet
    pdb.gimp_display_new(packed_image)

    image.undo_group_end()
    gimp.context_pop()

register(
    "python_fu_slice_and_pack",
    "Slice and Pack Character Animation Frames",
    "Splits a grid-based sheet into frames and repacks them into a horizontal atlas.",
    "Author",
    "Open Source",
    "2024",
    "<Image>/Filters/Animation/Slice and Pack...",
    "RGB*, GRAY*",
    [
        (PF_INT, "frame_width", "Frame Width (px)", 64),
        (PF_INT, "frame_height", "Frame Height (px)", 64),
        (PF_INT, "cols", "Columns", 4),
        (PF_INT, "rows", "Rows", 4),
    ],
    [],
    slice_and_pack_frames
)

main()

Running the Script

  1. Save the code as slice_and_pack.py and place it in the GIMP plug-ins folder.
  2. Restart GIMP so the new plugin is indexed.
  3. Open your character animation image.
  4. Navigate to Filters > Animation > Slice and Pack... from the top menu.
  5. Enter the target frame width, height, and grid dimensions, then click OK.

A new window will open containing the repacked animation sequence aligned as a continuous horizontal strip, ready to be exported to PNG or loaded directly into an animation engine.