There and Back Again

Problems with GD: Solved

In a recent project, I had the joy (make that pain) of using GD a lot. The project was an update to the a facial composite tool for Identikit. The new code isn’t live yet, its still in final testing, but its all done for me except for a bit of final bug fixing.

Anyway the app is written in Flash and integrates with PHP for things like saving files to the database or creating a jpeg for the user to save. So what I have is a bunch of transparent pngs that I composite together to make the final sketch. Just like you would do by hand if you had an old school version of this from little paper cutouts.

The problem I ran into with GD is it doesn’t repect an images transparency. So my basic test of this is, open an transparent Image and resize then output the image.

You take the starting Image (shown on and off a colored background so you can see the transparency)

Sample Image - Glasses with Transparent Lenses Sample Image - Glasses with Transparent Lenses

Next we resize it, some code for this is shown below:

// load in the source image
$im = imagecreatefrompng('sample1.png');
imagesavealpha($target,true);

// get its width and height
$imageWidth = imagesx($im);
$imageHeight = imagesy($im);

// were doubling in size
$targetWidth = $imageWidth*2;
$targetHeight = $imageHeight*2;

// create a target im
$target = imagecreatetruecolor($targetWidth,$targetHeight);
imagesavealpha($target,true);


// fill the image with transparency
$trans = imagecolorallocatealpha($target,255,255,255,127);
imagefill($target,0,0,$trans);

// copy with resize to the target
if (isset($_GET['resample'])) {
        imagecopyresampled($target,$im,0,0,0,0,$targetWidth,$targetHeight,$imageWidth,$imageHeight);
}
else {
        imagecopyresized($target,$im,0,0,0,0,$targetWidth,$targetHeight,$imageWidth,$imageHeight);
}


// output the image as a png
header('Content-Type: image/png');
imagepng($target);

Output from this is shown below, first using resize then using resample

Resized version of sample image
Resampled version of sample image


As you can see the background is still alpha transparent but the lenses are now black. I hope im just missing some flag, but in that case someone should really add a makeBrokenGDWorkLikeIWouldExpect flag so I don’t have to find the right combination of them to make things work. After all if I shelled out to ImageMagick convert to do the resize it would just do the right thing.

The next step would be to do some compositing, but you wouldn’t see it anyway since the eyes go below the glasses and they are black.

Anyhow feedback on what im doing wrong here would be nice, or if this just doesn’t work what I need to make this a good bug report.

Any how this example doesn’t have the problems I had before, that just had a simple bug in it from me filling in the wrong order.

Anyhow I did have similar problems when working with the compositing code for this app, im guessing it was caused by a similar mistake, but i’d have to read through commit logs to be sure, and that doesn’t sound like much fun. Suffice it too say, the equivalent code using Imagemagick on the command line is a lot easier to understand though not much easier to debug.

Anyhow the second part item after resizing an image is adjusting its properties. Shading the image, playing with gamma works pretty well, flipping it if needed (this would be nice to have as part of the GD api but its quite easy to write, This manual comment has one), and then adjusting the transparency of the image (Imagecopymerge works well for that).

A sample output would be:
Eyes with Glasses

And for a quality comparison, the same thing done shelling out to composite
Eyes with Glasses

The main things I would take out of this is that the API for GD is way to low of a level for normal use, so you’ll want to look into a wrapper (there are some options in PEAR but none with a stable release). There are also quality issues with GD when making images larger, but I don’t want to see new features like cubic interpolation added until the api to use them is reasonable.

Code for GD Compositing

// create a target im
$target = imagecreatetruecolor(500,200);
imagesavealpha($target,true);
imagealphablending($target,true);

// fill the image with transparency
$trans = imagecolorallocatealpha($target,255,255,255,127);
imagefill($target,0,0,$trans);


// copy the eyes in place first
// load in the source image
$im = imagecreatefrompng('sample2.png');
imagesavealpha($im,true);
imagealphablending($im,true);

// get its width and height
$imageWidth = imagesx($im);
$imageHeight = imagesy($im);

// were doubling in size
$targetWidth = $imageWidth*2;
$targetHeight = $imageHeight*2;

imagecopyresampled($target,$im,2,26,0,0,$targetWidth,$targetHeight,$imageWidth,$imageHeight);
imagedestroy($im);


// copy the glasses on top of that
// load in the source image
$im = imagecreatefrompng('sample1.png');
imagesavealpha($im,true);
imagealphablending($im,true);

// get its width and height
$imageWidth = imagesx($im);
$imageHeight = imagesy($im);

// were doubling in size
$targetWidth = $imageWidth*2.2;
$targetHeight = $imageHeight*2.2;

imagecopyresampled($target,$im,0,0,0,0,$targetWidth,$targetHeight,$imageWidth,$imageHeight);
imagedestroy($im);



// output the image as a png
header('Content-Type: image/png');
imagepng($target);

Code for ImageMagick compositing

header('Content-type: image/png');
passthru('composite -geometry +2+26 sample2.png -resize 200%  back.png - | composite sample1.png -resize 220% - - ');

10 thoughts on “Problems with GD: Solved

  1. Pierre

    sorry we do not have this makeBrokenGDWorkLikeIWouldExpect flag, we do not have bug reports from you neither. but I have one thing for you, could help:
    RTFM

  2. Joshua Eichorn Post author

    So are you saying the default behavior is purposly broken in that case, and I just need to set a flag to make it treat things correctly. Or GD is broken and I need to file a bug report.

    Really the purpose of this post is too find out which so I can either leave a note in the manual explaining how to get the expected results or file a useful bug report. I’m sorry if the original post didn’t make that clear.

  3. Pierre

    What I am saying is that one has to know what he is talking about before arguing, blogging and making noises without being of a single help to the GD developers (me included).

    The purpose of this post is nothing else that noises and arrogance as usual, if you were asking me instead you will get you answer. So yes, I think you should learn the topic and read the manual, it is not complete? not clear enough? report a bug, if I have time and motivation, I may help you.

    Have a nice day

    (tip of the day, having 100% alpha transparency does not mean that this color is the background color)

  4. Joshua Eichorn Post author

    Well lets see your calling me arrogant but yet you seem to imply im missing some thing simple to make this work. And instead of at least pointing me to the manual page that has this info, you give a tip of the day.

    Well lets see, I made a transparent color and then filled the background with it. There is no imagebackground function so how else do you make a 100% transparent canvas.

  5. Pierre

    What you are missing is that in both cases alpha is preserved. You have a background color but the glasses are still transparent (check it, the inside color has the alpha component set to 100%).

    What does that mean?

    If you copy this image, let say, on a face, and set the alpha blending mode to true, you will get a nice glasses on this face. something like:
    http://blog.thepimp.net/alpha.png (sorry, only (c)less photo with a face I found 🙂

    That just prooved that alpha is a) preserved and b) works with imagecopyresampled. To set or unset the alpha blending, see imagealphablending (http://de.php.net/imagealphablending).

    A blog is not a way to report bugs or ask questions. You expect one to answer you because you were ranting here, fine. Do not expect me to answer you anymore here, but bugs.php.net or ask php-general if you have problems to understand how gd works, or me if you have my mail (Greg should have it ;).

  6. Pierre

    Now we agree, the BC problems in GD force me to keep the default behaviors as they were. That makes the code slighty longer than what it should be (and, per se, harder to read).

    Stay tuned to php6, that will change 🙂

  7. Joshua Eichorn Post author

    I made a couple more changes while you were sending that last message Pierre. Mainly about quality on resize, so hopefully you will keep that in mind in php6 as well. It would be nice to be able to make the speed quality trade-off on those operations as long as its still the same copy call.

  8. Pierre

    I do not know the quality of the original images, but with imagemagic you can select different filters/interpolation methods, some are slower but if the quality is one of the goal.

    For GD itself, the current implementation (a generic one, not bad neither good 😉 ) is not going to change that soon but in php6, not sure if I will keep GD2 or not for this kind of tasks.

    I have some patches to add some features to the existing GD, giving either better quality or faster processing, if needed but not free…

  9. joeri

    Suddenly the tone changed… what happend?

    Im an artists myself and find the terms used by GD about opacity very confussing.

    imagesavealpha($im,true);

    save??? what to disc? preserve the alpha? Yeah why not?
    Just let me load a 24 or 32 bit image and leave me alone with commands about what to do with the channels… Dooh… what next?
    imagesavered($im,true); imagesaveblue($im,true); imagesavegreen($im,true);

    imagealphablending($im,true);
    can’t GD adopt terms Gimp and Photoshop use?

    Forget about imagealphablending, just give me composite tools.

    $newimage= imagecomposite($img_under, $img_over, GD_DEFAULT );
    ( just a 32bit image over a 32bit image, thank you very much )
    and why not add:
    $newimage= imagecomposite($img_under, $img_over, GD_ADD );
    $newimage= imagecomposite($img_under, $img_over, GD_MULTIPLY );
    $newimage= imagecomposite($img_under, $img_over, GD_MASK, $img_mask );

    Reading the Manual does not change GD not giving expected behavior, it only changes my expectations. There is a difference.
    Its not a bug, so sorry for not filing it Pierre.

  10. Dan

    Thanks, great help!

    Pierre, I didn’t care reading all your flaming, I don’t like it…