Sunday, January 21, 2007

Scripting Gimp

Last night I wrote a script-fu scheme script to automate some relatively simple stuff in Gimp. I'd put off trying to do this for a while, and even put up a request on guru.com to pay someone to do it.

You'd think I'd be able to do this fairly easily. In college we had to learn scheme for a class. I know gimp fairly well, and I've actually seen the handful of functions I wanted to call. It should just be a matter of getting a few things straight and that's it.

The problem is as follows. I hate scheme. Its confusing. I got the impression there was a lot of emporer's new clothes going on with it. There tended to be a higher quality and level of code with scheme projects, but I think that had a lot more to do with the people coding scheme rather than any advantage of the language itself. In fact, I find coding in scheme like swimming with a coat on. But maybe that's just me.

However, the only guru.com bid was for $200. The guy basically wanted to learn gimp's api on my dime. Personally, I was avoiding the exercise because of the time investment, but whatever. I'm a programming nerd. Once I got over the initial stuff, it wasn't so bad. On top of that, now I think I could do a lot quickly.

However, finding the right documentation quickly was difficult. Gimp has been around for a long time and several different scripting methods exist, so the documentation is fragmented and confusing. It turns out, the best place to start is on gimp's site (shocker).

http://docs.gimp.org/en/gimp-using-script-fu-tutorial.html

This will teach you a lot about scheme and writing scripts. Very, very, very useful.

I think the first question you have for yourself is which language should you use. Python, Perl, etc. seem like better options. However, there was some complication involved in the installation of those bindings. To be honest, I haven't figured out exactly what would be needed to use them. Because I was only calling a few simple functions, I decided to use scheme. I can deal with any language for a few functions.

For the person like me, who's been exposed to scheme before, I'll give the summary of the important things about the api that weren't clear before I started.
  • A "plugin" is defined by a main function. You can name the function whatever you want, but there's a naming scheme that gimp uses. You can read about it in the link above.
  • There are 2 types of functions. Those that operate globally (not a specific image). You can get the list of all images, or (more likely) create a new one. The other type is the image specific.
  • To write an image specific plugin, simply register an Image object as the first parameter and a Drawable object as the second. Gimp will provide those arguments.
  • All gimp functions return a list. ALL GIMP FUNCTIONS RETURN A LIST. Say it again. You need to wrap all returns in, at a minimum, '(car (gimp-some-funtion ...))'. This took me a little while.
  • One of the really cool things I didn't get was that those little dialogs that pop up for plugins aren't specifically coded. That's the part I thought would take a really long time. All you have to do is declare your variables. They show up in the dialog. I'm sure in certain situations this will be restrictive, but for the mast majority of situations, its much easier.
There's other stuff, but its redundant. For the most part, you can find it in the link above. I promise if you following along, you'll get it quick.

I'm including my code because I had a hard time finding simple examples on the web. All mine does is apply a border to an image, then apply a drop shadow. The idea is to dress up screen shots for documentation. Here's an example:

Its a screenshot of a little part of the gimp website. Functional, but hard to see what's really going on, where the shot starts and stops, etc. Well, it WOULD be if the screen background was white ;)

Here's the modified version:

Its a little easier to get your eyes around that one. The process? I applied a 1px gray border. Then I did a drop shadow. Pretty simple. However, the specific series of steps, while not terribly labor intensive, get annoying if you're doing a lot of them.

Here's the code...


(define (script-fu-kev-border-shadow
inImage
inDrawable
inBorderWidth
inBorderColor
inOffsetX
inOffsetY
inBlurRadius
inShadowColor
inShadowOpacity)

(let* ( (newLayer) )

(if (> (car (gimp-image-get-layers inImage)) 1)
(gimp-image-merge-visible-layers inImage 1)
)

(script-fu-addborder inImage (car (gimp-image-get-active-layer inImage)) inBorderWidth inBorderWidth inBorderColor 25)
(gimp-image-merge-down inImage (car (gimp-image-get-active-layer inImage)) 0)
(script-fu-drop-shadow
inImage
(car (gimp-image-get-active-layer inImage))
inOffsetX
inOffsetY
inBlurRadius
inShadowColor
inShadowOpacity
1)

(set! newLayer
(car
(gimp-layer-new
inImage
(car (gimp-image-width inImage))
(car (gimp-image-height inImage))
RGB-IMAGE
"layer 1"
100
NORMAL
)
)
)

(gimp-image-add-layer inImage newLayer 2)

(gimp-drawable-fill newLayer 2)

(gimp-image-merge-visible-layers inImage 1)
)
)

(script-fu-register
"script-fu-kev-border-shadow" ;func name
"Kev Border Shadow" ;menu label
"Convenience call to border and shadow" ;description
"Kevin Galligan" ;author
"copyright 2007, Kevin Galligan" ;copyright notice
"January 21, 2007" ;date created
"*"
SF-IMAGE "Input image" 0
SF-DRAWABLE "Input drawable" 0
SF-VALUE "Border Width:" "1" ;a string variable
SF-COLOR "Border Color:" '(102 102 102) ;color variable
SF-VALUE "Shadow Offset X:" "3" ;a string variable
SF-VALUE "Shadow Offset Y:" "3" ;a string variable
SF-VALUE "Shadow Blur Radius:" "5" ;a string variable
SF-COLOR "Shadow Color:" '(0 0 0) ;color variable
SF-VALUE "Shadow Opacity:" "80" ;a string variable
)
(script-fu-menu-register "script-fu-kev-border-shadow" "/Script-Fu/Kev")


More later.

No comments: