0

Objective

I have a bunch of .tga images that use transparency. These images are used in an application where they are sometimes rendered with their transparency and sometimes without, so the 'background' (the RGB areas corresponding to the black parts in a layer mask containing the alpha channel) is critically important.

In order to change this background for an individual image, my manual process (which may not even be the most efficient) is: Add layer mask > Transfer layer's alpha channel > copy contents of layer mask > Apply layer mask > paste new background layer into the image (same dimensions) > move new layer to bottom of stack > flatten image > Add layer mask (to resulting single layer) > Transfer layer's alpha channel > paste contents of previous layer mask from clipboard > Apply layer mask > export to .tga.

I am attempting to write a script that takes as arguments: a target file extension, a directory containing target files, a new background image, and an option to create backups of the target files. The batch script should apply the above process to every image in the directory with the target file extension.

Approach

Following the example of GIMP's scripting tutorial and their batch mode page, I wrote the following script-fu (scheme). I have not done any sort of exception handling for cases where the background and target images are different dimensions or file types. For my purposes, all target files are .tga and all backgrounds are .tga/.png/.jpg.

(script-fu-register
    "batch-background-replacement"
    "Batch Background Replacement"
    "Replace backgrounds of all images in a directory"
    "Callistonian"
    "2021, Callistonian"
    "September 28, 2021"
    ""
    SF-STRING   "File type"             "tga"
    SF-DIRNAME  "Directory"             ""
    SF-FILENAME "New background"        (string-append "" gimp-data-directory "")
    SF-TOGGLE   "Create backups?"       0
)

(script-fu-menu-register "batch-background-replacement" "<Image>/File")

(define (batch-background-replacement fileType directory background backups)
    (let*
        (
            (filelist (cadr (file-glob (string-append directory "*." fileType) 1)))
        )
        (while (not (null? filelist))
            (let*
                (
                    (filename (car filelist))
                    (image (car (gimp-file-load 1 filename filename)))
                    (layer (car (gimp-image-get-active-layer image)))
                )
                (gimp-image-undo-disable image)
                (if (equal? backups 1)
                    (gimp-file-save 1 image layer (string-append filename "_backup") (string-append filename "_backup"))
                )
                (gimp-layer-add-mask layer (car (gimp-layer-create-mask layer 3)))
                (gimp-edit-copy (car (gimp-image-get-active-drawable image)))
                (gimp-layer-set-apply-mask (car (gimp-image-get-active-layer image)) TRUE)
                (gimp-image-insert-layer image (car (gimp-file-load-layer 1 image (car (gimp-file-load 1 background background)))) 0 1)
                (gimp-image-flatten image)
                (gimp-layer-add-mask (car (gimp-image-get-active-layer image)) (car (gimp-layer-create-mask layer 3)))
                (gimp-edit-paste (car (gimp-image-get-active-drawable image)) TRUE)
                (gimp-layer-set-apply-mask (car (gimp-image-get-active-layer image)) TRUE)
                (gimp-image-undo-enable image)
                (gimp-file-save 1 image layer filename filename)
                (gimp-image-delete image)
            )
            (set! filelist (cdr filelist))
        )
    )
)

script dialogue box

Problem

After resolving some syntax errors, the script now runs but fails silently - nothing happens to the target images and there is no evidence GIMP has done anything.

Comments

I suspect there is a problem with the paths to the target images and the new background and the script simply isn't finding either of them. But I haven't found a way to print anything anywhere which makes debugging, well, impossible.

This is my first attempt at writing a GIMP script or using scheme. I know that GIMP's Batch Mode page actually suggests using the command line for batch processing, but I'd prefer for all inputs to be given in a nifty dialogue box like the one this script provides.

  • You can also use Python, which is somewhat simpler, see [here](https://stackoverflow.com/questions/44430081/how-to-run-python-scripts-using-gimpfu-from-windows-command-line/44435560#44435560), Some debug techniques [here](https://www.gimp-forum.net/Thread-Debugging-python-fu-scripts-in-Windows). – xenoid Sep 30 '21 at 06:51

2 Answers2

0

gimp-message is the command to get debug messages, either to a popup dialog or into the Error Console

Also I think you need a directory separator, DIR-SEPARATOR in this line:

(string-append directory "*." fileType)

Otherwise you get this: "C:\\Program Files\\GIMP 2.10\\share\\gimp\\2.0*.tga" which probably won't match any of your files.

i.e. (string-append directory DIR-SEPARATOR "*." fileType) to get this:

"C:\\Program Files\\GIMP 2.10\\share\\gimp\\2.0\\*.tga"
paynekj
  • 323
  • 2
  • 5
0

With the target file paths resolved (thanks paynekj, would vote for you if I could), I was able to continue debugging and come up with a functional script. There were a few bad function calls which have been corrected and I added a warning in the case that any target file dimensions don't match the selected background image.

While the script works as intended, the issue now is that it takes way too long - approximately 3 seconds on my machine per 69x96 image and with around 6,000 images to process it will take several hours to get through all of them. My warning message about the dimensions mismatch doesn't seem to affect processing time. Disabling the undo also doesn't seem to have much effect (I only added this because I read elsewhere that it would help). If anyone has any suggestions for making the script more efficient please enlighten me.

(define (batch-background-replacement fileType directory background backups)
    (let*
        (
            (filelist (cadr (file-glob (string-append directory DIR-SEPARATOR "*." fileType) 1)))
            (filelistLength (length filelist))
            (backgroundImage (car (gimp-file-load 1 background background)))
            (backgroundWidth (car (gimp-image-width backgroundImage)))
            (backgroundHeight (car (gimp-image-height backgroundImage)))
        )
        (gimp-image-delete backgroundImage)
        (gimp-message-set-handler 0)
        (while (not (null? filelist))
            (let*
                (
                    (filename (car filelist))
                    (image (car (gimp-file-load 1 filename filename)))
                    (layer (car (gimp-image-get-active-layer image)))
                    (width (car (gimp-image-width image)))
                    (height (car (gimp-image-height image)))
                )
                
                ;warning message for mismatched dimensions
                (if (and (= width backgroundWidth) (= height backgroundHeight)) ()
                    (gimp-message (string-append "WARNING: target image dimensions do not match selected background image:\n" filename "\n\nBatch Background Replacement will continue processing images but the new backgrounds may not line up correctly."))
                )
                
                (gimp-image-undo-disable image) ;purely to improve performance, doesn't seem to have much effect
                
                ;save backups
                (if (= backups 1)
                    (gimp-file-save 1 image layer (string-append filename "_backup." fileType) (string-append filename "_backup." fileType))
                )
                
                ;core process
                (gimp-layer-add-mask layer (car (gimp-layer-create-mask layer 3)))  ;'Transfer layer's alpha channel'
                (gimp-edit-copy (car (gimp-image-get-active-drawable image)))   ;copy contents of alpha channel
                (gimp-image-insert-layer image (car (gimp-file-load-layer 1 image background)) 0 1) ;insert new background as bottom layer
                (gimp-image-flatten image)  ;flatten image without applying layer mask, this doesn't work in interactive mode but seems to work fine in script
                (gimp-layer-add-mask (car (gimp-image-get-active-layer image)) (car (gimp-layer-create-mask (car (gimp-image-get-active-layer image)) 3)))  ;'Transfer layer's alpha channel'
                (gimp-edit-paste (car (gimp-image-get-active-drawable image)) TRUE) ;paste alpha channel from previous layer mask
                (gimp-floating-sel-anchor (car (gimp-image-get-active-layer image)))    ;'Anchor the floating layer'
                (gimp-layer-remove-mask (car (gimp-image-get-active-layer image)) 0)    ;'Apply layer mask'
                (gimp-image-undo-enable image)  ;probably unnecessary
                (gimp-file-save 1 image (car (gimp-image-get-active-layer image)) filename filename)    ;export
                (gimp-image-delete image)   ;close
            )
            (set! filelist (cdr filelist))
        )
        (gimp-message (string-append "Batch Background Replacement has finished processing " (number->string filelistLength) " images."))
    )
)

edit: Added some refinements to the script as well as comments that at least explain what I think is happening. It's still taking about 3-4 seconds per file, using 16 threads in Preferences. I tried unchecking 'Keep record of used files in the Recent Documents list' but had no noticeable effect.

Running the commands individually in the console, most are instantaneous, but opening the files and exporting certainly are not - these take about 3 seconds to run interactively which indicates that almost all of the processing time is just opening and exporting. Perhaps it would be faster to open all the target images before starting the core process on each and then export them all at the end?