How to convert XCF to PNG using GIMP from the command-line?
Asked Answered
I

5

42

As part of my build process I need to convert a number of XCF (GIMP's native format) images into PNG format. I'm sure this should be possible using GIMP's batch mode, but I have forgotten all of the script-fu I used to know.

My input images have multiple layers, so I need the batch mode equivalent of "merge visible layers" followed by "save as PNG". Also note that I can't install anything in ~/.gimp*/scripts/ — I need a self-contained command-line, or a way to install scripts in my source tree.

Note that while this is similar to this question, I have the additional constraint that I need this to be done using GIMP. I tried the current version of ImageMagick and it mangles my test images.

Interrogative answered 26/4, 2011 at 18:16 Comment(2)
Why does it have to be with GIMP? Compatibility concerns? On the question you linked to, people say Xcftools is doing a good job.Frequent
@CiroSantilli thanks for the heads-up. I actually don't need to use GIMP. The question I linked to, Xcftools hadn't been mentioned at the time I posted this question. The only non-GIMP option I knew of at the time was ImageMagick, and its XCF support was somewhat broken. Now that I know about it I'll try switching my build over to Xcftools. That said, I think this question may still useful for anyone who must use GIMP for whatever reason.Interrogative
E
16

There are a few ways to go through this - my preferred method is always developing a GIMP-Python plug-in. Script-fu uses GIMP's built-in scheme implementation, which is extremely poor in handling I/O (such as listing files in a directory) - a task which is absolutely trivial in Python.

SO, since you said you don't want to save new scripts (you could do it because you can add new plug-in and scripts directories on edit->preferences->folder options so that you don't need to write on ~/.gimp*/plugins/)

Anyway, you can open the python console under filters, and paste this snippet in the interactive prompt.

import gimpfu
def convert(filename):
    img = pdb.gimp_file_load(filename, filename)
    new_name = filename.rsplit(".",1)[0] + ".png"
    layer = pdb.gimp_image_merge_visible_layers(img, gimpfu.CLIP_TO_IMAGE)

    pdb.gimp_file_save(img, layer, new_name, new_name)
    pdb.gimp_image_delete(img)

This small function will open any image, passed as a file path, flatten it, and save it as a png with the default settings, in the same directory.

Following the function, you can simply type:

from glob import glob
for filename in glob("*.xcf"):
    convert(filename)

On the interactive prompt to convert all .xcf files on the current directory to .png

(If you don't have gimp-python installed, it may be a separate package on your linux distribution, just install it using your favorite tool - for those under windows, and gimp-2.6, the instructions on this page have have to be followed - it should become easier on gimp 2.8)

Another way, altogether, applies if your images are sequentially numbered in their filenames, such as myimage_001.xcf, mymage_002.xcf and so on. If they are so arranged you could install GIMP-GAP (gimp animation package) which allows one to apply any filters or actions in such an image sequence.

Eburnation answered 26/4, 2011 at 23:37 Comment(7)
Interesting! I didn't know about Python-Fu. That's certainly a lot nicer than Script-Fu's lobotomized Scheme implementation (and very non-lispy API). I want this to be completely non-interactive, though, so what's the best way to invoke Python-Fu from the command-line? gimp-console --batch-interpreter python-fu-eval -b - <myscript seems to work -- is there a better/less-verbose way?Interrogative
I just realized that I meant merge visible layers, not flatten. (I want the alpha channel.) I updated the question and your answer to match.Interrogative
As for starting python commands from the command line, if you write a python script witch registers itself as a plug-in (check the examples tahtc ome along with GIMP), you can call it straight after the --batch-interpreter parameter, instead of going though the pyhon-fu-eval bitEburnation
I have german filenames with international characters like ä, ö and ü. Glob won't return the correct filename but an empty line if I 'print filename' in the loop. What can I do?Mincemeat
This code runs, but the files that get generated are very blurry. Is there a way to specify the compression level?Perspiratory
I found out how to fix the blurriness issue for avif and jxl images. There are different save methods that are called for each file type, and each of them potentially has different parameters. In the case of avif, you can use pdb.file_heif_av1_save(img, layer, new_path, new_path, 100, 1). 100 specifies maximum quality. 1 indicates that it should be lossless. In the case of jpegxl, you can use the equivalent pdb.file_jpegxl_save method.Perspiratory
What is the advantage of using pdb.gimp_image_merge_visible_layers(img, gimpfu.CLIP_TO_IMAGE) over pdb.gimp_image_active_drawable(img)? Are they both equivalent in this instance?Perspiratory
I
31

Before jsbueno posted his answer I had also tried asking on the #gimp IRC channel. I was directed to this thread on Gimptalk which contains the following code:

gimp -n -i -b - <<EOF
(let* ( (file's (cadr (file-glob "*.xcf" 1))) (filename "") (image 0) (layer 0) )
  (while (pair? file's) 
    (set! image (car (gimp-file-load RUN-NONINTERACTIVE (car file's) (car file's))))
    (set! layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))
    (set! filename (string-append (substring (car file's) 0 (- (string-length (car file's)) 4)) ".png"))
    (gimp-file-save RUN-NONINTERACTIVE image layer filename filename)
    (gimp-image-delete image)
    (set! file's (cdr file's))
    )
  (gimp-quit 0)
  )
EOF

This scriptfu globs for xcf files, and then for each file it loads the file, merges the visible layers, saves the result as a PNG, and "unloads" the image. Finally, it quits GIMP. The glob approach is used to avoid starting up GIMP for each image. It also side-steps the issue of getting parameters from the shell into gimp.

I'm posting this answer just in case someone needs a way to do this without the use of GIMP-Python (perhaps because it isn't installed).

Interrogative answered 1/5, 2011 at 5:45 Comment(2)
Thank you, this works great. Any idea how I might go about setting the jpeg quality with that script? I'd also like it to find files in subfolders if that's possible.Extremity
This appears to be one of the few options that works with GNU Image Manipulation Program version 2.10. Note that if you have large xcf images you may get the TinyScheme ts> prompt lingering until the conversion process is finished. Xcftools (xcf2png mentioned elsewhere) doesn't support XCF version 12, and ImageMagick (as of version 7.0.8.15) only supports XCF version 11Alic
E
16

There are a few ways to go through this - my preferred method is always developing a GIMP-Python plug-in. Script-fu uses GIMP's built-in scheme implementation, which is extremely poor in handling I/O (such as listing files in a directory) - a task which is absolutely trivial in Python.

SO, since you said you don't want to save new scripts (you could do it because you can add new plug-in and scripts directories on edit->preferences->folder options so that you don't need to write on ~/.gimp*/plugins/)

Anyway, you can open the python console under filters, and paste this snippet in the interactive prompt.

import gimpfu
def convert(filename):
    img = pdb.gimp_file_load(filename, filename)
    new_name = filename.rsplit(".",1)[0] + ".png"
    layer = pdb.gimp_image_merge_visible_layers(img, gimpfu.CLIP_TO_IMAGE)

    pdb.gimp_file_save(img, layer, new_name, new_name)
    pdb.gimp_image_delete(img)

This small function will open any image, passed as a file path, flatten it, and save it as a png with the default settings, in the same directory.

Following the function, you can simply type:

from glob import glob
for filename in glob("*.xcf"):
    convert(filename)

On the interactive prompt to convert all .xcf files on the current directory to .png

(If you don't have gimp-python installed, it may be a separate package on your linux distribution, just install it using your favorite tool - for those under windows, and gimp-2.6, the instructions on this page have have to be followed - it should become easier on gimp 2.8)

Another way, altogether, applies if your images are sequentially numbered in their filenames, such as myimage_001.xcf, mymage_002.xcf and so on. If they are so arranged you could install GIMP-GAP (gimp animation package) which allows one to apply any filters or actions in such an image sequence.

Eburnation answered 26/4, 2011 at 23:37 Comment(7)
Interesting! I didn't know about Python-Fu. That's certainly a lot nicer than Script-Fu's lobotomized Scheme implementation (and very non-lispy API). I want this to be completely non-interactive, though, so what's the best way to invoke Python-Fu from the command-line? gimp-console --batch-interpreter python-fu-eval -b - <myscript seems to work -- is there a better/less-verbose way?Interrogative
I just realized that I meant merge visible layers, not flatten. (I want the alpha channel.) I updated the question and your answer to match.Interrogative
As for starting python commands from the command line, if you write a python script witch registers itself as a plug-in (check the examples tahtc ome along with GIMP), you can call it straight after the --batch-interpreter parameter, instead of going though the pyhon-fu-eval bitEburnation
I have german filenames with international characters like ä, ö and ü. Glob won't return the correct filename but an empty line if I 'print filename' in the loop. What can I do?Mincemeat
This code runs, but the files that get generated are very blurry. Is there a way to specify the compression level?Perspiratory
I found out how to fix the blurriness issue for avif and jxl images. There are different save methods that are called for each file type, and each of them potentially has different parameters. In the case of avif, you can use pdb.file_heif_av1_save(img, layer, new_path, new_path, 100, 1). 100 specifies maximum quality. 1 indicates that it should be lossless. In the case of jpegxl, you can use the equivalent pdb.file_jpegxl_save method.Perspiratory
What is the advantage of using pdb.gimp_image_merge_visible_layers(img, gimpfu.CLIP_TO_IMAGE) over pdb.gimp_image_active_drawable(img)? Are they both equivalent in this instance?Perspiratory
C
12

Here is my solution utilizing GIMP, GIMP's python-fu and bash. Note that GIMP's python-fu can only be run under a gimp process, not under plain python.

#!/bin/bash
set -e

while getopts df: OPTION "$@"; do
    case $OPTION in
    d)
        set -x
        ;;
    f)
        XCFFILE=${OPTARG}
        ;;
    esac
done

if [[ -z "$XCFFILE" ]]; then
    echo "usage: `basename $0` [-d] -f <xcfFile>"
    exit 1
fi

# Start gimp with python-fu batch-interpreter
gimp -i --batch-interpreter=python-fu-eval -b - << EOF
import gimpfu

def convert(filename):
    img = pdb.gimp_file_load(filename, filename)
    new_name = filename.rsplit(".",1)[0] + ".png"
    layer = pdb.gimp_image_merge_visible_layers(img, 1)

    pdb.gimp_file_save(img, layer, new_name, new_name)
    pdb.gimp_image_delete(img)

convert('${XCFFILE}')

pdb.gimp_quit(1)
EOF
Cymry answered 28/12, 2013 at 12:34 Comment(2)
I had to change [[ … ]] to [ … ]. Except this small change works brilliantly.Filamentary
Looks nice, but if I understand correctly, gimp is started and closed for each file. Could that be avoided?Anole
A
7

Use xcftools

It's possible that this tool wasn't available (or in any case widely known) at the time this question was originally asked, but the xcf2png terminal command from the xcftools package does exactly what you ask and is very good. I use it for my thesis scripts and would be my go-to choice for this question.

From the documentation:

This is a set of fast command-line tools for extracting information from the Gimp's native file format XCF.
The tools are designed to allow efficient use of layered XCF files as sources in a build system that use 'make' and similar tools to manage automatic processing of the graphics.
These tools work independently of the Gimp engine and do not require the Gimp to even be installed.

"xcf2png" converts XCF files to PNG format, flattening layers if necessary. Transparency information can be kept in the image, or a background color can be specified on the command line.


(PS. I spotted xcftools mentioned in the comments just after I decided to post an answer about it, but I'm posting anyway as that comment was easy to miss (I saw it because I specifically checked for it) and I think xcftools deserves a proper answer's slot, as it really is the most appropriate answer at this point in time.)
Archibaldo answered 22/9, 2016 at 11:29 Comment(2)
Unfortunately, I get Warning: XCF version 8 not supported (trying anyway...). Seems like the last update was in 2009 and even the fork is quite old and does not work.Salina
This answer is out of date and no longer works as of October 2021. It fails with Warning: XCF version 11 not supported (trying anyway...). The converted PNG files are wrong (empty with white background).Cheddar
D
1

I know this is not strictly the correct answer but seeing as a I got directed here while searching.

I modified the example code here to create a workable plugin

http://registry.gimp.org/node/28124

There is a bit of code from another answer on this page

#!/usr/bin/env python

from gimpfu import *
import os



def batch_convert_xcf_to_png(img, layer, inputFolder, outputFolder):
    ''' Convert the xcf images in a directory to png.

    Parameters:
    img : image The current image (unused).
    layer : layer The layer of the image that is selected (unused).
    inputFolder : string The folder of the images that must be modified.
    outputFolder : string The folder in which save the modified images.
    '''
    # Iterate the folder
    for file in os.listdir(inputFolder):
        try:
            # Build the full file paths.
            inputPath = inputFolder + "\\" + file

            new_name = file.rsplit(".",1)[0] + ".png"
            outputPath = outputFolder + "\\" + new_name

            # Open the file if is a XCF image.
            image = None
            if(file.lower().endswith(('.xcf'))):
                image = pdb.gimp_xcf_load(1,inputPath, inputPath)


            # Verify if the file is an image.
            if(image != None):

                #layer = pdb.gimp_image_merge_visible_layers(image, gimpfu.CLIP_TO_IMAGE)
                layer = pdb.gimp_image_merge_visible_layers(image, 1)

                # Save the image.
                pdb.file_png_save(image, image.layers[0], outputPath, outputPath, 0, 9, 0, 0, 0, 0, 0)

        except Exception as err:
            gimp.message("Unexpected error: " + str(err))   




'''                
    img = pdb.gimp_file_load(filename, filename)
    new_name = filename.rsplit(".",1)[0] + ".png"
    layer = pdb.gimp_image_merge_visible_layers(img, gimpfu.CLIP_TO_IMAGE)

    pdb.gimp_file_save(img, layer, new_name, new_name)
    pdb.gimp_image_delete(img)
'''    


register(
        "batch_convert_xcf_to_png",
        "convert chris",
        "convert ",
        "Chris O'Halloran",
        "Chris O'Halloran",
        "2014",
        "<Image>/Filters/Test/Batch batch_convert_xcf_to_png",
        "*",
        [
            (PF_DIRNAME, "inputFolder", "Input directory", ""),
            (PF_DIRNAME, "outputFolder", "Output directory", ""),            
        ],
        [],
        batch_convert_xcf_to_png)



main()
Denunciation answered 7/1, 2015 at 21:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.