GIMP Script-Fu functions for bulk operations ============================================
First draft: 2025-09-10 Published: 2025-11-11
Table of contents:
I am sharing the scripts I have written to manipulate more than one layer simultaneously.
**Note**: The code presented here is licensed differently than usual, as they had been dedicated to the Public Domain originally. Please see the licensing notes at the end.
My pixelated background
Just as there are cat people and there are dog people, there must be analog people and digital people. For sure I am a digital one myself. Although I understand the sentiment behind the famous quote from Bob Widlar saying "Digital? Every idiot can count to one", and I have the utmost respect for those capable of domesticating any kind of analog signal; digital is just such a handy abstraction that it is more pragmatic to use in software. It is a trade-off: I have accepted to treat anything analog as black magic and become a digital idiot just to be able to focus my thinking processes on binary representations of the world.
One especially apt example of this is graphics: I am a raster person as opposed to being a vector person. I was never particularly good in drawing and painting when I was young, on the other hand I was always interested in computer graphics. I was fascinated by 3D but that is a story for another time; 2D was something that I had more immediate uses for. Even my first photo camera was digital, so I guess this can also be kind of a generational thing. Again, I see the appeal of film photography, which is alive to this day (and it very well should be), I just cannot relate to it that much.
I believe the first lesson for a kid to learn on the topic "what to use a computer for besides playing games" should be that one can draw stuff with it. I remember fondly of my first adventures in Paintbrush on Win3.1, as limited as it was, I could spend hours with it when I was allowed to sit to a computer at my parents' workplace. Later I tried quite a few graphics packages, and during high school of course Photoshop was all the rage, of which I managed to get a "trial" version so I could hone my pixel manipulation skills. Then at the Uni came my full exposure to the open-source world, and becoming more responsible and mindful of software licenses, I started using GIMP. As some of my Master's projects and a significant portion of my professional career revolved around image processing algorithms, GIMP v2 became a reliable tool that I could count on every time.
Motivation
Right now I have GIMP version 2.10 installed on four different machines, actively using three of these at least on a weekly basis. Besides the obvious use cases like editing images that go to this site, on Windows I use it even for taking screenshots as well. While on Linux I have `scrot` so this is taken care of automatically, I like to merge screenshots when saving longer webpage views where it has to be scrolled. But by far the most I use GIMP for nowadays is editing PDF files: turning various RPG, board game and tabletop wargame books and resources to be print friendly. I know I could probably use some mobile app or at least a PDF viewer on a tablet or something similar instead of printing them; but I like to do this kind of thing the old-fashioned pen&paper way.
I am basing print friendliness on the ability to print the page on my Xerox black and white laser printer with a coverage figure that is at least close to what the toner capacity is specified at, typically around 5 percent. Although I cannot measure this exactly, because I would have to know the strategy used in the printer for creating grayscales, I can get a quick estimation with the Histogram panel, and also I think I have developed a good feel for this over the years.
We have various filters at our disposal in GIMP to achieve print friendliness, most of them found in the Color menu; I will briefly comment on each of these later. The main challenge that we are facing though is that usually we want to apply the same sequence of operations on every page, or what could be worse, *almost* the same operations but with little differences, as we can encounter various graphic features on just one or two pages. In GIMP, the PDF import function gives us one layer per page, thus applying the same filter on the whole document means executing it on all (or at least on multiple) layers. This feature is missing in GIMP2 as far as I am aware, but we can use the built-in scripting facilities to iterate over the layers and do what we want.
I have implemented most of the scripts presented here in the last three years. As usual, I invite you to join me while I document for future-me how I did what I did. Also the reason why this is rather actual in 2025 is that in this March GIMP version 3 came out, and in the new stable Debian (version 13) someone decided that it should be included. Before taking the leap of faith, I would like to make a snapshot of how these have worked, because I expect I will have to rework most of them for the new version.
About Script-Fu
In GIMP, we have two major ways (besides C obviously) for writing scripts: Script-Fu and Python. Don't ask about the latter, I knew nothing about it and the other option was far more interesting to me. Script-Fu is a variant of Scheme, which in turn is a dialect of Lisp, so I saw this as an opportunity to at least get to know a little the second-oldest high-level programming language still in common use, according to the Wikipedia article (after Fortran, which I should put on my to-do list now I guess).
For anyone who wants to learn scripting GIMP and Script-Fu, I would recommend to take a look at the following online resources:
- GIMP 2.10 User Manual, Chapter 13. Scripting: An introductory guide that has been put together to contain everything necessary to get started.
- GIMP Developer Site, Resource Development: Table of Contents for various additional official written materials related to scripting, plug-ins and custom filters.
- GIMP Developer Site, Quickstart: How to choose the right scripting system/language.
- GIMP Developer Site, Well-behaved plugins: Code of Conduct about respecting certain program states.
- GIMP Developer Site, Programmer's Reference: "This describes Script-Fu, [...] a reference, not a tutorial."
- GIMP Developer Site: Script-Fu Plug-Ins: Fifth chapter in the "How to write a plug-in" series, dedicated to Script-Fu.
Note: While at the first link (to the documentation) we could specify version 2.10 explicitly, the main Developer Site is an ever-evolving resource, so at this time of writing most of the content there is referring to GIMP3 already. Chances are nobody would want to start learning on the old system anyway, but in case you do, just be aware (or try finding an earlier state in a repository somewhere).
Note to self: I am seeing a couple of wordings such as "any language having GI module", "C or a similar low-level programming language", "C or in a high-level language". There are a couple of other language examples mentioned, but it could be worthwhile to explore what are the hard requirements in general for writing plug-ins in *any* language.
One additional thing I would like to point out, is that we also have two quite essential tools that can be accessed in GIMP directly: the 'Plug-in browser' and 'Procedure browser', which can be found in the 'Help' menu. They can play a fundamental role, because with these we can search for internal functions, and find out how to call them in our scripts.
Layer visibility scripts
I have mentioned that the end goal here is to be able to apply certain filters and other operations on multiple layers simultaneously, but also sometimes we want to exclude a couple of layers. As we do not have the ability to select more than one layer directly, and the linking feature is too tedious to use with many layers, I have thought that it would be a handy solution to simply omit any hidden layers, and apply on all that remain visible. This have worked out great but I needed some methods to be able to quickly adjust the visibility on many layers. I am starting my code listings with these, as they are very straight-forward; and in general, I will try to order the scripts in this article from least to most complex.
All layers visible
```
(define (script-fu-vis-on-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(gimp-item-set-visible (aref (cadr layers) i) TRUE)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-vis-on-all"
"/Filters/All Layers Visible"
"Set Visibility to ON for All Layers"
"Zoltan Kovari"
"CC0"
"2022"
"*"
SF-IMAGE "Image" 0
)
```
This is the first snippet presented, so I have to share some details on the general code layout and installation, which will be applicable to all scripts in the article.
There are two major parts, first the definition and then registration of a new procedure. Former is the actual script code itself, so we will mostly focus on this part in the following sections. Registration at the end is necessary to be able to call the procedure by the click of a menu item. In this example, the script will appear directly in the 'Filters' menu in GIMP (somewhere at the bottom).
To install this Script-Fu, on Debian I placed this code in a new file called `script-fu-vis-on-all.scm`, and put it in `~/.config/GIMP/2.10/scripts/` (on Windows, the respective directory is under `AppData` somewhere). You do not have to restart GIMP every time you change the script files, just execute 'Filters > Script-Fu > Refresh Scripts` to reload.
Lastly, some words on the procedure definition itself. Note that we receive `img` as a parameter, that we can in turn use ourselves as parameter to various internal procedures and functions. Then we can see the following basic formula which will be used in each of our scripts in a similar fashion:
- First we start an undo group
- With the `let` command the variables `layers` and `i` are set
- Then we iterate over `layers` with `while`
- In the middle, we do something on the currently indexed layer
- Incrementing `i`
- We end the undo group, and flush all displays to refresh
In this example, at point 4. we are setting the visibility of the layer at index `i` to TRUE.
All layers hide
```
(define (script-fu-vis-off-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(gimp-item-set-visible (aref (cadr layers) i) FALSE)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-vis-off-all"
"/Filters/All Layers Hide"
"Set Visibility to OFF for All Layers"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
)
```
This is the same as the previous one, with the only difference being that we set the layer visibility to FALSE.
All invert visibility
```
(define (script-fu-vis-inv-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let* ((layer (aref (cadr layers) i)) (visible (car (gimp-item-get-visible layer))))
(gimp-item-set-visible layer (if (= visible TRUE) FALSE TRUE))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-vis-inv-all"
"/Filters/All Invert Visibility"
"Invert Visibility for All Layers"
"Zoltan Kovari"
"CC0"
"2022"
"*"
SF-IMAGE "Image" 0
)
```
For each layer we get its current visibility, and basically just flip it to the alternate state.
Other layers hide
```
(define (script-fu-vis-off-others img)
(gimp-image-undo-group-start img)
(let (
(active (car (gimp-image-get-active-layer img)))
(layers (gimp-image-get-layers img))
(i 0)
)
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (<> layer active)
(gimp-item-set-visible layer FALSE)
)
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-vis-off-others"
"/Filters/Other Layers Hide"
"Set Visibility to OFF for Non-Active Layers"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
)
```
It is often desirable to set the visibility of all other layers to false, so that only the active layer would be participating in subsequent actions that operate only on visible layers (i.e. our bulk action scripts, described below).
Besides the usual initialization, we save the active layer to a variable, and then while iterating through all layers, we set the visibility to FALSE for every layer that does not equal the active one.
All matching visible
```
(define (script-fu-vis-match img layer_name invert hide)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (eq? (re-match layer_name (car (gimp-item-get-name layer))) (zero? invert))
(gimp-item-set-visible layer (if (zero? hide) TRUE FALSE))
)
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-vis-match"
"/Filters/All Matching Visible"
"Set Visibility for All Layers with Matching Name"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
SF-STRING "Layer name" "copy"
SF-TOGGLE "Invert match" FALSE
SF-TOGGLE "Hide" FALSE
)
```
With this script we can specify a search term through a pop-up dialog, and set the visibility of each layer with a matching layer name to either TRUE or FALSE. This can be used very effectively in a scenario for example when we have a bunch of layers that have been duplicated, thus all having "copy" appended to their original name. We can select these for bulk operations by matching on the "copy" string.
Note that the script does not touch the visibility of other, i.e. non-matching layers (or the matching, in case the inversion option is checked).
In this code we introduce using pop-up dialog options, so that we can use parameters in our scripts. You can observe that alongside `img` which was present in all our scripts so far, we declared additional parameters this time (namely `layer_name`, `invert` and `hide`). Then at the near end, we used additional lines starting with "SF-" to define how these parameters will get their values. We can ask for a textbox with `SF-STRING`, and for a checkbox with `SF-TOGGLE`. We will use some other types later on, and you can see the full list of possible choices in the official documentation: look for sections 3.4.7 and 3.4.8 on the Your First Script-Fu Script page (and it is also worth to take a look at the example they mention, test-sphere.scm).
The script employs the `re-match` function ("re" meaning regular expression). Inside the iteration we try to match the current layer name against the provided `layer_name` pattern. The `eq?` function allows us to either look for a match if `invert` is zero (unchecked), or a non-match in the alternate case. Then if the these matching criteria were satisfied, we set the layer visibility according to the state of `hide` (TRUE if zero, i.e. unchecked).
Filters
After manipulation of the visibility attribute, we can move on to the main topic, where we want to apply certain actions and filters on the pixel data itself, for multiple layers in bulk.
All visible fill
```
(define (script-fu-fill-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(gimp-edit-fill layer 0)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-fill-all"
"/Filters/Custom Filters/All Visible Fill"
"Run Fill with FG on All Visible Layers"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
)
```
We start with "fill-all", where we execute "Fill with FG Color" (shortcut: Ctrl+,) essentially filling the selected area (or the entire layer without a selection) with the current foreground color.
As usual, when describing these scripts we will concentrate to the inside of the layer iteration, unless something new happens at the initialization level. For every script in this "filter" category, the payload starts by checking if the current layer is visible, and skips it if not. Otherwise the script consists of simply calling `gimp-edit-fill` on the layer, with the zero setting the type of fill to `FILL-FOREGROUND`.
All visible clear
```
(define (script-fu-clear-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(gimp-edit-clear layer)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-clear-all"
"/Filters/Custom Filters/All Visible Clear"
"Run Clear on All Visible Layers"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
)
```
The `gimp-edit-clear` function executes "Clear" (shortcut: Del), which sets the selected pixels (or the entire layer without a selection) to transparent if the layer has an alpha channel, otherwise it fills with the background color.
All visible flatten
```
(define (script-fu-flatten-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(gimp-layer-flatten layer)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-flatten-all"
"/Filters/Custom Filters/All Visible Flatten"
"Remove the alpha channel from All Visible Layers"
"Zoltan Kovari"
"CC0"
"2025"
"*"
SF-IMAGE "Image" 0
)
```
By flattening a layer, we remove the alpha channel with blending all transparent pixels against the background color. This function has a dedicated menu item in the "Layer" menu, but that of course applies it only to the active layer.
I have found this useful when transparency is introduced trough working with feathered-edge selections, especially when I am using the same selection more than once, inverted. Sometimes a small semi-transparent line can appear if I am not careful enough, but this procedure can get rid of these artifacts.
All visible invert
```
(define (script-fu-invert-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(gimp-invert layer)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-invert-all"
"/Filters/Custom Filters/All Visible Invert"
"Run Invert on All Visible Layers"
"Zoltan Kovari"
"CC0"
"2023"
"*"
SF-IMAGE "Image" 0
)
```
Applies color inversion to the visible layers. Naturally, I use this all the time when converting graphical documents to print-friendly.
All visible desaturate
```
(define (script-fu-desat-all img desat-mode)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(gimp-drawable-desaturate layer desat-mode)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-desat-all"
"/Filters/Custom Filters/All Visible Desaturate"
"Call Desaturate to All Visible Layers"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
SF-OPTION "Desaturation Mode" '("Lightness (HSL)" "Luma" "Average (HSI
Intensity)" "Luminance" "Value (HSV)") 0
)
```
Some of the more complex filters come now. The "Destaurate" function has a parameter to set the mode, with `SF-OPTION` we can call up a drop-down list for the user to select their preference. The trailing zero sets the default to "Lightness", which I happen to use most of the time. When specifying the options, we have to take care to use an ordering so that `gimp-drawable-desaturate` gets the correct index.
All visible brightness-contrast
```
(define (script-fu-brcntr-all img bright contr)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(gimp-brightness-contrast layer bright contr)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-brcntr-all"
"/Filters/Custom Filters/All Visible Br.Contr."
"Brightness/Contrast to All Visible Layers"
"Zoltan Kovari"
"CC0"
"2022"
"*"
SF-IMAGE "Image" 0
SF-ADJUSTMENT "Brightness" '(0 -127 127 1 10 0 0)
SF-ADJUSTMENT "Contrast" '(0 -127 127 1 10 0 0)
)
```
Granted, "Levels" provide the same functionality with greater flexibility, but many times it is just easier to set two numbers than five. We use the `SF-ADJUSTMENT` element to display nice sliders. Its parameters are: default value, minimum, maximum, minor step, major step, decimal digits, and lastly I am using zero for the type so that I get a slider (opposed to a spinner).
All visible levels
```
(define (script-fu-levels-all img low-in high-in gamma low-out high-out)
(let (
(low_in (/ low-in 255))
(high_in (/ high-in 255))
(low_out (/ low-out 255))
(high_out (/ high-out 255))
)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(gimp-drawable-levels layer 0 low_in high_in FALSE
gamma low_out high_out FALSE)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
)
(script-fu-register
"script-fu-levels-all"
"/Filters/Custom Filters/All Visible Levels"
"Levels on All Visible Layers"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
SF-ADJUSTMENT "Low Input" '(0 0 255 1 10 0 0)
SF-ADJUSTMENT "High Input" '(255 0 255 1 10 0 0)
SF-ADJUSTMENT "Gamma" '(1 0.1 10 0.1 1 1 0)
SF-ADJUSTMENT "Low Output" '(0 0 255 1 10 0 0)
SF-ADJUSTMENT "High Output" '(255 0 255 1 10 0 0)
)
```
As I was mentioning, there are some effects that cannot be achieved with brightness-contrast alone or at least not exactly, so I had to add this. Still I lack the ability to use curves, which have some advantages in some situations, but honestly it would need some serious thought how to implement that using these "SF-" elements.
Note that I have set some of the parameters explicitly to `gimp-drawable-levels`: the zero means using the VALUE channel, and the two FALSE values disable clamping both on input and output. These could be added easily with a drop-down and some checkboxes, but as never needed them before I decided to make the interface less cramped.
All visible Sobel
```
(define (script-fu-sobel-all img horizontal vertical keep-sign)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(plug-in-sobel RUN-NONINTERACTIVE img layer horizontal
vertical keep-sign)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-sobel-all"
"/Filters/Custom Filters/All Visible Sobel"
"Sobel filter on All Visible Layers"
"Zoltan Kovari"
"CC0"
"2025"
"*"
SF-IMAGE "Image" 0
SF-TOGGLE "Horizontal" TRUE
SF-TOGGLE "Vertical" TRUE
SF-TOGGLE "Keep sign" TRUE
)
```
Last in this section is the Sobel edge-detect filter, which can be used to reduce ordinary photos and large artwork to its silhouettes, thus greatly decreasing the amount of ink it would waste when printed. Sobel works reliably but needs some post-processing; for one-off work there are better filters available in GIMP in the "Edge-Detect" or even in the "Artistic" menus, but I have found that those are generally much more sensitive to their parameters and need previewing in each individual case. My first choice would have been "Difference of Gaussians" as it is simple as well and gives a cleaner result, but I remember having some difficulty with that in GIMP2 invoking the plugin from a script. This is definitely a point where I am looking forward to switch to the new scripting framework.
Other manipulations
We have only two helper scripts left to discuss, which aid in working with multiple layers having near-identical structure, that represent individual cards for example, or just pages laid out according to a similar template. These tools make it possible to have a workflow where we duplicate each of these layers, do our thing on every one of the copies in bulk, than merge them back down to their respective source layers. This makes it easier to deal with situations where we need to have different filters applied to different areas on the page layout.
All visible duplicate
```
(define (script-fu-dupl-all img)
(gimp-image-undo-group-start img)
(let ((layers (gimp-image-get-layers img)) (i 0))
(while (< i (car layers))
(let ((layer (aref (cadr layers) i)))
(if (= (car (gimp-item-get-visible layer)) TRUE) (begin
(let ((layer_copy (car (gimp-layer-copy layer TRUE))))
(gimp-image-insert-layer
img layer_copy
(car (gimp-item-get-parent layer))
(car (gimp-image-get-layer-position img layer))
)
)
))
)
(set! i (+ i 1))
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-dupl-all"
"/Filters/Custom Manipulation/All Visible Duplicate"
"Run Duplicate on All Visible Layers"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
)
```
The script works by invoking `gimp-layer-copy` first, returning the copy itself, then simply calling `gimp-image-insert-layer` on it. We are using the same parent and position as the source layer, so the copy appears just above the source in the layer list. The naming of the new layer is automatic, appending "copy", "copy #1" and so on.
All matching merge-down
```
(define (script-fu-mdown-match img layer_name)
(gimp-image-undo-group-start img)
(define start_layers (car (gimp-image-get-layers img)))
(do (
(i 0 (+ i 1))
(break #f)
) (
(or (>= i start_layers) (eq? break #t))
)
(let* (
(layers (gimp-image-get-layers img))
(num_layers (car layers))
)
(if (>= i num_layers)
(set! break #t)
(begin ;else
(let ((layer (aref (cadr layers) i)))
(if (and (= (car (gimp-item-get-visible layer)) TRUE)
(eq? (re-match layer_name (car (gimp-item-get-name layer))) #t)
)
(gimp-image-merge-down img layer 0)
)
)
)
)
)
)
(gimp-image-undo-group-end img)
(gimp-displays-flush)
)
(script-fu-register
"script-fu-mdown-match"
"/Filters/Custom Manipulation/All Matching Merge-down"
"Run Merge-down on All Visible Layers with Matching Name"
"Zoltan Kovari"
"CC0"
"2024"
"*"
SF-IMAGE "Image" 0
SF-STRING "Layer name" "copy"
)
```
Finally we have something a bit more complex with the merge-down script. The principal idea is similar to what we saw in the case of All matching visible, with doing a `re-match` on the layer name, and this time calling `gimp-image-merge-down` in the end. What is all the other trickery then?
We are using a `do` loop now, which is a bit more clear than `while`. First we define our loop variables, then the conditions for termination, then comes the body expressions. We have the variable `i` here the same just as before, that we will increment in each iteration and compare to the starting number of layers. Note that `break` in Scheme is not a keyword, so it can be used as a variable name, signaling that a previous run of the body requested early loop termination. This is triggered here when the `i` index exceeds the *current* number of layers, which would obviously cause an out-of-bounds access. The main thing to notice of course is that we have to check the number of layers each time, because it is changing due to our actions: each merge-down call removes one layer. As always, we have to be careful when mutating the list we are currently iterating over: it is okay in this case, because we do not want to chain the merges, so it is not a problem that the index is in fact skipping an element.
Let me close with the following observation: reviewing the code now to be able to write the commentary, I have just realized that `start_layers` is of course superfluous, as we are never adding new layers in this script. The inner `num_layers' check is sufficient by itself, and would terminate the loop even if there were no matches thus no removals; and in case there were some, it would break exactly the same way as it does right now. I could have corrected this before publishing now, but left it as it is for the sake of the exercise. Naturally I will clean this up in the GIMP3 version.
Conclusion
Although it might be an option to use GIMP2 for a little while longer, I think I should start adapting to the new version for multiple reasons. I have already mentioned package repositories switching even on Debian, so it would be inconvenient trying to cling on. Apart from this, last time I wanted to expand on these scripts with the Sobel filter, I already encountered difficulties with the old version 2 Script-Fu interpreter. Now I believe there is some kind of difference going on between main GIMP version and the interpreter, I would imagine it is the best to use the latest of each.
I had seen GIMP3 briefly a month or two ago. My first thought was that the UI seem to have changed a lot, while on second glimpse the functionality looks to be about the same. The real problem for me would be subtle changes in the algorithms and things like that. I can tell you that on literally the first time I fired up the program not just to look around but with a specific task to do, it did something in a very different way than I expected (and something basic like color inversion if I remember correctly), and I could not figure it out in short order so had to change back to GIMP2.
I guess what I wanted to say here is that the reason I was waiting this much already is the expectation that there is bit of a struggle ahead of me. In any way, make sure to check back later for an updated suite of layer manipulation scripts for GIMP3.
Licensing notes
Please excuse that I did not make these scripts available either on GitHub, or as direct file download. The reason for this is laziness for sure, but I think in a different way as you would expect. You can see I have licensed these originally as CC0, or to use a more familiar term, they have the Public Domain dedication (see the relevant description at Creative Commons). I do not know what could have motivated me to choose this other than being lazy, because I am pretty sure I should have been very well aware of CC licenses being sub-optimal for code, when I wrote the first script in 2022 (even Creative Commons recommends against this).
You might say now: Hold on a minute, what is the difference between CC0 and CC-BY in this regard? Why is this not a problem for any other article here that have code snippets? And you would be right, it is a problem, so in fact I am publishing another post right after this to make the situation clear. You can read it here, and I intend to make it accessible right at the index page as well.