Python-fu Gimp Tutorial #1: Variables, simple math and types

See the other tutorials in this series.

This tutorial is designed to help people that have absolutely no experience with programming at all, in any language. The basic principles you need to understand before you can start to use Python-fu and start scripting in GIMP are actually quite easy, so it won’t take long before you can understand everything you are doing in the simple scripts we’ll explore in this series. Hopefully by the end of this series you should be able to write your own script from scratch that can automate a series of tasks to produce a desired effect.

But before we can get into writing those scripts, we have to learn some basic Python. If you know the basics of Python already, you can safely skip this tutorial.

We are going to look at variables, simple mathematical expressions and functions. Some of the vocabulary might be unfamiliar, but it’s all pretty easy to pick up.

This blog post is a companion piece to this video tutorial:

Now, I’ve had both Python and GIMP separately installed on my computers for years, so I can’t remember if you need to download Python separately, and I can’t do a clean install of either without messing with some important customizations to both. So, the first thing you need to do is open up GIMP, go to Filters and look at the bottom end of the menu. If you can see the options ‘Python-fu’ which leads to Console, try running console, and if you get the console screen, which we will call the shell, then we are good to go. We don’t need to download anything new for this tutorial, since GIMP has a Python console built in. If, however, this console is not available, you’ll probably need to install Python. So go and download Python 2.7. So, we Open up GIMP, go to Filters, Python-fu, and then Console. Each time I type something into the console, do it yourself too. Experiment with the principles and try to develop your understanding by hacking at the things we go over here. Learning a tiny bit of code and then experimenting with it will be the key to success, especially if you start modifying other people’s plug-ins.

Variables

A variable is a label we can give to a value that might not always be the same everytime we run our program or script. For example, I might have a script that puts some copyright information on my photos. I could hardcode my name and the year of creation into a script so it always say ‘Jackson Bates – 2015’, but what about next year? Should I manually change the script every year? What if I publish the script for others to use – they don’t all want to be me. So the sensible thing to do is store the name and year as seperate variables.

Note: In the examples below, the items preceded by >>> are what you type in the console. Bold text represents values returned by python console or program.

To do that I can simple write in the console:

>>>name = "Jackson Bates"

and

>>>year = "2015"

And if I want to see the results of that I can simply type:

>>>print name
Jackson Bates
>>>print year
2015

One of the cool things we can do with variables like these is add them together using the + operand (the plus sign):

>>>print name + year
Jackson Bates2015

But you can see that it jams them together because we didn’t add a space in between, so we could type this instead:

>>>print name + " " + year
Jackson Bates 2015

We can also create a new variable from the two old ones:

>>>copyright_info = name + " " + year
>>>print copyright_info
Jackson Bates 2015

Notice that each of my variables has a very easy to understand name. This is a good habit to get into. You should be able to tell exactly what a variable represents. Python is actually a pretty readable language, and you can make that easier for yourself if you add variables that are easily readable too. Notice that I also use underscores for spaces. A variable name can be split by a space in Python

So what happens if we change the year and then print the copyright_info again?
Well we can test that:

>>>year = "2016"
>>>print copyright_info
Jackson Bates 2015

You can see that the copyright_info variable didn’t change. Python doesn’t remember that copyright_info was initially built from name and year, and then assume that when one updates it should flow on. If you want the variable copyright_info to update you need to assign the values again:

>>>copyright_info = name + " " + year
>>>print copyright_info
Jackson Bates 2016

So the first thing to remember about variables is that they are an easy to remember name that represents a value that can change – i.e. it can vary (hence ‘variable’).

The second thing we have learnt is that we can perform operations on variables. We can stitch variables together when we print them, or we can stitch them together to make a new variable.
The proper word for this is ‘concatenation’, which is just a fancy word for sticking them together, like we did when we made the copyright_info variable by concatenating the name, a printed space, and the year.

What do you think happens if you write something like:

>>>print copyright_info - year
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unsupported operand type(s) for -: 'str' and 'str'

Well, we get an error message. Don’t be scared of these – you’ll see plenty, and not because you suck, but just because that’s what happens when you program. What it tells us here is that we can’t use a minus sign for ‘str.’

So what’s a ‘str’? Well there are 3 main types of variable that we’ll focus on in this tutorial. There are more than that in Python, but for our purposes we’ll get by with these three to begin with. We have Strings, Integers and Floats. A string, the ‘str’ we saw earlier, is a string of literal text. Anything you put in quotes is a literal string in Python. If you add a piece of text to another piece of text, you concatenate them – you stick them together. But Python doesn’t know how to do other mathematical looking things to strings, as we saw with minus. You can divide a string by another string either, for obvious reasons. You can multiply a string by an integer though…

What effect do you think this has:

>>>print "hello" * 5
hellohellohellohellohello

Now you may notice that 5 is not in quotation marks. When we wrote the year as “2015” earlier, we were treating it as a text string. But the 5 we just used is an integer. That means we can use it for real maths. The Python console can actually be used as a calculator:

>>>print 1 + 2 + 3
6

Which is different to:

>>>print "1" + "2" + "3"
123

An integer is a whole number, and we need to remember that because if we try to divide an integer by another integer, it will return an integer. That’s ok when the first number is perfectly divisable by the second, but if you expect it to return a fraction or decimal, you will be disappointed, since they are not whole numbers.

So:

>>>print 10 / 5
2

is easy to understand

but:

>>>print 3 / 2
1

behaves oddly.

We can use the modulus operand (the percent sign) to see what is going on:

>>>print 3 % 2
1

This tells us that there is a remainder of 1. So the two together tell us that when we divide 3 by 2, there is 1 in each group and one left over.

All we really need to know about integers is that they are whole numbers and return whole numbers when used in calculations.

The final type we will look at is the floating point number, or simply float, which is a decimal number.

To set a variable as a float, we just use a decimal point – even if it is a whole number. Like this:

>>>number1 = 3.0
>>>number2 = 2.0
>>>print number1 / number2
1.5

Now we get the result we were probably expecting when we divided 3 by 2 before.

If we forget what type of variable we have set, we can use a built-in function, like this:

>>>type(number1)

This tells us it is a float. Try it yourself on variable of other types.

That’s all for this tutorial. In the next one we look at functions.

Related posts: Python Fu GIMP Tutorials

Python-fu Gimp Tutorial #1: Variables, simple math and types

Python Fu #8: Conditional statements, modifying the Lomo plug-in

See the other tutorials in this series.

In this tutorial we will be modifying our last plug-in. All of the code is the same until we get to setting the direction that the blend tool runs in. We will offer the user the option to have the blend tool come in from the top, left, bottom and right, as well as from the four corners, and stop in the middle of the image. To do this we will give the options in a drop-down menu, using compass directions to describe where the blend begins. The code for the tutorial can be found here: lomo2.py.


def lomo_opt(image, drawable, direction):
...
register(
    "python-fu-lomo-opt",
    "Lomo effect opt",
    "Creates a lomo effect on a given image and user input",
    "Jackson Bates", "Jackson Bates", "2015",
    "Lomo opt...",
    "RGB", # type of image it works on (*, RGB, RGB*, RGBA etc...)
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None),
        (PF_OPTION, "direction", "Direction: ", 0,
             (
                 ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
              )
         )
    ],
    [],
    lomo_opt, menu="/Filters")  # second item is menu location

main()

The first thing we will do is change the registry and function name in the appropriate spaces. You’ll notice I have added an argument to the function for the ‘direction.’ I have also added the parameters in the registry section that will provide users with an option to select a direction. The metaphor that made sense to me was that of the compass, so I allow the user to pick one of 8 compass points as the starting point for the blend effect, and keep the centre as the end point as in the previous version of the plug in.

The tricky bit is getting the plug in to alter the values that we pass into the pdb.gimp_edit_blend method we used before. We basically want it to follow the logic: if the user picked N, blend down from the top center, if the user picked NE blend down from the top right corner…and so on.

Now there are probably a few solutions to this – and I suspect I picked the slowest, hackiest way to do it – but the final plug in works, so I don’t care!

The first thing I did was create a variable for each of the collections of gimp_edit_blend arguments that would be possible. I repeat my self a lot in this code – breaking the ‘don’t repeat yourself’ rule – but I’m not perfect. A pro would find a cleaner way to do this, I am certain.

I know what arguments gimp_edit_blend takes (layer, blend mode, paint_mode etc…) so I create a variable to contain each of those sets of values, labelled for each compass point (N, NE, E etc…).

Since the only part that is changing is the starting point of the blend, I only need to alter the 13th and 14th arguments, which specifiy the x and y axes respectively. The final two arguments specify the end point, but since this is always the centre it will always be layer.width / 2 and layer.height / 2.

It can be tricky to keep the proper pixel positions in your head, so to make it easier for my self I put together a quick reference chart. This should help me keep track of the values I need to put in.

pixel-position ref

So I go ahead and make the appropriate edits:


# arguments for blend, including directions
    # backslash character is used to allow short line lengths
    n = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        layer.width / 2, 0, layer.width / 2, layer.height / 2
    
    ne = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         layer.width, 0, layer.width / 2, layer.height / 2
    
    e = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        layer.width, layer.height / 2, layer.width / 2, layer.height / 2
    
    se = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         layer.width, layer.height, layer.width / 2, layer.height / 2
    
    s = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        layer.width / 2, layer.height, layer.width / 2, layer.height / 2
    
    sw = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         0, layer.height, layer.width / 2, layer.height / 2
    
    w = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        0, layer.height / 2, layer.width / 2, layer.height / 2
    
    nw = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         0, 0, layer.width / 2, layer.height / 2

And there we have the variables containing the arguments we pass into the gimp_edit_blend. It is not pretty.

The next trick is one of the core concepts of programming that you will find useful time and time again: conditional statements.

We’ll look at a simple example in the console:

We will create 3 possible conditions and provide different responses based on which condition is met.
We’ll create it as a function so we can test all three outcomes easily.
We create the function with using the def command:

>>>def greater(a, b):

and I say it will take two arguments, a and b. Then we have to remember to indent – I’ll use two spaces each time I indent. I want my function to check which value is greater, and print a message accordingly.


...  if a > b:
...    print "A is greater"
...  elif b > a:
...    print "B is greater"
...  else:
...    print "same same, but different"

So this should be pretty readable, but I’ll explain it. the first line checks the condition, “If a is greater than b” and the next line provides the action to take if the condition is true. The remaining lines follow the same principle, but the language is slightly different. Elif means ‘else if’, so if the first condition is not met, it tries the next one. The final one, else, is for when all other conditions fail. You can have as many elifs as you like in the middle, which is handy, because we have 8 conditions for the plugin.

So we can test the function by calling it and passing in the appropriate values:

>>>greater(2,1)
A is greater
>>>greater(1,2)
B is greater
>>>greater(2,2)
same, same, but different

We will use that principle for the final part of our plug in.

The condition we are checking is the value of the ‘direction’ variable that is selected by the user. We can find those in the PF_OPTION information in the registry section. The values are stored as strings in a list, but we will be referring to them by their position in the list. So instead of ‘if direction == “N” we would write if direction == 0, because it is the first item in the list, and computers start counting at zero.

So each conditonal statement will look like this:


if direction == 0:
  pdb.gimp_edit_blend(*n)

The asterix is required because for some reason I could not fathom, simply passing in the variable led to the wrong number of parameters. I did some googling and found that the asterix allows a flexible number of values in the variable. Like I said previously, this was a hacky solution, and probably not best practice, but it worked.

If I fill in the rest of the conditions now, you should see how it all comes together.


# the number used denotes the list position of the direction chosen
    if direction == 0:
        pdb.gimp_edit_blend(*n)
    elif direction == 1:
        pdb.gimp_edit_blend(*ne)
    elif direction == 2:
        pdb.gimp_edit_blend(*e)
    elif direction == 3:
        pdb.gimp_edit_blend(*se)
    elif direction == 4:
        pdb.gimp_edit_blend(*s)
    elif direction == 5:
        pdb.gimp_edit_blend(*sw)
    elif direction == 6:
        pdb.gimp_edit_blend(*w)
    else:
        pdb.gimp_edit_blend(*nw)

Finally we save it and check that our plug in has worked.

And of course it does, so we are finished. I hope you’ve found this series helpful. It should give you enough of an idea to be able to create your own original plug ins. It won’t always be easy, but you should get there with some detective work and learning to ask the right questions on forums.

Related posts: Python Fu GIMP tutorials

Here’s the completed code for the tutorial:


#!/usr/bin/env python

# Tutorial available at: https://www.youtube.com/watch?v=oNn9D_8d4zQ
# Feedback welcome: jacksonbates@hotmail.com

from gimpfu import *


def lomo_opt(image, drawable, direction):
    pdb.gimp_image_undo_group_start(image)

    #adjust curves on RG and B channels
    s_curve = (0, 0, 96, 64, 128, 128, 160, 192, 255, 255)
    inverted_s_curve = (0, 0, 64, 96, 128, 128, 192, 160, 255, 255)
    num_points = 10
    pdb.gimp_curves_spline(drawable, HISTOGRAM_RED, num_points, s_curve)
    pdb.gimp_curves_spline(drawable, HISTOGRAM_GREEN, num_points, s_curve)
    pdb.gimp_curves_spline(drawable, HISTOGRAM_BLUE, num_points,
                           inverted_s_curve)

    #add new layer & Set to 'overlay'
    opacity_100 = 100
    layer = pdb.gimp_layer_new(image, image.width, image.height, RGB_IMAGE,
                               "Overlay", opacity_100, OVERLAY_MODE)
    layer_position = 0
    pdb.gimp_image_insert_layer(image, layer, None, layer_position)
    
    # blend argument variables
    blend_mode = 0
    paint_mode = 0
    gradient_type = 0
    offset = 0
    repeat = 0
    reverse = False
    supersample = False
    max_depth = 1
    threshold = 0
    dither = True
    
    # arguments for blend, including directions
    # backslash character is used to allow short line lengths
    n = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        layer.width / 2, 0, layer.width / 2, layer.height / 2
    
    ne = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         layer.width, 0, layer.width / 2, layer.height / 2
    
    e = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        layer.width, layer.height / 2, layer.width / 2, layer.height / 2
    
    se = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         layer.width, layer.height, layer.width / 2, layer.height / 2
    
    s = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        layer.width / 2, layer.height, layer.width / 2, layer.height / 2
    
    sw = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         0, layer.height, layer.width / 2, layer.height / 2
    
    w = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
        repeat, reverse, supersample,  max_depth, threshold,  dither, \
        0, layer.height / 2, layer.width / 2, layer.height / 2
    
    nw = layer, blend_mode, paint_mode, gradient_type, opacity_100, offset, \
         repeat, reverse, supersample,  max_depth, threshold,  dither, \
         0, 0, layer.width / 2, layer.height / 2
    
    # blend tool, linear gradient (to add user entry,
    # based on diagonal desired for gradient direction?)
    # the number used denotes the list position of the direction chosen
    if direction == 0:
        pdb.gimp_edit_blend(*n)
    elif direction == 1:
        pdb.gimp_edit_blend(*ne)
    elif direction == 2:
        pdb.gimp_edit_blend(*e)
    elif direction == 3:
        pdb.gimp_edit_blend(*se)
    elif direction == 4:
        pdb.gimp_edit_blend(*s)
    elif direction == 5:
        pdb.gimp_edit_blend(*sw)
    elif direction == 6:
        pdb.gimp_edit_blend(*w)
    else:
        pdb.gimp_edit_blend(*nw)
        
    #merge all layers, and end undo group
    layer = pdb.gimp_image_merge_visible_layers(image, 0)
    pdb.gimp_image_undo_group_end(image)
    

register(
    "python-fu-lomo-opt",
    "Lomo effect opt",
    "Creates a lomo effect on a given image and user input",
    "Jackson Bates", "Jackson Bates", "2015",
    "Lomo opt...",
    "RGB", # type of image it works on (*, RGB, RGB*, RGBA etc...)
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None),
        (PF_OPTION, "direction", "Direction: ", 0,
             (
                 ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
              )
         )
    ],
    [],
    lomo_opt, menu="/Filters")  # second item is menu location

main()
Python Fu #8: Conditional statements, modifying the Lomo plug-in

Python Fu #7: More complicated plug-ins, recreating the Lomo effect

See the other tutorials in this series.

For our 7th Python-Fu tutorial we are going to make a more complicated plug-on. I made a tutorial for the Lomo effect 4 years ago, and we’re going to automate that. It’s a good idea for you to watch that tutorial first, and maybe even attempt to manually recreate the effect, before we script it. This will give you a good sense of how we will create our program. The code for this tutorial can be found here: lomo.py.

And here is how we automate that:

The first thing I will do is figure out the steps I need to recreate and then write each of those as comments in the main function.

# open curves, select red channel and create an 's' curve
# 's' curve green channel
# 'inverted 's' curve blue channel
# add new layer
# set layer to 'overlay'
# blend tool, using linear gradient
# merge layers

And I also know I will want to start and end an undo group for the image, so I’ll put that in comments too, and then code it straight away.

# start undo group
pdb.gimp_image_undo_group_start(image)
# end undo group
pdb.gimp_image_undo_group_start(image)

Now, when I first started making this I didn’t know how to do any of this, so I had to investigate the PDB and test some ideas. It took a little while, so don’t be put off if you find what you though would work doesn’t the first time. Just go back and read the documentation, google some key phrases, and eventually you’ll figure it out.

So, lets look at curves first. I check the PDB and see that there are two entries for curves, and they both look pretty similar.

explicit curves

spline

Both take a drawable layer and a channel, so that’s good, since I know I need to be able to pick the channel. But the next two options for both are confusing. The explicit function asks for a number of bytes, whatever that means, tells me it’s always 256, and has to be greater than or equal to 0. Then the curve is an ARRAY, which is another name for a list, and it just tells me it’s the explicit curve. To be honest – I don’t even know how to start understanding those options! So I take a look at spline, and even though the word spline is more off-putting, the arguments are more recognisable to me. The number of points is something between 4 and 34 – so worst case scenario, I can just try every number in between if I need to…(don’t worry, I didn’t actually need to do that – it took a little thinking and just 3 attempts…). Also, the control points also accepts an ARRAY, or a list, but the description is more informative. The list seems to take values labelled in numbers that increase and correspond to the x and y axes. So I still didn’t really know what to do, but had mroe to go on – so I’ll try spline.

I copied the procedure into my main function and then opened up curves again to see if I can get the coordinates.


pdb.gimp_curves_spline(drawable, HISTOGRAM_RED, num_points, s_curve)
pdb.gimp_curves_spline(drawable, HISTOGRAM_GREEN, num_points, s_curve)

curves

Sure enough, the graph for the curve reports the coordinates when I move around. So lets write down the main ones and see if we can use them. My first point is roughly 95 – but I can be more precise if I think about it. The maximum coordinate values are 255, and the first coordinate on each axis is 0 – so there are really 256 points. There are also 8 grid boxes up and across. Each grid line represents an 8th of 256, or 32 points. Since I want to ‘go in’ 3 boxes the real value I want is 96, or 3 x 32. So I go pretty close by eye-balling it, ut knowing the principal means I can do the rest without even looking at the grid again! Work smart, not hard! So the coordinates I use in are 96, 64 for the first point and 160, 192 for the second.

pdb.gimp_curves_spline(drawable, HISTOGRAM_RED, num_points, (0, 0, 96, 64, 128, 128, 160, 192, 255, 255))

Now, to speed things up – here’s a spolier. If I use these 4 control points it does have an effect, but not the one I expected.

So I thought about it and realised I could give more points – I knew both the start point of the line, 0, 0, and the end point 255, 255 and I can also see that it goes throught the centre-point 128, 128.

So I put those values in, too, and that didn’t work initially, with the number of points set to 4, but when I set it to 10, it matched my expectation perfectly. How did I get the magic number 10? Another hunch – the list for the curve was 10 values long. I don;t know if that is the real reason, but it worked!

pdb.gimp_curves_spline(drawable, HISTOGRAM_RED, 10, (0, 0, 96, 64, 128, 128, 160, 192, 255, 255))

Now, you’ll notice that both the red and green channels use the same curve. A good principal in programming is ‘Don’t Repeat Yourself’. The way I avoid this is by creating a tuple called s_curve and typing the coordinates into that. I can then just put the name of the tuple in instead of those numbers. Another good rule is to avoid what we call magic numbers in your code. Numbers could mean anything, and replacing them with meaningful variable names is cheap. Using variable names may seem redundant, but it makes your code more readable and it will be easier for you to read it days or months later and still know what is happening. So I will create a variable called num_points and set that to 10. I can set the channel either using the numbers in the PDB, or the text label. Obviously to make it as readable as possible, I’ll use the text. The only other thing is that the blue channel has an inverted S curve, so I’ll write the tuple for that and then put it in the code…


    s_curve = (0, 0, 96, 64, 128, 128, 160, 192, 255, 255)
    inverted_s_curve = (0, 0, 64, 96, 128, 128, 192, 160, 255, 255)
    num_points = 10
    pdb.gimp_curves_spline(drawable, HISTOGRAM_RED, num_points, s_curve)
    pdb.gimp_curves_spline(drawable, HISTOGRAM_GREEN, num_points, s_curve)
    pdb.gimp_curves_spline(drawable, HISTOGRAM_BLUE, num_points,
                           inverted_s_curve)

So, I’m not showing you that I’m testing this every time I add a line, but I did this painstakingly when actually developing this script. It’s slow, as I said before, but it makes it easier to debug.

The curves are taken care of, so now we add a layer. The PDB says we can create a variable called layer, with all the appropriate settings, but that doesn’t actually put the layer in the image – it just holds it in memory until we do something with it.

The layer arguments are: image, which is the one we’re working on, width and height can be accessed using the image.width and image.height variables we discussed in tutorial four, type of layer (as in RGB or whatever), name, which is just a label, opacity, and mode. This last one is very encouraging because setting the mode was a separate step in our comment version of the code, so we’ve probably just saved a line of code. To avoid magic numbers in my code I will create a variable for the opacity. This is all straightforward enough, so I put in the values I know I need:


#add new layer & Set to 'overlay'
opacity_100 = 100
layer = pdb.gimp_layer_new(image, image.width, image.height, RGB_IMAGE,
                               "Overlay", opacity_100, OVERLAY_MODE)

Actually, putting the layer in the image took some trouble, because some of the differences between Scheme and Python aren’t that well documented. A little digging in the PDB shows that to put the layer in the image we use gimp.image_insert_layer and there are some complicated instructions in the description. It talks about ‘parent layers’ and layers being in groups. It also says ‘if the parent is 0…’ and this stumped me. I figured I didn’t have a parent group, since I hadn’t made one but the value 0 didn’t work. After half an hour of Googling and reading some forums I found that someone else figure out that Python wants that value to say None instead of 0 – as soon as I put that it, it worked. Again – this will happen to you occassionally when you start trying to make your own plug-ins. Just keep at it and you’ll find the solution. The position is straightforward – I want my new layer to be the first in the list, so the position will be 0 – a variable will take care of the magic number.


layer_position = 0
pdb.gimp_image_insert_layer(image, layer, None, layer_position)

Finally, I need to use the blend tool. The PDB tells us we can do this and gives us a huge range of arguments! To be honest – I guessed at these and got it right first time, which I was glad for since the new layer took ages.

I figured blend mode was FG_BG_RGB. Paint mode was normal. The gradient is definitely linear and opacity is definitely 100. Offset I guessed and stuck with 0, repeat I also guessed and stuck with 0, reverse I guessed was false, supersample I guessed was false, I guessed max depth was 1 and threshold was 0, and I saw on the blend tool options in the toolbox UI that dither was set to true. The coordinates were easy, but also presented a different challenge. In the original tutorial, I experiment with this until I like the way it looks. But, if I’m going to automate it I can only really make it do the same thing every time, or randomise it completely. There are actually other options, but those two are the easiest. In the next video, we’ll give the user a little more choice, but for now we’ll make it easy.

I will simply make the blend line go from the top right hand corner to the center of the image – that should be sufficient. To get the top right corner I know the x-axis for the first point is 544, and that the y-axis is 0 (because the coordinates for images are flipped on the y-axis). Well, what happens when I use a different sized image? that first value is wrong. So instead of using the value of my test image, I can use the generic image.width variable which will always be correct!

Similarly, to find the center point I just divide the height by 2 for the x axis, and divide the width by 2 for the y axis. image.height / 2 works perfectly for this.

So I put all of that in and test it.


# blend arguments and call to function
blend_mode = 0
paint_mode = 0
gradient_type = 0
offset = 0
repeat = 0
reverse = False
supersample = False
max_depth = 1
threshold = 0
dither = True
x1 = layer.width # top-right
y1 = 0
x2 = layer.width / 2 # centre
y2 = layer.height / 2
pdb.gimp_edit_blend(layer, blend_mode, paint_mode, gradient_type,
                    opacity_100, offset, repeat, reverse, supersample,
                    max_depth, threshold,  dither, x1, y1, x2, y2)

The final step is to merge the visible layers, which can be achieved with this PDB command:

layer = pdb.gimp_image_merge_visible_layers(image, 0)

It took a lot of trial and error, but I hope me explaining my thinking as I went helps you to investigate this kind of thing for yourself.

Related posts: Python Fu GIMP tutorials

Here’s all that code:


#!/usr/bin/env python

# Tutorial available at: https://www.youtube.com/watch?v=X0_a6U6PkCA
# Feedback welcome: jacksonbates@hotmail.com

from gimpfu import *


def lomo(image, drawable):
    pdb.gimp_image_undo_group_start(image)
    s_curve = (0, 0, 96, 64, 128, 128, 160, 192, 255, 255)
    inverted_s_curve = (0, 0, 64, 96, 128, 128, 192, 160, 255, 255)
    num_points = 10
    pdb.gimp_curves_spline(drawable, HISTOGRAM_RED, num_points, s_curve)
    pdb.gimp_curves_spline(drawable, HISTOGRAM_GREEN, num_points, s_curve)
    pdb.gimp_curves_spline(drawable, HISTOGRAM_BLUE, num_points,
                           inverted_s_curve)
    #add new layer & Set to 'overlay'
    opacity_100 = 100
    layer = pdb.gimp_layer_new(image, image.width, image.height, RGB_IMAGE,
                               "Overlay", opacity_100, OVERLAY_MODE)
    layer_position = 0
    pdb.gimp_image_insert_layer(image, layer, None, layer_position)

    # blend arguments and call to function
    blend_mode = 0
    paint_mode = 0
    gradient_type = 0
    offset = 0
    repeat = 0
    reverse = False
    supersample = False
    max_depth = 1
    threshold = 0
    dither = True
    x1 = layer.width # top-right
    y1 = 0
    x2 = layer.width / 2 # centre
    y2 = layer.height / 2
    pdb.gimp_edit_blend(layer, blend_mode, paint_mode, gradient_type,
                        opacity_100, offset, repeat, reverse, supersample,
                        max_depth, threshold,  dither, x1, y1, x2, y2)
    #merge all layers
    layer = pdb.gimp_image_merge_visible_layers(image, 0)
    #pdb.gimp_displays_flush()
    pdb.gimp_image_undo_group_end(image)
    

register(
    "python-fu-lomo",
    "Lomo effect",
    "Creates a lomo effect on a given image",
    "Jackson Bates", "Jackson Bates", "2015",
    "Lomo",
    "RGB", # type of image it works on (*, RGB, RGB*, RGBA etc...)
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None)
    ],
    [],
    lomo, menu="/Filters")  # second item is menu location

main()
Python Fu #7: More complicated plug-ins, recreating the Lomo effect

Python Fu #6: Accepting user input

See the other tutorials in this series.

In this tutorial we will take the plug-in we made from the last tutorial, and allow the user to pass their own parameters into the function. The code for this tutorial can be found at: UI Examples and extreme_unsharp_desaturate_options.py.

The first thing I’ll do is change the name and register arguments for this script.


def extreme_unsharp_desaturation_options(image, drawable):
    # function code here..
    

register(
    "python-fu-extreme-unsharp-desaturation-options",
    "Unsharp mask and desaurate image, with options",
    "Run an unsharp mask with variables set by user",
    "Jackson Bates", "Jackson Bates", "2015",
    "Extreme unsharp and desaturate options...",
    "RGB",
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None),
        (PF_SLIDER, "radius", "Radius", 5, (0, 500, 0.5)),
        # note extra tuple (min, max, step)
        (PF_SLIDER, "amount", "Amount", 5.0, (0, 10, 0.1)),
        (PF_RADIO, "mode", "Set Desauration mode: ", DESATURATE_LIGHTNESS,
            (
                 ("Lightness", DESATURATE_LIGHTNESS),
                 ( "Luminosity", DESATURATE_LUMINOSITY),
                 ("Average", DESATURATE_AVERAGE)
            )
         )
    ],
    [],
    extreme_unsharp_desaturation_options, menu="/Filters/Enhance")

Now I need to decide what options I’ll let the user change. For the unsharp mask, I will let them change the radius and amount variables, and for the desauration I’ll let them choose the mode.

The first thing I need to do is easy enough. I just need to add the arguments to the main function so that the plug-in is expecting them.

To do this simply add sensible variable names to the arguments for the function – radius, amout, and mode. Remember to comma seperate them.

def extreme_unsharp_desaturation_options(image, drawable, radius, amount, mode):

Then I go down to the parameters section of the register arguments and add those three variables as GUI inputs the user can play with. The great thing about gimpfu is that it comes with it’s own GUI library that is set up to work with minimal fuss by the developer. Simply throw in a few details and it will build the GUI for you.

We will add 3 user input options, one for each variable. To decide which ones we’ll use, we should consult the documentation: http://www.gimp.org/docs/python/

Scroll down to the section called Plug-in Framework and you’ll see a whole bunch of options beginning PF_.

Each parameter is held in something that is a little bit like a list. If you remember from the last tutorial, a list is a collection of values, all assigned to the same label, and they are set by square brackets. Well, if you look closely at the parameters, they are nested within square brackets – and there is another type of grouping within that list. The first item in the parameter list is the collection in parentheses from PF_Image through to None, then there is a comma, telling us that we are moving on to item two in the list…But each of the things in parentheses looks like a list, too. Well it sort of is. These collections in parentheses are called tuples, and they are used in slightly different ways. We don’t need to get bogged down in that for now, but all we need to know is that the parameters argument of the register function accepts a list of tuples. That sounds more complicated than it really is to a beginner, so just copy copy what I do for now.

Firstly, I need to take a value from the user for radius. Looking at the documentation, and remembering what I know from the PDB entry, I shouldn’t pick the INT options, because radius is supposed to be a float. There is a FLOAT option, and if you are familiar with the different user interface options in GIMP, you might be able to picture what that one looks like. There are some others you might be able to recall as well, like PF_SLIDER.

There is a dummy script called pyui.py, created by Akanna Peck which demonstrates what each parameter looks like in a GUI and you can see what kinds of arguments they need. You can get a copy here: http://gimpbook.com/scripting/

However Peck’s script doesn’t fit on my screen, and you might find the labelling of each parameter a little confusing as a beginner. So I have made two smaller scripts that show half of the parameters each, and I’ve labelled them a little clearer. Also, the variable names I’ve picked and a few other details are a little clearer for absolute beginners, so if you find those helpful you can download those from the Github Gist I’ve saved them at. Let’s take a look at mine to see what each parameter looks like and to pick the most suitable one for the radius and amount.

Well, looking at the first half of parameters, it looks like PF_FLOAT will be the best option – the user can type a float in easily. So that might be the best choice. Looking at the second set, though, we have some other options. Firstly, spinner and adjustment do the same thing, so that means One of those or slider look promising too. Lets take a look at the code for all three to further evaluate which one will best suit our needs.

So Float takes the following settings: the variable name which is called by the main function (in my script this is called float_var), the label for the GUI (my script just calls this float), then a default value.

Both Spinner and Slider have the same first three items, variable, label, default, but then there is a final setting which is another one of those tuples we introduced earlier. This tuple has three elements to it: a minimum value, a maximum value, and a step value. This means for these two we can set maximum values so the user can’t accidentally enter a number out of range and crash the script. The original unsharp mask plug in has a maximum radius of 500 and a maximum amout of 10. Setting these as our maximums will save the user from problems, making either of these a better choice than pf_float.

After that, it’s just a usability choice, and the step doesn’t matter much beyond picking something reasonable. The step value describes how much the number will increase with each click of the spinner or movement of the slider. Experiment with it until you find values that suit your needs.

So I will pick a slider for radius and amount, since those are the ones on the original unsharp mask. Pay attention to all parentheses and commas!

(PF_SLIDER, "radius", "Radius", 5, (0, 500, 0.5)),
(PF_SLIDER, "amount", "Amount", 5.0, (0, 10, 0.1)),

For desatuaration mode, I’ll use the same method that the GIMP developers picked there, too. So I’ll set it as a radio option.

Now, I can’t pull these code snippets from the PDB, so we have to remember them, keep a note of them somewhere or refer to sample scripts like the ones Akanna Peck and I have made.

Notice that RADIO keeps the value and label for each radio button in it’s own tuple…you can have as many tuples as you need for this, and to confuse matters this group of tuples is also in a tuple! To make mine more readable, I put each separate tuple on a new line. And remember to convert dashes to underscores, and count all the opening and closing parentheses very carefully, and include commas in all the right places. If you have bugs, this is most likely where you will find them, assuming there are no typos in function names or variables.

(PF_RADIO, "mode", "Set Desauration mode: ", DESATURATE_LIGHTNESS,
            (
                 ("Lightness", DESATURATE_LIGHTNESS),
                 ( "Luminosity", DESATURATE_LUMINOSITY),
                 ("Average", DESATURATE_AVERAGE)
            )
         )

So that’s how we take some user input. It’s pretty straightforward once you do it a few times, I promise.

Related posts: Python Fu GIMP tutorials

Here’s the code for the completed plug-in.


#!/usr/bin/env python

# Tutorial available at: https://www.youtube.com/watch?v=5Ld8Todog5s
# Feedback welcome: jacksonbates@hotmail.com

from gimpfu import *

def extreme_unsharp_desaturation_options(image, drawable, radius, amount, mode):
    pdb.gimp_image_undo_group_start(image)
    threshold = 0
    pdb.plug_in_unsharp_mask(image, drawable, radius, amount, threshold) 
    pdb.gimp_desaturate_full(drawable, mode)
    pdb.gimp_image_undo_group_end(image)
    

register(
    "python-fu-extreme-unsharp-desaturation-options",
    "Unsharp mask and desaurate image, with options",
    "Run an unsharp mask with variables set by user",
    "Jackson Bates", "Jackson Bates", "2015",
    "Extreme unsharp and desaturate options...",
    "RGB",
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None),
        (PF_SLIDER, "radius", "Radius", 5, (0, 500, 0.5)),
        # note extra tuple (min, max, step)
        (PF_SLIDER, "amount", "Amount", 5.0, (0, 10, 0.1)),
        (PF_RADIO, "mode", "Set Desauration mode: ", DESATURATE_LIGHTNESS,
            (
                 ("Lightness", DESATURATE_LIGHTNESS),
                 ( "Luminosity", DESATURATE_LUMINOSITY),
                 ("Average", DESATURATE_AVERAGE)
            )
         )
    ],
    [],
    extreme_unsharp_desaturation_options, menu="/Filters/Enhance")

main()
Python Fu #6: Accepting user input

Python Fu #5: Automating Workflows, coding a complete plug-in

See the other tutorials in this series.

If you haven’t been following this series and any of this doesn’t make sense, go back and watch the earlier videos. In this tutorial we create a plug-in that performs two GIMP effects in quick succession, so that it can be called from the menus like every other plug-in or filter. It’s not a particularly desireable effect that we are making, but it will teach us the basics.

Now what we are doing here is automating a workflow. This is what programming is: You break down the task you want to acheive into the smallest steps you can think of, and then perform those steps in the correct order. The fancy name for the end product is an algorithm – but all it really means is the steps the program takes. We are going to use a process called incremental development for all of our scripts which means every time we add a step to our algorithm, we test it. We keep an eye on the error console and check that it has the effect we expected. If it works, great! If not, we debug that line until it does what we want it to. It feels slow doing it incrementally, but it is much slower to write what you think is the whole script and then run it and have no clue which line caused the error. If you progress by one tested step at a time, you will know exactly which line is causing problems, and should be able to debug much quicker. In the early days – maybe for months – you will find lots of errors as you learn how Python wants to recive the information. It’s a slow process but you’ll learn a ton about programming – and remember everything can be Googled, asked about on a GIMP forum, or on Reddit’s ‘Learn Python’ subreddit, or on StackOverflow – a programmer’s Q&A site.

Anyway, lets make the plug-in! The code for the completed plug-in can be found here: extreme_unsharp_desaturation.py

So what I want this to do is run the unsharp mask filter in a very distinctive way, and then make the image black and white.

I’ll set up the register for the plug-in first by editing my template and saving it with the appropriate name.


def extreme_unsharp_desaturation(image, drawable):
# Function code goes here

register(
    "python-fu-extreme-unsharp-desaturation",
    "Unsharp mask and desaurate image",
    "Run an unsharp mask with amount set to 5, then desaurate image",
    "Jackson Bates", "Jackson Bates", "2015",
    "Extreme unsharp and desaturate",
    "RGB", 
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None)
    ],
    [],
    extreme_unsharp_desaturation, menu="/Filters/Enhance")

main()

What I do next is manually do the task myself and write down in the script file in comments what I am doing, taking note of all of the parameters I use.

So what I do for this effect is run the Unsharp mask from the filters menu.
Set the radius to 5.0, the amount to 5.0 and the threshold to 0 (even if I don’t change the settings, I write down the defaults…we might need them.)
Then I run the desaturate command and set the option to Lightness.

And that’s it! I write that in my code as comments and then add each of the functions under each comment. This means I am commenting my code as I go. If I come back to this months from now and can’t remember what I did, the comments are there to remind me.


# Unsharp mask from filters menu
# Radius = 5.0
# Amount = 5.0
# Threshold = 0
# Desaturate, option = Lightness

Now I open up the PDB and start getting the actual commands I need, one at a time.
Firstly I look up the unsharp mask. Now I can see there are two, so I need to do some digging. I go back to the one I used and hover over it. The description tells me it’s the most widely used one, and sure enough, the plug-in version tells me the same thing. So I double click that to snatch the code I can use, and then I read the information in the PDB about the arguments it takes.

Now, here’s a gotcha that used to catch me out. The first argument – run-mode – is not needed. It is there for people that run Gimp procedures from the command-line, you and I might also refer to them as wizards, but the clue that we regular folk don’t need it is that the next two arguments are image and drawable – those are the two that all plug-ins that work on an existing image always begin with. The important info we learn from the PDB is that the radius and amount are FLOATS, while the threshold is an integer. You paid attention in the very first video, so you know what that means!

Now we can either put that in as it is and set the variables in our function above it, or we can hardcode the values into the arguments. Essentially, when you make the choice for yourself, make it as readable as possible.

So image and drawable can stay the same. They are actually being supplied in the parameters below in the register function, PF_Image and PF_Drawable, so GIMP knows what those variables mean. But the other three aren’t established anywhere so I’ll create variables to hold the values to make it easy to understand.


# radius = 5.0
# amount = 5.0
# threshold = 0
pdb.plug_in_unsharp_mask(image, drawable, radius, amount, threshold)

I’ll save that and run it to check that it worked. If it doesn’t you need to go back and debug it. If your code looks the same as mine, but it doesn’t run check all spellings and punctuation very carefully. A misplaced comma is all it takes to grind everything to a halt – so be very precise and careful. I spent an hour looking for a comma the other day…it happens.

So now that that works, we go through the same process for the next step. Look up desaturate, notice that there are two, but one lets us set the mode to lightness, so pick that one. We can either write in the value or the number that represents the mode. I’ll use the name, since it’s more readable. Remember that Python wants you to convert dashes to underscores! There’s another bug you just avoided!

pdb.gimp_desaturate_full(drawable, DESATURATE_LIGHTNESS)

Now if we go back to the image and try to undo that, you notice that I can only undo each step at a time. This would be annoying for you if there were more steps – we’d rather just be able to undo the effect of the plug-in in one go, right?

So the way we do that is by creating an undo group. You’ll want to do this all the time. Look up undo in the PDB and you’ll see there is an undo group start and end function. So we’ll stick the start on at the top of the function and the end at the end. These only take the image as an argument, and since that gets passed in already, save one last time and then test the undo functionality.


pdb.gimp_image_undo_group_start(image)
...
pdb.gimp_image_undo_group_end(image)

It works!

So now you have made your first useful GIMP plug in. Using these principals alone, and some careful think and detective work, you can pretty much make anything now. We haven’t looked at getting new inputs from the user yet, so we’ll do that in the next video.

Related Posts: Python Fu GIMP tutorials 

Here’s the complete code:


#!/usr/bin/env python

# Tutorial available at: https://www.youtube.com/watch?v=uSt80abcmJs
# Feedback welcome: jacksonbates@hotmail.com

from gimpfu import *

def extreme_unsharp_desaturation(image, drawable):
    pdb.gimp_image_undo_group_start(image)
    radius = 5.0
    amount = 5.0
    threshold = 0
    pdb.plug_in_unsharp_mask(image, drawable, radius, amount, threshold)
    pdb.gimp_desaturate_full(drawable, DESATURATE_LIGHTNESS)
    pdb.gimp_image_undo_group_end(image)
    

register(
    "python-fu-extreme-unsharp-desaturation",
    "Unsharp mask and desaurate image",
    "Run an unsharp mask with amount set to 5, then desaurate image",
    "Jackson Bates", "Jackson Bates", "2015",
    "Extreme unsharp and desaturate",
    "RGB", 
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None)
    ],
    [],
    extreme_unsharp_desaturation, menu="/Filters/Enhance")

main()
Python Fu #5: Automating Workflows, coding a complete plug-in

Python Fu #4, Hello Warning

See the other tutorials in this series.

In the last three tutorials we looked at the very basics of Python, just variables and functions, and then we added a little more Python knowledge with the basics of modules and why that is relevant to the GIMP and the Procedure Database. If all of that is nonsense to you, you better go back and check the last few tutorials, because this next one won’t make much sense. The code explained in this tutorial can be downloaded here: registration template.py and hello warning.py.

In this tutorial we are going to write a scipt that simply prints ‘Hello, World!’ to the error console. This is more or less pointless, but it will teach us three things: How to register a script, where to find the error console, and how to test some of you assumptions using the error console – this last one is very helpful as you start to develop you own plug-ins and need to do some detective work to get them working.

You will also notice that for this tutorial I will be using a particular text editor. You could use Notepad on Windows, but getting a text editor designed for programmers is much more helpful because they highlight key words and can show you where brackets are closed. These features, and many others, can make it much easier to spot errors and debug your code. On Windows you could use Notepad++, which is very easy to use. If you download Python, it comes with it’s own text editor, called IDLE. I’ll be using IDLE for the rest of these tutorials.

The first thing we will look at is registering the script. This has to be done in every plug-in we write, so it can be helpful to produce yourself a little template that you can always refer to, like this one:


#!/usr/bin/env python

# Tutorial available at: https://www.youtube.com/watch?v=nmb-0KcgXzI
# Feedback welcome: jacksonbates@hotmail.com

from gimpfu import *

def NAME_OF_MAIN_FUNCTION(image, drawable):
    # function code goes here...
    

register(
    "python-fu-NAME-OF-MAIN-FUNCTION",
    "SHORT DESCRIPTION",
    "LONG DESCRIPTION",
    "Jackson Bates", "Jackson Bates", "2015",
    "NAME FOR MENU",
    "", # type of image it works on (*, RGB, RGB*, RGBA, GRAY etc...)
    [
        # basic parameters are: (UI_ELEMENT, "variable", "label", Default)
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None)
        # PF_SLIDER, SPINNER have an extra tuple (min, max, step)
        # PF_RADIO has an extra tuples within a tuple:
        # eg. (("radio_label", "radio_value), ...) for as many radio buttons
        # PF_OPTION has an extra tuple containing options in drop-down list
        # eg. ("opt1", "opt2", ...) for as many options
        # see ui_examples_1.py and ui_examples_2.py for live examples
    ],
    [],
    NAME_OF_MAIN_FUNCTION, menu="")  # second item is menu location

main()

It might look confusing to begin with, but there is nothing here, apart from the first line, that we shouldn’t already recognize.

#!/usr/bin/env python

The first line begins with a shebang, the !# symbol, and then a directory path. You don’t have to worry too much about this. It’s a UNIX thing that allows a script to run in the correct environment. You don’t always need it – but get in the habit of using it, in case you ever start programming more seriously. Then we have some commented out lines which we will ignore.

from gimpfu import *

The next line is an import statement that allows us access to the PDB, among other things.

def NAME_OF_MAIN_FUNCTION(image, drawable):

Next up we have our main function. The filename of the main function, and the later references to it should all match. More on that in a minute. You can see that I’ve already said this function has two arguments, or parameters that it needs: image and drawable. This type of function works on an existing image, and as such always needs to have these two arguments first. It can have others too, but these two go first and can be skipped. The image variable is obvious. The drawable refers to the active layer. GIMP thinks of images kind of like containers that can’t be directly ‘drawn’ on, that contain things that can be drawn on, drawables! In this case the drawable is the layer. Channels are drawables too, but we canignore that for now.

When we know what our function will do, obviously we fill the rest in.


register(
    "python-fu-NAME-OF-MAIN-FUNCTION",
    "SHORT DESCRIPTION",
    "LONG DESCRIPTION",
    "Jackson Bates", "Jackson Bates", "2015",
    "NAME FOR MENU",
    "", # type of image it works on (*, RGB, RGB*, RGBA, GRAY etc...)
    [
        # basic parameters are: (UI_ELEMENT, "variable", "label", Default)
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None)
        # PF_SLIDER, SPINNER have an extra tuple (min, max, step)
        # PF_RADIO has an extra tuples within a tuple:
        # eg. (("radio_label", "radio_value), ...) for as many radio buttons
        # PF_OPTION has an extra tuple containing options in drop-down list
        # eg. ("opt1", "opt2", ...) for as many options
        # see ui_examples_1.py and ui_examples_2.py for live examples
    ],
    [],
    NAME_OF_MAIN_FUNCTION, menu="")  # second item is menu location

Next we have another function called register. It may not look like a function because of the strange layout, but if you ignore all the whitespace and linebreaks, you can see that this is a function with lots of arguments. In Python we use whitespace like this to make our code more readable. Python ignores this kind of white space, so it doesn’t effect the way it runs.

The register function takes the following arguments or parameters:

  • The name of the script – which by convention begins python-fu, if written in Python (script-fu if written in Scheme)
  • A short description of what it does.
  • A longer description / help string.
  • The author’s name
  • Copyright info
  • Copyright year
  • The name to be printed in the menu
  • The image type
  • A list of parameters – I’ll explain a little more about lists below
  • Results – allegedly, I haven’t found this being used in the wild yet, I’ve always just seen it blank!
  • The name of the main plug in function
  • The menu location
main()

The final thing in our script here is a call to the main() function. This simply tell GIMP to run the main function. We need to do this because even though we defined a function earlier, we didn’t call upon it to actually run. More complicated scripts also have multiple functions defined in them, so call the main one is what triggers the plug-in. The main function might call upon those other functions depending on input.

So finally we need to add something to the function to make it do something.

def hello_warning(image, drawable):

We will change the name of the function to hello_warning(), and need to replace that in three places in the register arguments.

Next we will fire up the PDB to see what the Error console message function is:
pdb.gimp_message(message)

pdb.gimp_message("Hello, world!")

So we change the argument to the string of text we want. and save it. Now we need to close and reopen GIMP, because the first time we register a plug-in, GIMP needs to restart. From then on though, every time we save the script the changes flow through automatically.

Now that we’ve restarted GIMP, we need to get the error console up, which can be pulled from any tools dialog – I like to use the layer / channels / undo / paths docking area for mine. Hit the little triangle, add a tab, and select error console at the bottom.

Now open an image, because this plug in supposedly runs on an image, and then go to the menu location that you registered the plug-in for. In this case, simply ‘File.’

So when we run the plug in we get the “Hello, world!” message in the console.

So that’s pretty pointless, but we can actually make this quite useful for us by feeding a different argument into the message. For example, when we imported everything from gimpfu, we loaded lots of extra information that is useful for us to know.

Just like we can access math.pi, that is a variable called pi that belongs to the math module, we can also access things like image.name, the name variable belonging to the image. So replace the hello world argument in the message function with image.name – notice this is not a string, so no speech marks, it’s a specific variable I’m calling.

The error console helpfully tells us the name of the image.

We can also access things like image.width. Make the changes to you code and see what that does. This particular variable, and image.height is very useful – you’ll likely use it lots when you begin making your own plug-ins.

There is also a slightly more complicated thing we can access, image.layers. Now, my original image only has one layer, but let’s make another and see what happens when I ask for this information. So I’ll make a blank layer and call it Booyah! and then call for the message image.layers. Now the variables we have seen so far have all had a single value. But if we ask for this variable, it will have two values.

Python deals with this by creating a list. A list is like a variable in the way it is set up, but obviously it contains a list of items. We can access the whole list or just one at a time. The parameters we set up in the register earlier were in a list, and we can tell because they were put in square-brackets – this is how we set a list in Python. I’ll quickly show you that in the Python console:

>>>a = [1, 2, 3, 4, 5]

Here we create a list of integers, 1 through 5.

I can print the whole list:

>>>print a
[1, 2, 3, 4, 5]

Or I can print elements from the list:

>>>print a[0]
1

Notice that I ask for the Zeroth item to get what we think of as the first item! This is how computers count – they start at zero. If I want to retrieve the number 2 from the list I:

>>>print a[1]
2

It’s confusing to begin with, but you get used to it.

This principle is useful to know though, because if you have to work on a specific layer in the image list, you need to know how to access it and how to refer to it’s position.

Here is what the final hello warning script looks like:


#!/usr/bin/env python

# Tutorial available at: https://www.youtube.com/watch?v=nmb-0KcgXzI
# Feedback welcome: jacksonbates@hotmail.com

from gimpfu import *

def hello_warning(image, drawable):
    # function code goes here...
    pdb.gimp_message("Hello, world!")
    

register(
    "python-fu-hello-warning",
    "Hello world warning",
    "Prints 'Hello, world!' to the error console",
    "Jackson Bates", "Jackson Bates", "2015",
    "Hello warning",
    "", # type of image it works on (*, RGB, RGB*, RGBA, GRAY etc...)
    [
        (PF_IMAGE, "image", "takes current image", None),
        (PF_DRAWABLE, "drawable", "Input layer", None)
    ],
    [],
    hello_warning, menu="/File")  # second item is menu location

main()

Anyway, that’s a lot of detail for a simple hello world tutorial.

In the next tutorial we’ll make a more functional, but just as simple plug-in that actually edits a photo, which is what GIMP is for after all.

Related posts: Python Fu GIMP tutorials

Python Fu #4, Hello Warning

Python Fu #3: The Procedure Database

See the other tutorials in this series.

In the last two videos we looked at the basics of programming: what variables are, what functions are and how to pass variables into functions to change the output of the function. One of the things Python is great for is it’s range of extensive modules that extend the functionality of the language. We won’t look at how these modules are made or what’s in them in much detail, we just need to know how to refer to their functions. For example, in the console or shell we can:

>>>import math

Which allows us to access the Math module. We can use the math module to access some values and functions that make math easier.

Now that we have imported math we can use it to access the value of pi. To do this we use the name of the module, followed by a dot followed by the object we want to access in the module, in this case the variable pi.

>>>print math.pi
3.14159265359

We can use this to calculate the area of a circle for which we know the radius, using the formula pi (r)squared

>>>print math.pi * (5 * 5)
78.5398163397

So why do we need to know how to import modules? Well, all of the GIMP functionality we want access to from Python is stored in a group of modules collectively known as gimpfu.

To use these we write:

>>>from gimpfu import *

Which means it will import all of the relevant GIMP modules in one go for us to use in our script. Most Python programmers usually steer clear of a bulk import like this since they can cause issues you might not foresee, but most GIMP scripts use this format anyway, so we’ll stick with it.

So what does that give us access to? Well the easiest way to get a sense of what we can do now is to open up the Procedure Database. We can access the PDB two ways. If the Python console is open, we can press the browse button. Or we can find it in the regular GIMP menus under help, called the Procedure Browser. You may notice that opening it up from the Console is slightly different to opening it through the help menu. The title of the window changes. If we launch it from the Console it is called the ‘Python Procedure browser.’ This is actually the one we want to use for now, because it can give us some extra information that is helpful. More on that in a minute.

With that open we can see every function specific to GIMP that we can use in our scripts. There are roughly 1200 by default, and the more plug-ins you install, or write yourself, the more of these you will see. Finding the function you are after is easy with a little common-sense. Let’s say I want to find the function for desaturating an image. Simply type ‘desat’ in the search box and by default, two options will appear. You pick the one that is most suitable and you can use it in your script.

How do we use it? Well, we use the same dot notation we used for math.pi. If we opened up the Python Procedure Browser from the console, we can actually get the line of code we need very easily. Simply double click the procedure name and it will print a Pythonic version to the console. You will notice that it converts the dashes to underscores, which is the preffered way to write out function names in Python.

So if we double click gimp-desaturate-full, it will print the following in the console:

>>>pdb.gimp_desaturate_full(drawable, desaturate_mode)

If you remember from the last video, some functions have variables passed to them in parentheses. So the desaturation function has two variables it needs. We need to remember that if we use it in our script. We can look that up in the PDB for more information about the variables, most importantly, what TYPE of variable is expected. There is more help available online that can answer other questions we may have, but we’ll look at those once we start scripting real plug-ins for ourselves.

So that’s all we need from this video. The key things to remember are that Python uses modules to access some special variables and pre-made functions, and these are referenced in our scripts using dot notation. We also learned that we can launch the Procedure Database, otherwise known as the Procedure Browser, and if we do this from the Browse… button in the Python console, we can actually get correctly formatted Python functions to copy and paste.

In the next video we are going to look at an extremely simple example of a Python script.

Related posts: Python Fu GIMP tutorials

Python Fu #3: The Procedure Database