Smart Image Resizing while Preserving Transparency With PHP and GD Library
Should be easy to find, right? All you want is a function that resizes an image to constraints (doesn't care if it should scale up or down), with possibility to select if you want to keep it proportional, and possibility to use either width or height as the constraint. Also, you want it to preserve transparency damn it! Surprisingly, I was unable to find a good function that does all that, so I decided to attempt writing it. This should do it.
Features/Usage:
If you pass width as 0 (zero) -- this function will disregard width, and use height as constraint. Same vice versa.
If you set "proportional" to false - the function will simply stretch (or shrink) the image to its full constraints.
If one of the dimensions is set to zero, and proportional set to "false" - then the image will be forced to stretch or shrink the other dimension, and disregard the zeroed dimension (leave it the same).
If proportional is set to true - the image will resize to constraints proportionally, once again, with possibility to have either width or height set to zero.
The function can use either linux "rm" command, or php @unlink. Most probably you don't need to ever use that flag, but on some setups - @unlink won't work due to user access restrictions.
The function will simply replace the file that you give it, with the resized file.
The function supports gif, png, and jpeg, and preserves the transparency of gif and png images.
Tested on GD version 2.0.28 only.
Essentially, everything happens just as logically expected. Please, if you see anything wrong, and you know ways to enhance, or optimize it, let me know.
Update: Thank you Joanne for finding a bug with proportional resize, and providing a great, more concise and clear solution to proportional resize. (see comments)
Update: The image transparency code was updated to a more thorough solution. See comments for info.
Update: There is a new parameter "output" which can be set to either
- "file" - overwrite the given file (default)
- "browser" - output image through http - with correct mime type
- "return" - return GD Library Image object
- or any filename of choice - save changed version to output path
Update: There is another new parameter "delete_original". Speaks for itself.
Please, excuse a few code misalignments. Bug with coloring filter.
<?php function smart_resize_image( $file, $width = 0, $height = 0, $proportional = false, $output = 'file', $delete_original = true, $use_linux_commands = false ) { if ( $height <= 0 && $width <= 0 ) { return false; }
$info = getimagesize($file); $image = '';
$final_width = 0; $final_height = 0; list($width_old, $height_old) = $info;
if ( $proportional) { if ($width == 0) $factor = $height/$height_old; elseif ($height == 0) $factor = $width/$width_old; else $factor = min ( $width / $width_old, $height / $height_old);
$final_width = round ($width_old * $factor); $final_height = round ($height_old * $factor);
} else { $final_width = ( $width <= 0 ) ? $width_old : $width; $final_height = ( $height <= 0 ) ? $height_old : $height; }
switch ( $info[2] ) { case IMAGETYPE_GIF: $image = imagecreatefromgif($file); break; case IMAGETYPE_JPEG: $image = imagecreatefromjpeg($file); break; case IMAGETYPE_PNG: $image = imagecreatefrompng($file); break; default: return false; } $image_resized = imagecreatetruecolor( $final_width, $final_height ); if ( ($info[2] == IMAGETYPE_GIF) || ($info[2] == IMAGETYPE_PNG) ) { $trnprt_indx = imagecolortransparent($image); // If we have a specific transparent color if ($trnprt_indx >= 0) { // Get the original image's transparent color's RGB values $trnprt_color = imagecolorsforindex($image, $trnprt_indx); // Allocate the same color in the new image resource $trnprt_indx = imagecolorallocate($image_resized, $trnprt_color['red'], $trnprt_color['green'], $trnprt_color['blue']); // Completely fill the background of the new image with allocated color. imagefill($image_resized, 0, 0, $trnprt_indx); // Set the background color for new image to transparent imagecolortransparent($image_resized, $trnprt_indx); } // Always make a transparent background color for PNGs that don't have one allocated already elseif ($info[2] == IMAGETYPE_PNG) { // Turn off transparency blending (temporarily) imagealphablending($image_resized, false); // Create a new transparent color for image $color = imagecolorallocatealpha($image_resized, 0, 0, 0, 127); // Completely fill the background of the new image with allocated color. imagefill($image_resized, 0, 0, $color); // Restore transparency blending imagesavealpha($image_resized, true); } }
imagecopyresampled($image_resized, $image, 0, 0, 0, 0, $final_width, $final_height, $width_old, $height_old); if ( $delete_original ) { if ( $use_linux_commands ) exec('rm '.$file); else @unlink($file); } switch ( strtolower($output) ) { case 'browser': $mime = image_type_to_mime_type($info[2]); header("Content-type: $mime"); $output = NULL; break; case 'file': $output = $file; break; case 'return': return $image_resized; break; default: break; }
switch ( $info[2] ) { case IMAGETYPE_GIF: imagegif($image_resized, $output); break; case IMAGETYPE_JPEG: imagejpeg($image_resized, $output); break; case IMAGETYPE_PNG: imagepng($image_resized, $output); break; default: return false; }
return true; } ?>
|
How do I use this script to
How do I use this script to output an image to the browser? Is there any example code?
Is there a way to make the
Is there a way to make the crop area to stand more 'up' than the current position (vertically) considerring that
i have pictures which containt valued information mainly on the uppper part of the image.
Last, but not least is to say that the function is great! Works excellent with no black lines, no matter what the source and destination is !
Well, I would say that
Well, I would say that cropping goes a little beyond this function's intended use. Instead, you should set the output to "return", this way you'll have the image object that you can manipulate with gd library any way you like.
resize tranparent gif
Hi Max;
you have nice script . i'm new to php and im trying to get a script that risizes tranparent gif and display the image in the screen. I spentlot of time playingwith your wornderfull script , but could not worked it. any help and scipt example would be appreciated
thanks
jeff
Let me know what kind of
Let me know what kind of problem occurs and I will be able to help. : )
This is very nice
This is very nice function
very very useful for me
Thanks a lot.
Hi, thanks for the great
Hi,
thanks for the great solution to preserving transparency. On Debian Etch I found that the transparency is not coming out correctly, and instead a grey background shows up (try for example this image: http://www.nba.com/media/bos_50px_080328.gif
Any others experiencing the same thing?
Output to web browser?
So...how would I use this function to output a resized image to the browser?
FANTASTIC :)
FANTASTIC :)
indeed!! thank you very much
indeed!! thank you very much programmer. you're fantastic, and your script too. thanks a bunch >:D<
Thanks!
Thanks, this is just what i was looking for, really made life easier for me!
Cheers!
R
P.S Please do something about your captcha... its one of the most irritating that i have ever come accross.
error message - php5 related?
I used this great script for a small Web project and it works fine on my local computer in an (OSX 10.4) PHP 4 environment. Recently I upgraded to OSX 10.5 that works with PHP5 and now I get this fatal error: 'call to undefined funtion imagecreatefromjpeg()' it's probably just a tiny thing but anyone here with the same problem and a suggestion how to fix it? thx a lot!
Sounds like you're missing
Sounds like you're missing GD Library in your PHP installation on the server. : )
GIF and 8 bit PNG do not show transparency
Thank you for writing this. It is exactly what I have been looking for all day, but there is one problem. GIF's and 8 bit PNG's do not show transparency correctly. GIF's just come out with white wherever it is supposed to be transparent and then 8 bit PNG's end up distorted-looking with random pixels transparent. 24-bit PNG's look great though.
Thanks
I'm seeing the same
I'm seeing the same thing...
8bit png's do not work
hi. thanks a lot for this
hi. thanks a lot for this code!
It's the best commented one and the best working one I have found!
when using proportional resizing, I wanted to have some changes.
if you set maxwidt=100px,maxheight=100px, the resultimage can be for example 100x75.
In my case I wanted that image to fixed to maxwidt and maxheight, and still have the picture propotional resized.
I have made it create a transparent frame that is fixed to maxwidth an maxheight, the propotional picture is then centered to that one.
look on the end of this page:(you should be able to see the code there without logging in)
http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/...
multiple resizes
Help I can't figure out how to use this excellent function to make multiple resizes and upload them to separate directories (for larger, medium-sized, and thumbnail images). Here is my code:
$full = $_FILES['upload']['tmp_name'][$number]; smart_resize_image( $full, $width = 400, $height = 0,$proportional = true, $output = 'file',
$delete_original = true, $use_linux_commands = true );
smart_resize_image( $full, $width = 240, $height = 0, $proportional = true, $output = '$display', $delete_original = true, $use_linux_commands = true );
smart_resize_image( $display, $width = 120, $height = 0, $proportional = true, $output = '$thumb', $delete_original = true, $use_linux_commands = true );
$success_full = move_uploaded_file($full, FULL_DIR.$file_name);
$success_display = move_uploaded_file($display, DISPLAY_DIR.$file_name);
$success_thumbs = move_uploaded_file($thumb, THUMB_DIR.$file_name);
Sorry I know I'm a newbie at this kind of thing. I've tried DELETE_ORIGINAL = false, I've tried renaming the image files. No relief from my frustration.
Figured it out
I figured it out. I should have been using copy() and not move_uploaded_file.
Thanks for the great function!!!
/* USE SMART_RESIZE FUNCTION TO MAKE 3 COPIES AND UPLOAD TO MEDIA DIR */smart_resize_image( $_FILES['upload']['tmp_name'][$number], $width = 240, $height = 0, $proportional = true, $output = 'file', $delete_original = false, $use_linux_commands = false );
$success_display = copy($_FILES['upload']['tmp_name'][$number], DISPLAY_DIR.$file_name);
smart_resize_image( $_FILES['upload']['tmp_name'][$number], $width = 400, $height = 0, $proportional = true, $output = 'file', $delete_original = false, $use_linux_commands = false );
$success_full = copy($_FILES['upload']['tmp_name'][$number], FULL_DIR.$file_name);
smart_resize_image( $_FILES['upload']['tmp_name'][$number], $width = 120, $height = 0, $proportional = true, $output = 'file', $delete_original = true, $use_linux_commands = false );
$success_thumbs = move_uploaded_file($_FILES['upload']['tmp_name'][$number], THUMB_DIR.$file_name);
You don't have to copy the
You don't have to copy the file yourself. You can use $output parameter -- it accepts any path where you want result image to appear. If you make $output to be "/path/to/directory" - it will copy file there for you. Just make sure delete_original = false for the first 2 conversions - and make delete_original = true for the last conversion, when you don't need to keep original file anymore.
Pingback
Helpful!
Great thanks to thou, author. This transparency saving solution was helpful. Write more! :)
Thanks
This is a great bit of code. Exactly what I needed. There doesn't seem to be much reference on how to keep the transparency of png images and l was having real problems with it, your code helped me sort it. Thanks!
Transparency
I am having difficulty with the transparency. Sometimes my images have a completely black background and no transparency. Sometimes they have a dithered background with some pixels transparent and others not.
I'm using GIFs saved from Photoshop either with the Save to Web function or just the Save As.
Any ideas???
older version?
It depends on what kind of settings you choose when saving for web in photoshop. As for this function if it doesn't work for you - try the older version over at php.net. I would appreciate if you let me know how was your experience. It might be that updates have screwed up something.
Crop function
Excellent function. Really useful.
I was wondering, however, if you planned to add a crop function in as well? For example, if you allowed people to upload images to your site but you wanted to keep them within a set size (say 300 x 200) and someone uploads an image at 600 x 600, rather than resizing the image to fit the 300 x 200 area, why not first crop the image to 600 x 400, then resize? This would avoid distorting the image.
Just a thought.
Thanks anyway.
Thank you for comment! I'd
Thank you for comment! I'd say, cropping is a separate issue. You never know what exactly you're cropping out, - it might be that the whole point of the image is near its border. Resizing doesn't actually alter the image, whereas cropping does, that's why I decided to stay away from it, since most people require their images intact, but resized to specific dimensions.
Hi, I actually came up with
Hi,
I actually came up with a few changes this afternoon to do with the cropping after finding a cropping function on the web. FYI, first I added a new argument -
function smart_resize_image($file, $width = 0, $height = 0, $proportional = false, $crop = false, $output = 'file', $delete_original = true, $use_linux_commands = false) {
Then added the following after the closing brace for if ($proportional) {...}
/* Start of new Code */
$int_width = 0;
$int_height = 0;
$adjusted_height = $final_height;
$adjusted_width = $final_width;
if ($crop) {
$wm = $width_old/$width;
$hm = $height_old/$height;
$h_height = $height/2;
$w_height = $width/2;
if ($width_old > $height_old) {
$adjusted_width = $width_old / $hm;
$half_width = $adjusted_width / 2;
$int_width = $half_width - $w_height;
} else if(($width_old < $height_old) || ($width_old == $height_old)) {
$adjusted_height = $height_old / $wm;
$half_height = $adjusted_height / 2;
$int_height = $half_height - $h_height;
}
}
/* End new code */
Finally I changed the imagecopyresampled() function to -
imagecopyresampled($image_resized, $image, -$int_width, -$int_height, 0, 0, $adjusted_width, $adjusted_height, $width_old, $height_old);
How it works -> say you have an image 500 x 500 and want to crop & resize to 250 x 200, rather than crop from the top left and losing the bottom 100px of the image, it crops from 50px from the top instead, i.e. it centres the cropping area. It works both ways so if you want to crop to 200 x 250 instead, the cropping area is 50px in from the LHS.
I was going to get around to changing the variable names so that the final_height and final_width are used in the imagecopyresampled() function but didn't get around to it yet. Also, I was thinking of setting the arguments so it would be either proportional resize, crop or none instead.
One other change I did do but not listed above was add a quality argument ($quality = '75') and changed the imagejpeg() function call to imagejpeg($image_resized, $output, $quality);
Hope it helps.
Looks great! I will add some
Looks great! I will add some of these functionalities into the function soon. Also it seems that with this amount of parameters it would be reasonable to switch the function to accepting an associative array. It would be much easier if you could say something like array( 'crop' => false, 'proportional' => true, 'output' => 'file'), and then understand what all those parameters mean when you come across this function in your code later.
Slight correction
I noticed a slight problem with the cropping on images that had a different width : height ratio to the final image. For example if I wanted to crop to 180 x 135 but my source image was 300 x 245, even though the width is greater than the height, I would not want to crop this so that I lost some of the width as it would end up with black borders on the side. Instead I would need to lose some of the height. So by knowing that the ratio we want is 1.3333, our source image has a ratio of 1.224 and as it is less, we know to crop using height instead. Looking at this another way, say our source image was 520 x 340, this has a ratio of 1.529 and so is greater than the target ratio we need to lose the width.
So my next changes are -
if ($crop) {
$wm = $width_old/$width;
$hm = $height_old/$height;
$h_height = $height/2;
$w_height = $width/2;
$ratio = $width/$height;
$old_img_ratio = $width_old/$height_old;
if ($old_img_ratio > $ratio) {
$adjusted_width = $width_old / $hm;
$half_width = $adjusted_width / 2;
$int_width = $half_width - $w_height;
} else if($old_img_ratio <= $ratio) {
$adjusted_height = $height_old / $wm;
$half_height = $adjusted_height / 2;
$int_height = $half_height - $h_height;
}
}
Now I'm hoping that this works better!
Alright, thanks! Glad you
Alright, thanks! Glad you caught it. : )
This is great - it works a
This is great - it works a treat for me. Thanks very much.
I like it
Thanks for posting this script on the PHP manual site. I really like having a script that handles any standard image type in one place.
You asked for feedback and suggestions. I made a few changes for my implementation, which you may or may not think are improvements:
(1) Transparency for GIFs was not working for me...all backgrounds were black. I changed the transparency section of your script to the one posted by Rob on 9-Oct-2007 on the imagecolortransparent page of the PHP site, and it works well:
http://www.php.net/manual/en/function.imagecolortransparent.php
(2) When both width and height are specified for proportional resizing, I thought the new image should fit INSIDE the box specified by width and height. So I changed your proportional resize algorithm to:
<?phpif ($width == 0) $factor = $height/$height_old;
elseif ($height == 0) $factor = $width/$width_old;
else $factor = min($width/$width_old,$height/$height_old);
$final_width = round ($width_old * $factor);
$final_height = round ($height_old * $factor);
?>
(3) I wanted the script to be able to output either to a file or to the browser. At the place where you delete the old file, I changed it to:
<?phpif ($output == 'file') unlink($file);
else { // output to browser
$mime = image_type_to_mime_type($info[2]);
header("Content-type: $mime");
$file = NULL;
}
?>
Thanks again...I've had fun playing with this.
Joanne
nice corrections
Thanks Joanne - I appreciate your suggestions.
1) I will update this function with that block in the near future. : ) Great find!
2) From first glance your code does same thing as mine, only seems to be written a little better. Maybe there are some bugs I can't notice, however - I was wondering what do you mean by "INSIDE the box" - what exactly was the problem?
3) That's very nice. I had this in mind too, for the sake of overall convenience.
Appreciate comment. : )
(2) Here's an example of
(2) Here's an example of what I meant by "inside the box:" Start with an original image 2400 pixels wide x 1600 pixels high. Call the function and specify width=800, height=600, proportional. With your algorithm, the image is resized to 900 x 600 pixels and it does not fit inside a 800 x 600 pixel box. With my algorithm the image is resized to 800 x 533 pixels.
I see exactly what you mean.
I see exactly what you mean. This is a bug, and it only occurs in a few cases. Thank you for pointing it out! I will update the original code.
Very nice! I thougth it was
Very nice! I thougth it was impossible to create high quality thumbnails using php. This article proved me wrong. I would recomend to add quality paremeter to imagejpeg, like imagejpeg($image_resized, $file, 95); or such, default 75 leaves to many artifacts. Anyway, a very usefull article and a suburb code!
I will implement it soon,
I will implement it soon, thank you.
I have bug which i would
I have bug which i would describe like this:
when i use source image with bigger width than height (lets say 3:1 ratio),
the function does not work at all. Any suggestions to fix it?