Sunday, May 27, 2012

ImageMagick, Imagick, and whatever the hell else you want to call it

So I'm a GD boy, when it comes to writing php scripts to build and manipulate images - or at least, I have been for quite some time. I never ventured into ImageMagick, largely because it was significantly less accessible, what with having to be enabled on the server and all that. Besides, until recently I never really needed anything so complex that GD couldn't do the trick.

Yesterday I was experimenting with my avatar bases for AoA. I'm currently painting them in greyscale, then applying a single layer set to the "Color" blend mode in Photoshop. This way, I can make any tweaks I want to the value aspect of the image without having to redo several versions of the base (specifically the various skin tones). Unfortunately, as far as I could tell at that point, I was going to have to save off individual versions of the base for each skin tone, which was not at all something I was looking forward to.

So, as a programmer, I try to facilitate my laziness by writing code, and this time I went on a hunt for a native GD function that would allow me to blend two images together. I found a function that worked similarly to the "Overlay" blend mode (if you're in need of this kind of a function, look into the GD function, imagelayereffect() with the effect IMG_EFFECT_OVERLAY). Unfortunately, this was not exactly what I was looking for, and resulted in some very harsh lighting effects. In the end, I ended up writing a script to scroll through each pixel, convert RGB values to HSL, pull the hue and saturation and dump it into the greyscale image. It worked pretty well, but took a couple seconds. Combined with caching, this was a workable solution, but could not be extended much further, say, if I wanted to work with numerous layers all being processed individually in series.

At this point, I didn't need anything more than that, so I could have settled down with GD and things would have been fine. Fortunately, I don't settle easily, and I wanted to see if there was a better way. In comes ImageMagick. Note: it feels extremely different from GD, and documentation for the PHP functions is piss-poor. This is because ImageMagick is primarily intended to be used through a command-line, and the PHP functions attempt to abstract the commands into something more OOP-friendly.

I ultimately found that I understood better by working with the command-line functions, which can be used directly from PHP using the exec() function. I don't know much just yet, but from what I've gathered, the basic anatomy of a command is as follows:
convert file1.png file2.png file3.png +append file123.png
The above command opens three separate files and puts them into your working space. You can imagine this as being three files opened as layers on top of each other if you like, but you can also imagine them to be opened one beside the other. All that matters is the order; file1 comes before file2, which comes before file3. The command then flattens them all down, placing one after the other horizontally, and then saves them as file123.png.

convert - starts the command
file1.png, file2.png, file3.png - names of existing files in the folder from which the php script is being run
+append - a command that flattens the image down and places the 'layers' side by side horizontally. Conversely, -append could be used to arrange them vertically.
file123.png - the file name of the resulting image, saved relative to the php script's location in the file system.

Fairly easy to understand. It took some doing, but I eventually found a command that allowed me to achieve something *fairly* close to what I wanted, in this form.
convert female_base.png female_colour_peach.png -compose colorize -composite result.png
Here, -compose seems to be setting the blending mode to colorize, while -composite seems to flatten the whole image down as though they were layers placed on top of each other. Remember, I'm merely recording my own findings, so I may not be entirely correct in my assumptions. All I know is that this worked fairly well. It carried the hue and saturation of my female_colour_peach.png image and applied them to the corresponding pixels of the female_base.png image, whilst retaining the original brightness/value.

For all intensive purposes, this worked. I made a few adjustments, tinkering with levels and brightness of the resulting image (because Photoshop's color blend mode actually tinkers with the value levels a bit), but most importantly I ultimately decided to rewrite this using the PHP Imagick functions, as follows:
$base = new Imagick("female_base.png");
$overlay = new Imagick("female_colour_peach.png");
$base->compositeImage($overlay,imagick::COMPOSITE_COLORIZE,0,0);
$base->levelImage (0, 1.21, 65535 * .85);
$base->modulateImage(100,125,100);
Having written out the code as a command afforded me a greater understanding of how the Imagick PHP functions work. Here, I'm pretty much doing the same thing. I open my files, then I use compositeImage() to paste my $overlay image on top with a blend mode of COLORIZE (the 0,0 are referring to x,y offsets). Then I tinkered with the levels of the image with levelImage() and finally the brightness, saturation and hue with modulateImage().

And the final result is...


No comments:

Post a Comment