Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Processing XCF files in batch
#1
I have a large number (~3500) of .xcf GIMP projects. I would like to perform some simple operation on all of them. Specifically, the images all contain one layer that's all green and one layer that's all red, and I would like to flip those two colours (i.e. I would like to turn RGB (0, 255, 0) to (255, 0, 0) and vice versa).

Is there a way to do this automatically without manually opening each file in GIMP and recolouring it by hand?
Reply
#2
Yes, with a script. How do you identify the layers? By name? Position in the stack?
Reply
#3
Either should be fine, since as far as I can see, both are consistent across all the files. But probably by name would be more reliable.
Reply
#4
So this is how far I've gotten using the GIMP docs and a ton of googling:

I find my files in a python script and then use the following code to call a separate GIMP script on each of the files:
Code:
for f in found_files:
    scriptpath = r'path/to/script'
    scriptname = 'gimp.py'
    pycode = f'import sys; sys.path.insert(0, "{scriptpath}"); import {scriptname}; {scriptname}.run({f})'
    subprocess.run(f'gimp -idf --batch-interpreter python-fu-eval -b \'{pycode}\' -b \'pdb.gimp_quit(1)\'', shell=True, check=True)
The gimp.py file then contains the following code:
Code:
from gimpfu import *
import gimpcolor


def run(f):
   xcf = pdb.gimp_xcf_load(0, f, f)
   for layer in xcf.layers:
       layer.opacity = 100.
   if layer.name == 'Currently Green':
       pdb.gimp_context_set_foreground(gimpcolor.RGB(255, 0, 0))
       layer.fill(FOREGROUND_FILL)
   elif layer.name == 'Currently Red':
       pdb.gimp_context_set_foreground(gimpcolor.RGB(0, 255, 0))
       layer.fill(FOREGROUND_FILL)
   pdb.gimp_xcf_save(0, xcf, None, '/path/to/save/to.xcf', '/path/to/save/to.xcf')
   xcf.merge_visible_layers(NORMAL_MODE)
   pdb.file_png_save(xcf, xcf.layers[0], '/export/path.png', '/export/path.png', 0, 9, 1, 0, 0, 1, 1)

However this results in a bunch of batch command experienced an execution error error messages, which I don't know how to debug, since printing doesn't seem to work from the gimp script so I can't even tell where the error is happening.

What would be the correct way to fill the layers with the desired colours?
Reply
#5
One of the clever guys needed for advice, Hopefully Ofnuts will come along.
I looked at using the built-in Gimp color exchange when you first posted.

It looks to me like you are using linux (which distro ?). It might help if you post an example .xcf file.

I am no coder, I would normally use the BIMP batch plugin but that is no good with multi-layer .xcf files.

The Gimp 2.10 color-exchange uses GEGL. The exchange syntax is straight forward but I can not get it to recognize a color, either in GEGL-graph or a plugin.

You might want to consider falling back to the old Gimp 2.8 compiled exchange plugin. On the simplest level, a python plugin, with encoded layer positions, might work like this in Gimp 2.10: https://i.imgur.com/hujMEzL.mp4

I have no idea how that gets wrapped in a Gimp batch command.
Reply
#6
I unfortunately can't upload the actual XCFs as they're confidential stuff but I've created a sample XCF that captures the main idea.

I'm currently running this on Windows but I do also have a Linux distro installed (Ubuntu 20). So if Windows is the problem, I can swap to Linux any time.


Attached Files
.xcf   Sample.xcf (Size: 721.43 KB / Downloads: 91)
Reply
#7
Well, I repeat, my scripting ability is very basic.

The attached zip contains 3 files. color-exchange (linux),  color-exchange.exe (win) , and a very simple two line plugin swap-rgb.py

Put the appropriate color-exchange & swap-rgb.py in your user plugins folder. swap-rgb.py registers in the Tools menu.

Fortunately your example uses rgb(255,0,0) red and rgb(0,255,0) green  the layer position is in  "image.layers[x] and works for your example.xcf. Edit as required.

Works here Gimp 2.10.32 / kubuntu 20.04 (providing you have a working gimp-python ) and Gimp 2.10.32 / Win 10 (VM)

How to put this into a batch file, I do not know. Might / might not help you.


Attached Files
.zip   color-exchange.zip (Size: 35.55 KB / Downloads: 65)
Reply
#8
(12-09-2022, 03:43 PM)Mate de Vita Wrote: So this is how far I've gotten using the GIMP docs and a ton of googling:

I find my files in a python script and then use the following code to call a separate GIMP script on each of the files:
Code:
for f in found_files:
    scriptpath = r'path/to/script'
    scriptname = 'gimp.py'
    pycode = f'import sys; sys.path.insert(0, "{scriptpath}"); import {scriptname}; {scriptname}.run({f})'
    subprocess.run(f'gimp -idf --batch-interpreter python-fu-eval -b \'{pycode}\' -b \'pdb.gimp_quit(1)\'', shell=True, check=True)
The gimp.py file then contains the following code:
Code:
from gimpfu import *
import gimpcolor


def run(f):
   xcf = pdb.gimp_xcf_load(0, f, f)
   for layer in xcf.layers:
       layer.opacity = 100.
   if layer.name == 'Currently Green':
       pdb.gimp_context_set_foreground(gimpcolor.RGB(255, 0, 0))
       layer.fill(FOREGROUND_FILL)
   elif layer.name == 'Currently Red':
       pdb.gimp_context_set_foreground(gimpcolor.RGB(0, 255, 0))
       layer.fill(FOREGROUND_FILL)
   pdb.gimp_xcf_save(0, xcf, None, '/path/to/save/to.xcf', '/path/to/save/to.xcf')
   xcf.merge_visible_layers(NORMAL_MODE)
   pdb.file_png_save(xcf, xcf.layers[0], '/export/path.png', '/export/path.png', 0, 9, 1, 0, 0, 1, 1)

However this results in a bunch of batch command experienced an execution error error messages, which I don't know how to debug, since printing doesn't seem to work from the gimp script so I can't even tell where the error is happening.

What would be the correct way to fill the layers with the desired colours?

Several things:
  • Your "external" script in PythonV3. Keep in mind that the Python using by Gimp is PythonV2 (so that's the old print syntax and there are no f-strings).  If you abide to this then print statements do send output to the Gimp process StdOut and on Linux/OSX if you start Gimp from a terminal this is readily readable. Things are a bit more complicated on windows but there are ways.
  • You also have to make sure that you have Python support in Gimp (presence of Filters ➤ Python-fu ➤ Console). Not too clear on which OS you are but on Windows installing both Gimp and an external Python interpreter can mess up things for Gimp.
  • Using the shell=True form is somewhat risky, given the nesting of quotes you have to deal with.
  • You are importing moduleName.py, not moduleName
  • NORMAL_MODE is not really valid for merge_visible_layers(). That should be EXPAND_AS_NECESSARY (same value, but better vibes)
  • If would be a lot better to pass a directory to scan for Gimp and to iterate the files inside the Gimp script. With the current form you are potentially paying the price of a Gimp startup for every file (unless there is already a Gimp instance running)
Reply
#9
(12-09-2022, 09:02 PM)rich2005 Wrote: Well, I repeat, my scripting ability is very basic.

The attached zip contains 3 files. color-exchange (linux),  color-exchange.exe (win) , and a very simple two line plugin swap-rgb.py

Put the appropriate color-exchange & swap-rgb.py in your user plugins folder. swap-rgb.py registers in the Tools menu.

Fortunately your example uses rgb(255,0,0) red and rgb(0,255,0) green  the layer position is in  "image.layers[x] and works for your example.xcf. Edit as required.

Works here Gimp 2.10.32 / kubuntu 20.04 (providing you have a working gimp-python ) and Gimp 2.10.32 / Win 10 (VM)

How to put this into a batch file, I do not know. Might / might not help you.

Thank you, but as far as I can tell this still only works for a single project/image, so the batch processing is still necessary. Additionally, unfortunately I've found that some of the files have pixels that don't have the exact colour values (255, 0, 0) or (0, 255, 0), but instead have something close, like (252, 0, 0). So I'd like to instead fill all the coloured pixels in the two layers with the correct colour (so for instance also replace (252, 0, 0) with (0, 255, 0)).

(12-10-2022, 07:39 AM)Ofnuts Wrote: Several things:
  • Your "external" script in PythonV3. Keep in mind that the Python using by Gimp is PythonV2 (so that's the old print syntax and there are no f-strings).  If you abide to this then print statements do send output to the Gimp process StdOut and on Linux/OSX if you start Gimp from a terminal this is readily readable. Things are a bit more complicated on windows but there are ways.
  • You also have to make sure that you have Python support in Gimp (presence of Filters ➤ Python-fu ➤ Console). Not too clear on which OS you are but on Windows installing both Gimp and an external Python interpreter can mess up things for Gimp.
  • Using the shell=True form is somewhat risky, given the nesting of quotes you have to deal with.
  • You are importing moduleName.py, not moduleName
  • NORMAL_MODE is not really valid for merge_visible_layers(). That should be EXPAND_AS_NECESSARY (same value, but better vibes)
  • If would be a lot better to pass a directory to scan for Gimp and to iterate the files inside the Gimp script. With the current form you are potentially paying the price of a Gimp startup for every file (unless there is already a Gimp instance running)

So I've gone through this and adapted my approach significantly, as well as adding the colour correction mentioned above to the blue layer too. I now call my GIMP script using:
Code:
scrpath = 'path/to/script'
scrname = 'fix_projects'
xcfdir = 'dir/of/xcf/files'
savedir = 'dir/to/save/new/xcf/files/to'
pngdir = 'dir/to/export/png/files/to'
pycode = f'import sys; sys.path.insert(0, "{scrpath}"); import {scrname}; {scrname}.run("{xcfdir}", "{savedir}", "{pngdir}")'
subprocess.run(['gimp', '-idf', '--batch-interpreter', 'python-fu-eval', '-b', pycode, '-b', 'pdb.gimp_quit(1)'], check=True)

And this is now my GIMP script:
Code:
from gimpfu import *
import gimpcolor
import os
import os.path as osp
import sys


sys.stderr = open(osp.splitext(__file__)[0] + '.log', 'a')
sys.stdout = sys.stderr


def run(project_dir, save_dir, export_dir):
    for root, _, fnames in os.walk(project_dir):
        for fname in fnames:
            bname, ext = osp.splitext(fname)
            if ext.lower() != '.xcf':
                continue

            project_f = osp.join(root, fname)
            rel_path = osp.dirname(osp.relpath(project_f, project_dir))
            save_f = osp.join(save_dir, rel_path, bname + '.xcf')
            export_f = osp.join(export_dir, rel_path, bname + '.png')
            fix_project(project_f, save_f, export_f)


def fix_project(project_f, save_f, export_f):
    print project_f
    xcf = pdb.gimp_xcf_load(0, project_f, project_f)
    for layer in xcf.layers:
        layer.opacity = 100.
        if layer.name == 'Currently Green':
            pdb.gimp_context_set_foreground(gimpcolor.RGB(255, 0, 0))
            layer.fill(FOREGROUND_FILL)
        elif layer.name == 'Currently Red':
            pdb.gimp_context_set_foreground(gimpcolor.RGB(0, 255, 0))
            layer.fill(FOREGROUND_FILL)
        elif layer.name == 'Currently Blue':
            pdb.gimp_context_set_foreground(gimpcolor.RGB(0, 0, 255))
            layer.fill(FOREGROUND_FILL)

    if not osp.isdir(osp.dirname(save_f)):
        os.makedirs(osp.dirname(save_f))
    if not osp.isdir(osp.dirname(export_f)):
        os.makedirs(osp.dirname(export_f))
    pdb.gimp_xcf_save(0, xcf, None, save_f, save_f)
    xcf.merge_visible_layers(EXPAND_AS_NECESSARY)
    pdb.file_png_save(xcf, xcf.layers[0], export_f, export_f, 0, 9, 1, 0, 0, 1, 1)
    pdb.gimp_image_delete(xcf)

This code now runs properly and produces the desired log file, as well as the .xcf and .png outputs. However, this current version fills the entire layers with the colours so the end image just ends up being all blue (since the blue layer is on top). How would I fill only the parts of the layer that are already coloured and not transparent?
Reply
#10
Just replace
Code:
layer.fill(FOREGROUND_FILL)


with:
Code:
layer.lock_alpha=True
pdb.gimp_edit_fill(layer,FILL_FOREGROUND)

So now you bucket-fill with the alpha channel locked, so pixels keep their opacity. layer.fill() always fill the whole layer, it is intended to initialize the layer after creation, not for general painting.
Reply


Forum Jump: