What's the most efficient way to resize large images in PHP?
I'm currently using the GD function imagecopyresampled to take high resolution images, and cleanly resize them down to a size for web viewing (roughly 700 pixels wide by 700 pixels tall).
This works great on small (under 2 MB) photos and the entire resize operation takes less than a second on the server. However, the site will eventually service photographers who may be uploading images up to 10 MB in size (or images up to 5000x4000 pixels in size).
Doing this kind of resize operation with large images tends to increase the memory usage by a very large margin (larger images can spike the memory usage for the script past 80 MB). Is there any way to make this resize operation more efficient? Should I be using an alternate image library such as ImageMagick?
Right now, the resize code looks something like this
function makeThumbnail($sourcefile, $endfile, $thumbwidth, $thumbheight, $quality) {
// Takes the sourcefile (path/to/image.jpg) and makes a thumbnail from it
// and places it at endfile (path/to/thumb.jpg).
// Load image and get image size.
$img = imagecreatefromjpeg($sourcefile);
$width = imagesx( $img );
$height = imagesy( $img );
if ($width > $height) {
$newwidth = $thumbwidth;
$divisor = $width / $thumbwidth;
$newheight = floor( $height / $divisor);
} else {
$newheight = $thumbheight;
$divisor = $height / $thumbheight;
$newwidth = floor( $width / $divisor );
}
// Create a new temporary image.
$tmpimg = imagecreatetruecolor( $newwidth, $newheight );
// Copy and resize old image into new image.
imagecopyresampled( $tmpimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height );
// Save thumbnail into a file.
imagejpeg( $tmpimg, $endfile, $quality);
// release the memory
imagedestroy($tmpimg);
imagedestroy($img);
People say that ImageMagick is much faster. At best just compare both libraries and measure that.
Prepare 1000 typical images.
Write two scripts -- one for GD, one
for ImageMagick.
Run both of them a few times.
Compare results (total execution
time, CPU and I/O usage, result
image quality).
Something which the best everyone else, could not be the best for you.
Also, in my opinion, ImageMagick has much better API interface.
Here's a snippet from the php.net docs that I've used in a project and works fine:
<?
function fastimagecopyresampled (&$dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality = 3) {
// Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled.
// Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled".
// Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting.
// Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain.
//
// Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero.
// Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect.
// 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized.
// 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3.
// 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster.
// 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images.
// 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled.
if (empty($src_image) || empty($dst_image) || $quality <= 0) { return false; }
if ($quality < 5 && (($dst_w * $quality) < $src_w || ($dst_h * $quality) < $src_h)) {
$temp = imagecreatetruecolor ($dst_w * $quality + 1, $dst_h * $quality + 1);
imagecopyresized ($temp, $src_image, 0, 0, $src_x, $src_y, $dst_w * $quality + 1, $dst_h * $quality + 1, $src_w, $src_h);
imagecopyresampled ($dst_image, $temp, $dst_x, $dst_y, 0, 0, $dst_w, $dst_h, $dst_w * $quality, $dst_h * $quality);
imagedestroy ($temp);
} else imagecopyresampled ($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
return true;
}
?>
http://us.php.net/manual/en/function.imagecopyresampled.php#77679
phpThumb uses ImageMagick whenever possible for speed (falling back to GD if necessary) and seems to cache pretty well to reduce the load on the server. It's pretty lightweight to try out (to resize an image, just call phpThumb.php with a GET query that includes the graphic filename and output dimensions), so you might give it a shot to see if it meets your needs.
For larger images use libjpeg to resize on image load in ImageMagick and thereby significantly reducing memory usage and improving performance, it is not possible with GD.
$im = new Imagick();
try {
$im->pingImage($file_name);
} catch (ImagickException $e) {
throw new Exception(_('Invalid or corrupted image file, please try uploading another image.'));
}
$width = $im->getImageWidth();
$height = $im->getImageHeight();
if ($width > $config['width_threshold'] || $height > $config['height_threshold'])
{
try {
/* send thumbnail parameters to Imagick so that libjpeg can resize images
* as they are loaded instead of consuming additional resources to pass back
* to PHP.
*/
$fitbyWidth = ($config['width_threshold'] / $width) > ($config['height_threshold'] / $height);
$aspectRatio = $height / $width;
if ($fitbyWidth) {
$im->setSize($config['width_threshold'], abs($width * $aspectRatio));
} else {
$im->setSize(abs($height / $aspectRatio), $config['height_threshold']);
}
$im->readImage($file_name);
/* Imagick::thumbnailImage(fit = true) has a bug that it does fit both dimensions
*/
// $im->thumbnailImage($config['width_threshold'], $config['height_threshold'], true);
// workaround:
if ($fitbyWidth) {
$im->thumbnailImage($config['width_threshold'], 0, false);
} else {
$im->thumbnailImage(0, $config['height_threshold'], false);
}
$im->setImageFileName($thumbnail_name);
$im->writeImage();
}
catch (ImagickException $e)
{
header('HTTP/1.1 500 Internal Server Error');
throw new Exception(_('An error occured reszing the image.'));
}
}
/* cleanup Imagick
*/
$im->destroy();
From you quesion, it seems you are kinda new to GD, I will share some experence of mine,
maybe this is a bit off topic, but I think it will be helpful to someone new to GD like you:
Step 1, validate file. Use the following function to check if the $_FILES['image']['tmp_name'] file is valid file:
function getContentsFromImage($image) {
if (#is_file($image) == true) {
return file_get_contents($image);
} else {
throw new \Exception('Invalid image');
}
}
$contents = getContentsFromImage($_FILES['image']['tmp_name']);
Step 2, get file format Try the following function with finfo extension to check file format of the file(contents). You would say why don't you just use $_FILES["image"]["type"] to check file format? Because it ONLY check file extension not file contents, if someone rename a file originally called world.png to world.jpg, $_FILES["image"]["type"] will return jpeg not png, so $_FILES["image"]["type"] may return wrong result.
function getFormatFromContents($contents) {
$finfo = new \finfo();
$mimetype = $finfo->buffer($contents, FILEINFO_MIME_TYPE);
switch ($mimetype) {
case 'image/jpeg':
return 'jpeg';
break;
case 'image/png':
return 'png';
break;
case 'image/gif':
return 'gif';
break;
default:
throw new \Exception('Unknown or unsupported image format');
}
}
$format = getFormatFromContents($contents);
Step.3, Get GD resource Get GD resource from contents we have before:
function getGDResourceFromContents($contents) {
$resource = #imagecreatefromstring($contents);
if ($resource == false) {
throw new \Exception('Cannot process image');
}
return $resource;
}
$resource = getGDResourceFromContents($contents);
Step 4, get image dimension Now you can get image dimension with the following simple code:
$width = imagesx($resource);
$height = imagesy($resource);
Now, Let's see what variable we got from the original image then:
$contents, $format, $resource, $width, $height
OK, lets move on
Step 5, calculate resized image arguments This step is related to your question, the purpose of the following function is to get resize arguments for GD function imagecopyresampled(), the code is kinda long, but it works great, it even has three options: stretch, shrink, and fill.
stretch: output image's dimension is the same as the new dimension you set. Won't keep height/width ratio.
shrink: output image's dimension won't exceed the new dimension you give, and keep image height/width ratio.
fill: output image's dimension will be the same as new dimension you give, it will crop & resize image if needed, and keep image height/width ratio. This option is what you need in your question.
function getResizeArgs($width, $height, $newwidth, $newheight, $option) {
if ($option === 'stretch') {
if ($width === $newwidth && $height === $newheight) {
return false;
}
$dst_w = $newwidth;
$dst_h = $newheight;
$src_w = $width;
$src_h = $height;
$src_x = 0;
$src_y = 0;
} else if ($option === 'shrink') {
if ($width <= $newwidth && $height <= $newheight) {
return false;
} else if ($width / $height >= $newwidth / $newheight) {
$dst_w = $newwidth;
$dst_h = (int) round(($newwidth * $height) / $width);
} else {
$dst_w = (int) round(($newheight * $width) / $height);
$dst_h = $newheight;
}
$src_x = 0;
$src_y = 0;
$src_w = $width;
$src_h = $height;
} else if ($option === 'fill') {
if ($width === $newwidth && $height === $newheight) {
return false;
}
if ($width / $height >= $newwidth / $newheight) {
$src_w = (int) round(($newwidth * $height) / $newheight);
$src_h = $height;
$src_x = (int) round(($width - $src_w) / 2);
$src_y = 0;
} else {
$src_w = $width;
$src_h = (int) round(($width * $newheight) / $newwidth);
$src_x = 0;
$src_y = (int) round(($height - $src_h) / 2);
}
$dst_w = $newwidth;
$dst_h = $newheight;
}
if ($src_w < 1 || $src_h < 1) {
throw new \Exception('Image width or height is too small');
}
return array(
'dst_x' => 0,
'dst_y' => 0,
'src_x' => $src_x,
'src_y' => $src_y,
'dst_w' => $dst_w,
'dst_h' => $dst_h,
'src_w' => $src_w,
'src_h' => $src_h
);
}
$args = getResizeArgs($width, $height, 150, 170, 'fill');
Step 6, resize image Use $args, $width, $height, $format and $resource we got from above into the following function and get the new resource of the resized image:
function runResize($width, $height, $format, $resource, $args) {
if ($args === false) {
return; //if $args equal to false, this means no resize occurs;
}
$newimage = imagecreatetruecolor($args['dst_w'], $args['dst_h']);
if ($format === 'png') {
imagealphablending($newimage, false);
imagesavealpha($newimage, true);
$transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127);
imagefill($newimage, 0, 0, $transparentindex);
} else if ($format === 'gif') {
$transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127);
imagefill($newimage, 0, 0, $transparentindex);
imagecolortransparent($newimage, $transparentindex);
}
imagecopyresampled($newimage, $resource, $args['dst_x'], $args['dst_y'], $args['src_x'], $args['src_y'], $args['dst_w'], $args['dst_h'], $args['src_w'], $args['src_h']);
imagedestroy($resource);
return $newimage;
}
$newresource = runResize($width, $height, $format, $resource, $args);
Step 7, get new contents, Use the following function to get contents from the new GD resource:
function getContentsFromGDResource($resource, $format) {
ob_start();
switch ($format) {
case 'gif':
imagegif($resource);
break;
case 'jpeg':
imagejpeg($resource, NULL, 100);
break;
case 'png':
imagepng($resource, NULL, 9);
}
$contents = ob_get_contents();
ob_end_clean();
return $contents;
}
$newcontents = getContentsFromGDResource($newresource, $format);
Step 8 get extension, Use the following function to get extension of from image format(note, image format is not equal to image extension):
function getExtensionFromFormat($format) {
switch ($format) {
case 'gif':
return 'gif';
break;
case 'jpeg':
return 'jpg';
break;
case 'png':
return 'png';
}
}
$extension = getExtensionFromFormat($format);
Step 9 save image If we have a user named mike, you can do the following, it will save to the same folder as this php script:
$user_name = 'mike';
$filename = $user_name . '.' . $extension;
file_put_contents($filename, $newcontents);
Step 10 destroy resource Don't forget destroy GD resource!
imagedestroy($newresource);
or you can write all your code into a class, and simply use the following:
public function __destruct() {
#imagedestroy($this->resource);
}
TIPS
I recommend not to convert file format that user upload, you will meet many problems.
I suggest that you work something along these lines:
Perform a getimagesize( ) on the uploaded file to check image type and size
Save any uploaded JPEG image smaller than 700x700px in to the destination folder "as-is"
Use GD library for medium size images (see this article for code sample: Resize Images Using PHP and GD Library)
Use ImageMagick for large images. You can use ImageMagick in background if you prefer.
To use ImageMagick in background, move the uploaded files to a temporary folder and schedule a CRON job that "convert"s all files to jpeg and resizes them accordingly. See command syntax at: imagemagick-command line processing
You can prompt the user that file is uploaded and scheduled to be processed. The CRON job could be scheduled to run daily at a specific interval. The source image could be deleted after processing to assure that an image is not processed twice.
I've heard big things about the Imagick library, unfortunately I couldn't install it at my work computer and neither at home (and trust me, I spent hours and hours on all kinds of forums).
Afterwords, I've decided to try this PHP class:
http://www.verot.net/php_class_upload.htm
It's pretty cool and I can resize all kinds of images (I can convert them to JPG also).
ImageMagick is multithreaded, so it appears to be faster, but actually uses a lot more resources than GD. If you ran several PHP scripts in parallel all using GD then they'd beat ImageMagick in speed for simple operations. ExactImage is less powerful than ImageMagick but a lot faster, though not available through PHP, you'll have to install it on the server and run it through exec.
For larger images use phpThumb(). Here is how to use it: http://abcoder.com/php/problem-with-resizing-corrupted-images-using-php-image-functions/. It also works for large corrupted images.
Related
I have created this script based on reading other posts on StackOverflow. The script creates a thumbnail from the original image and adds it to a folder. The source images are located on my local server so Glob would work however I am not sure how to adjust this script so that it will run this function on all files in a folder (Glob). I realize it may be memory intensive but I only need to do it a few times on several folders and I'll be done with it.
Just in case you are questioning the reason I have included $height2, it is a little hack I came up with where we create the thumbnail while maintaining the aspect ratio then only save the top 250px so that the thumb is 250 x 250 but not distorted (stretched). It works great.
I just need to adjust to do a batch of all images in the source folder.
Any help would be great. Please keep in mind that I am a front end developer (html and css) and not great at PHP. This script alone took me forever to make work lol.
If anyone can help me adjust it for a full folder, that would be great.
foreach(glob('SourceFolder/*.jpg', GLOB_NOSORT) as $url); {
Thumbnail ($url, "DestinationFolder/*.jpg");
function Thumbnail($url, $filename, $width = 250, $height = true, $height2 = 250) {
// download and create gd image
$image = ImageCreateFromString(file_get_contents($url));
// calculate resized ratio
// Note: if $height is set to TRUE then we automatically calculate the height based on the ratio
$height = $height === true ? (ImageSY($image) * $width / ImageSX($image)) : $height;
// create image
$output = ImageCreateTrueColor($width, $height2);
ImageCopyResampled($output, $image, 0, 0, 0, 0, $width, $height, ImageSX($image), ImageSY($image));
// save image
ImageJPEG($output, $filename, 100);
}
}
I was able to adjust the code on my own after reading the online PHP manual.
The final code:
foreach (glob("SourceFolder/*.jpg") as $url) {
$destdir = "Destination/" . basename($url, null);
$width = 250;
$height = true;
// download and create gd image
$image = ImageCreateFromString(file_get_contents($url));
// calculate resized ratio
// Note: if $height is set to TRUE then we automatically calculate the height based on the ratio
$height = $height === true ? (ImageSY($image) * $width / ImageSX($image)) : $height;
// create image
$output = ImageCreateTrueColor($width, $height);
ImageCopyResampled($output, $image, 0, 0, 0, 0, $width, $height, ImageSX($image), ImageSY($image));
// save image
ImageJPEG($output, $destdir, 100);
}
This solution uses opendir and readdir. I have found that this approach can
handle folders with more files.
// Set desired height and width
$desiredWidth = 250;
$desiredHeight = true;
// Set source and destination folders
$srcFolder = "SourceFolder";
$destFolder = "Destination";
// This is more memory efficient than using glob()
$dh = opendir($srcFolder);
if ( ! $dh ) {
die('Unable to open folder ' . $srcFolder);
}
while($filename = readdir($dh) ) {
// only process jpg files.
if ( preg_match('/.jpg$/', $filename) ) {
$inputFile = sprintf('%s/%s', $srcFolder, $filename);
$outputFile = sprintf('%s/%s', $destFolder, $filename);
echo "file: $filename\n";
echo "input: $inputFile\n";
echo "output: $outputFile\n";
$inputImage = #ImageCreateFromJpeg($inputFile);
if ( $inputImage ) {
// calculate resized ratio
// Note: if $height is set to TRUE then we automatically calculate the height based on the ratio
$height = $desiredHeight === true ? (ImageSY($inputImage) * $desiredWidth / ImageSX($inputImage)) : $desiredHeight;
// create image
$outputImage = ImageCreateTrueColor($desiredWidth, $height);
ImageCopyResampled($outputImage, $inputImage, 0, 0, 0, 0, $desiredWidth, $height, ImageSX($inputImage), ImageSY($inputImage));
// save image
ImageJPEG($outputImage, $outputFile, 100);
ImageDestroy($inputImage);
ImageDestroy($outputImage);
} else {
echo "Could not process file\n";
}
}
}
closedir($dh);
I have made two GIFs to explain what I am trying to do. Where the grey border is the dimensions I am after (700*525). They are at the bottom of this question.
I want for all images that are larger than the given width and height to scale down to the border (from the centre) and then crop off the edges. Here is some code I have put together to attempt this:
if ($heightofimage => 700 && $widthofimage => 525){
if ($heightofimage > $widthofimage){
$widthofimage = 525;
$heightofimage = //scaled height.
//crop height to 700.
}
if ($heightofimage < $widthofimage){
$widthofimage = //scaled width.
$heightofimage = 700;
//crop width to 525.
}
}else{
echo "image too small";
}
Here are some GIFs that visually explain what I am trying to achieve:
GIF 1: Here the image proportions are too much in the x direction
GIF 2: Here the image proportions are too much in the y direction
image quality comparison for #timclutton
so I have used your method with PHP (click here to do your own test with the php) and then compared it to the original photo as you can see there is a big difference!:
Your PHP method:
(source: tragicclothing.co.uk)
The actual file:
(source: mujjo.com)
The below code should do what you want. I've not tested it extensively but it seems to work on the few test images I made. There's a niggling doubt at the back of mind that somewhere my math is wrong, but it's late and I can't see anything obvious.
Edit: It niggled enough I went through again and found the bug, which was that the crop wasn't in the middle of the image. Code replaced with working version.
In short: treat this as a starting point, not production-ready code!
<?php
// set image size constraints.
$target_w = 525;
$target_h = 700;
// get image.
$in = imagecreatefrompng('<path to your>.png');
// get image dimensions.
$w = imagesx($in);
$h = imagesy($in);
if ($w >= $target_w && $h >= $target_h) {
// get scales.
$x_scale = ($w / $target_w);
$y_scale = ($h / $target_h);
// create new image.
$out = imagecreatetruecolor($target_w, $target_h);
$new_w = $target_w;
$new_h = $target_h;
$src_x = 0;
$src_y = 0;
// compare scales to ensure we crop whichever is smaller: top/bottom or
// left/right.
if ($x_scale > $y_scale) {
$new_w = $w / $y_scale;
// see description of $src_y, below.
$src_x = (($new_w - $target_w) / 2) * $y_scale;
} else {
$new_h = $h / $x_scale;
// a bit tricky. crop is done by specifying coordinates to copy from in
// source image. so calculate how much to remove from new image and
// then scale that up to original. result is out by ~1px but good enough.
$src_y = (($new_h - $target_h) / 2) * $x_scale;
}
// given the right inputs, this takes care of crop and resize and gives
// back the new image. note that imagecopyresized() is possibly quicker, but
// imagecopyresampled() gives better quality.
imagecopyresampled($out, $in, 0, 0, $src_x, $src_y, $new_w, $new_h, $w, $h);
// output to browser.
header('Content-Type: image/png');
imagepng($out);
exit;
} else {
echo 'image too small';
}
?>
Using Imagick :
define('PHOTO_WIDTH_THUMB', 700);
define('PHOTO_HEIGHT_THUMB', 525);
$image = new Imagick();
$image->readImage($file_source);
$width = $image->getImageWidth();
$height = $image->getImageHeight();
if($width > $height){
$image->thumbnailImage(0, PHOTO_HEIGHT_THUMB);
}else{
$image->thumbnailImage(PHOTO_WIDTH_THUMB, 0);
}
$thumb_width = $image->getImageWidth();
$thumb_height = $image->getImageHeight();
$x = ($thumb_width - PHOTO_WIDTH_THUMB)/2;
$y = ($thumb_height - PHOTO_HEIGHT_THUMB)/2;
$image->cropImage(PHOTO_THUMB_WIDTH, PHOTO_THUMB_HEIGHT, $x, $y);
$image->writeImage($thumb_destination);
$image->clear();
$image->destroy();
unlink($file_source);
I have used GD library to accomplish the resize. Basically what I did is, I calculated the image dimension and then resized the image to dimension 700x525 from the center.
<?php
/*
* PHP GD
* resize an image using GD library
*/
//the image has 700X525 px ie 4:3 ratio
$src = 'demo_files/bobo.jpg';
// Get new sizes
list($width, $height) = getimagesize($src);
$x = 0;
$y = 0;
if($width < $height){
$newwidth = $width;
$newheight = 3/4 * $width;
$x = 0;
$y = $height/2 - $newheight/2;
}else{
$newheight = $height;
$newwidth = 4/3 * $height;
$x=$width/2 - $newwidth/2;
$y=0;
}
$targ_w = 700; //width of the image to be resized to
$targ_h = 525; ////height of the image to be resized to
$jpeg_quality = 90;
$img_r = imagecreatefromjpeg($src);
$dst_r = ImageCreateTrueColor( $targ_w, $targ_h );
imagecopyresampled($dst_r,$img_r,0,0,$x,$y,$targ_w,$targ_h,$newwidth,$newheight);
header('Content-type: image/jpeg');
imagejpeg($dst_r,null,$jpeg_quality);
exit;
?>
i used http://phpthumb.sourceforge.net to have a beutiful solution also with transparent curved edges.
this is an alternative route to solution, might suit someone's need with little configuration.
I have a quick question that I'm not quite sure to set up. I've seen examples elsewhere but nothing specifically like my situation. I would like to resize images using PHP so they're readable and not just wonkily stretched like if you use HTML. If they're not 250 pixels wide, or 160 pixels tall, how can I resize the picture so it's proportionate but fits within that space?
Thanks!
PHP does not manipulate images directly. You will need to use an image manipulation library such as gd or ImageMagick to accomplish this goal.
In ImageMagick, image resizing is accomplished like this:
$thumb = new Imagick('myimage.gif');
$thumb->resizeImage(320,240,Imagick::FILTER_LANCZOS,1);
$thumb->writeImage('mythumb.gif');
With GD, you can do it like this:
<?php
// The file
$filename = 'test.jpg';
$percent = 0.5;
// Content type
header('Content-Type: image/jpeg');
// Get new dimensions
list($width, $height) = getimagesize($filename);
$new_width = $width * $percent;
$new_height = $height * $percent;
// Resample
$image_p = imagecreatetruecolor($new_width, $new_height);
$image = imagecreatefromjpeg($filename);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
// Output
imagejpeg($image_p, null, 100);
?>
Ok, so below is an Image object that I use in my store. It maintains scale - requires GD
<?php
class Store_Model_Image extends My_Model_Abstract
{
const PATH = STORE_MODEL_IMAGE_PATH;
const URL = "/store-assets/product-images/";
public function get_image_url($width, $height)
{
$old_file = self::PATH . $this->get_filename();
$basename = pathinfo($old_file, PATHINFO_FILENAME);
$new_name = sprintf("%s_%sx%s.jpg", $basename, $width, $height);
if(file_exists(self::PATH . $new_name))
{
return self::URL . $new_name;
}
else
{
list($width_orig, $height_orig, $image_type) = #getimagesize($old_file);
$img = FALSE;
// Get the image and create a thumbnail
switch($image_type)
{
case 1:
$img = #imagecreatefromgif($old_file);
break;
case 2:
$img = #imagecreatefromjpeg($old_file);
break;
case 3:
$img = #imagecreatefrompng($old_file);
break;
}
if(!$img)
{
throw new Zend_Exception("ERROR: Could not create image handle from path.");
}
// Build the thumbnail
if($width_orig > $height_orig)
{
$width_ratio = $width / $width_orig;
$new_width = $width;
$new_height = $height_orig * $width_ratio;
}
else
{
$height_ratio = $height / $height_orig;
$new_width = $width_orig * $height_ratio;
$new_height = $height;
}
$new_img = #imagecreatetruecolor($new_width, $new_height);
// Fill the image black
if(!#imagefilledrectangle($new_img, 0, 0, $new_width, $new_height, 0))
{
throw new Zend_Exception("ERROR: Could not fill new image");
}
if(!#imagecopyresampled($new_img, $img, 0, 0, 0, 0, $new_width, $new_height, $width_orig, $height_orig))
{
throw new Zend_Exception("ERROR: Could not resize old image onto new bg.");
}
// Use a output buffering to load the image into a variable
ob_start();
imagejpeg($new_img, NULL, 100);
$image_contents = ob_get_contents();
ob_end_clean();
// lastly (for the example) we are writing the string to a file
$fh = fopen(self::PATH . $new_name, "a+");
fwrite($fh, $image_contents);
fclose($fh);
return self::URL . $new_name;
}
}
}
I resize the image at request time, so the first time the page loads an image will be resized to the required size for the template. (this means I don't have to crash a shared host trying to regenerate image thumbnails everytime my design changes)
So in the template you pass your image object, and when you need a image thumb,
<img src="<?php echo $image->get_image_url(100, 100); ?>" />
you now have a 100x100 thumb, which is saved to the Server for reuse at a later date
gd and imagemagick are two tools that may work for you
http://php.net/manual/en/book.image.php
http://php.net/manual/en/book.imagick.php
Here is something I used to use
class cropImage{
var $imgSrc,$myImage,$cropHeight,$cropWidth,$x,$y,$thumb;
function setImage($image,$moduleWidth,$moduleHeight,$cropPercent = "1") {
//Your Image
$this->imgSrc = $image;
//getting the image dimensions
list($width, $height) = getimagesize($this->imgSrc);
//create image from the jpeg
$this->myImage = imagecreatefromjpeg($this->imgSrc) or die("Error: Cannot find image!");
if($width > $height) $biggestSide = $width; //find biggest length
else $biggestSide = $height;
//The crop size will be half that of the largest side
//$cropPercent = 1.55; // This will zoom in to 50% zoom (crop)
if(!$cropPercent) {
$cropPercent = 1.50;
}
$this->cropWidth = $moduleWidth*$cropPercent;
$this->cropHeight = $moduleHeight*$cropPercent;
//$this->cropWidth = $biggestSide*$cropPercent;
//$this->cropHeight = $biggestSide*$cropPercent;
//getting the top left coordinate
$this->x = ($width-$this->cropWidth)/2;
$this->y = ($height-$this->cropHeight)/2;
}
function createThumb($moduleWidth,$moduleHeight){
$thumbSize = 495; // will create a 250 x 250 thumb
$this->thumb = imagecreatetruecolor($moduleWidth, $moduleHeight);
//$this->thumb = imagecreatetruecolor($thumbSize, $thumbSize);
imagecopyresampled($this->thumb, $this->myImage, 0, 0,$this->x, $this->y, $moduleWidth, $moduleHeight, $this->cropWidth, $this->cropHeight);
//imagecopyresampled($this->thumb, $this->myImage, 0, 0,$this->x, $this->y, $thumbSize, $thumbSize, $this->cropWidth, $this->cropHeight);
}
function renderImage(){
header('Content-type: image/jpeg');
imagejpeg($this->thumb);
imagedestroy($this->thumb);
}
}
Call it by using
$image = new cropImage;
$image->setImage($imagepath,$moduleWidth,$moduleHeight,$scaleRelation);
$image->createThumb($moduleWidth,$moduleHeight);
$image->renderImage();
Use GD or ImageMagick. Here you may find a real production example of code (used by MediaWiki) that supports consoled ImageMagick interface (transformImageMagick method), ImageMagick extension interface (transformImageMagickExt method) and GD (transformGd method).
There a simple to use, open source library called PHP Image Magician that will can help you out.
Example of basis usage:
$magicianObj = new imageLib('racecar.jpg');
$magicianObj -> resizeImage(100, 200, 'crop');
$magicianObj -> saveImage('racecar_small.png');
I have a php script which saves the original image, then resizes it - one thumbnail and one larger image for web viewing. This works well except with some images the quality is terrible. It seems to be saved with a very low colour pallet. You can see the result at http://kalpaitch.com/index.php?filter=white - click on the first thumbnail with the title 'white white white'
Below is the code used for the image resampling:
function resizeImg($name, $extension, $size1, $size2) {
if (preg_match('/jpg|jpeg|JPG|JPEG/',$extension)){
$image = imagecreatefromjpeg($name);
}
if (preg_match('/gif|GIF/',$extension)){
$image = imagecreatefromgif($name);
}
$old_width = imageSX($image);
$old_height = imageSY($image);
$old_aspect_ratio = $old_width/$old_height;
if($size2 == 0){
$new_aspect_ratio = $old_aspect_ratio;
if($old_width > $old_height){
$new_width = $size1;
$new_height = $new_width / $old_aspect_ratio;
} else {
$new_height = $size1;
$new_width = $new_height * $old_aspect_ratio;
}
} elseif($size2 > 0){
$new_aspect_ratio = $size1/$size2;
//for landscape potographs
if($old_aspect_ratio >= $new_aspect_ratio) {
$x1 = round(($old_width - ($old_width * ($new_aspect_ratio/$old_aspect_ratio)))/2);
$old_width = round($old_width * ($new_aspect_ratio/$old_aspect_ratio));
$y1 = 0;
$new_width = $size1;
$new_height = $size2;
//for portrait photographs
} else{
$x1 = 0;
$y1 = 0;
$old_height = round($old_width/$new_aspect_ratio);
$new_width = $size1;
$new_height = $size2;
}
}
$new_image = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($new_image, $image, 0, 0, $x1, $y1, $new_width, $new_height, $old_width, $old_height);
return $new_image;
Many Thanks
P.S.
[photos removed from server]
And here is the rest of the upload code:
// Move the original to the right place
$result = #move_uploaded_file($image['tmp_name'], $origlocation);
// Resize the image and save the thumbnail
$new_image = resizeImg($origlocation, $extension, 500, 0);
if (preg_match("/gif/",$extension)){
imagegif($new_image, $normallocation);
} else {
imagejpeg($new_image, $normallocation);
}
// Resize the image and save the thumbnail
$new_image = resizeImg($origlocation, $extension, 190, 120);
if (preg_match("/gif/",$extension)){
imagegif($new_image, $thumblocation);
} else {
imagejpeg($new_image, $thumblocation);
}
The loss in quality is down not to imagecopyresampled(), but to the JPEG compression. Unfortunately, GD's compression algorithms are no match to Photoshop's - in fact, very few are. But you can improve the result: GD's default JPG compression level is 75 of 100.
You can raise the quality using the third parameter to imagejpeg() (which I assume you are using for the final output):
imagejpeg ( $new_image, null, 99);
Play around in the 90-100 range. The image will become larger in file size than the original - that is going to be the price you pay. But it should be possible to achieve comparable quality.
Alternatively, as John Himmelman already says in the comments, try using imagepng() for better quality - also at the price of a notably larger file size, of course.
well, php.net documentation says you should have a imagecreatetruecolor() image for your dest_image if you want to avoid using only a 255 color palette but you already do that.
I guess an alternative would be to use an external tools such as imagemagick with a system() call.
The quick an dirty trick is to make the thumbnails 1000 x 1000 pixels (or more) on imagecopyresized() then set the JPEG quality to 20 or less on imagejpeg($img, $savePath, 20);. The output will usually be smaller than 100 kb.
Let the client CSS do the resizing and the pictures will be fast to load and look flawless in modern browsers when scaled to thumbnail size.
function img_resize( $tmpname, $size, $save_dir, $save_name, $maxisheight = 0 )
{
$save_dir .= ( substr($save_dir,-1) != "/") ? "/" : "";
$gis = getimagesize($tmpname);
$type = $gis[2];
switch($type)
{
case "1": $imorig = imagecreatefromgif($tmpname); break;
case "2": $imorig = imagecreatefromjpeg($tmpname);break;
case "3": $imorig = imagecreatefrompng($tmpname); break;
default: $imorig = imagecreatefromjpeg($tmpname);
}
$x = imagesx($imorig);
$y = imagesy($imorig);
$woh = (!$maxisheight)? $gis[0] : $gis[1] ;
if($woh <= $size)
{
$aw = $x;
$ah = $y;
}
else
{
if(!$maxisheight)
{
$aw = $size;
$ah = $size * $y / $x;
}
else
{
$aw = $size * $x / $y;
$ah = $size;
}
}
$im = imagecreatetruecolor($aw,$ah);
if (imagecopyresampled($im,$imorig , 0,0,0,0,$aw,$ah,$x,$y))
if (imagejpeg($im, $save_dir.$save_name))
return true;
else
return false;
}
I'd like crop an image in PHP and save the file. I know your supposed to use the GD library but i'm not sure how. Any ideas?
Thanks
You could use imagecopy to crop a required part of an image. The command goes like this:
imagecopy (
resource $dst_im - the image object ,
resource $src_im - destination image ,
int $dst_x - x coordinate in the destination image (use 0) ,
int $dst_y - y coordinate in the destination image (use 0) ,
int $src_x - x coordinate in the source image you want to crop ,
int $src_y - y coordinate in the source image you want to crop ,
int $src_w - crop width ,
int $src_h - crop height
)
Code from PHP.net - a 80x40 px image is cropped from a source image
<?php
// Create image instances
$src = imagecreatefromgif('php.gif');
$dest = imagecreatetruecolor(80, 40);
// Copy
imagecopy($dest, $src, 0, 0, 20, 13, 80, 40);
// Output and free from memory
header('Content-Type: image/gif');
imagegif($dest);
imagedestroy($dest);
imagedestroy($src);
?>
This function will crop image maintaining image aspect ratio :)
function resize_image_crop($image, $width, $height)
{
$w = #imagesx($image); //current width
$h = #imagesy($image); //current height
if ((!$w) || (!$h)) { $GLOBALS['errors'][] = 'Image couldn\'t be resized because it wasn\'t a valid image.'; return false; }
if (($w == $width) && ($h == $height)) { return $image; } //no resizing needed
$ratio = $width / $w; //try max width first...
$new_w = $width;
$new_h = $h * $ratio;
if ($new_h < $height) { //if that created an image smaller than what we wanted, try the other way
$ratio = $height / $h;
$new_h = $height;
$new_w = $w * $ratio;
}
$image2 = imagecreatetruecolor ($new_w, $new_h);
imagecopyresampled($image2,$image, 0, 0, 0, 0, $new_w, $new_h, $w, $h);
if (($new_h != $height) || ($new_w != $width)) { //check to see if cropping needs to happen
$image3 = imagecreatetruecolor ($width, $height);
if ($new_h > $height) { //crop vertically
$extra = $new_h - $height;
$x = 0; //source x
$y = round($extra / 2); //source y
imagecopyresampled($image3,$image2, 0, 0, $x, $y, $width, $height, $width, $height);
} else {
$extra = $new_w - $width;
$x = round($extra / 2); //source x
$y = 0; //source y
imagecopyresampled($image3,$image2, 0, 0, $x, $y, $width, $height, $width, $height);
}
imagedestroy($image2);
return $image3;
} else {
return $image2;
}
}
To crop an image using GD you need to use a combination of GD methods, and if you look at "Example #1" on PHP's documentation of the imagecopyresampled method, it shows you how to crop and output an image, you would just need to add some code to that to capture and write the output to a file...
http://us2.php.net/manual/en/function.imagecopyresampled.php
There are also other options, including Image Magick which, if installed on your server, can be accessed directly using PHP's exec method (or similar) or you can install the PHP Imagick extension, which yields higher quality images and, in my opinion, is a little more intuitive and flexible to work with.
Finally, I've used the open source PHPThumb class library, which has a pretty simple interface and can work with multiple options depending on what's on your server, including ImageMagick and GD.
I use this script in some projects and it's pretty easy to use:
http://shiftingpixel.com/2008/03/03/smart-image-resizer/
The script requires PHP 5.1.0 (which is out since 2005-11-24 - time to upgrade if not yet at this version) and GD (which is rarely missing from good Web hosts).
Here is an example of it's use in your HTML:
<img src="/image.php/coffee-bean.jpg?width=200&height=200&image=/wp-content/uploads/2008/03/coffee-bean.jpg" alt="Coffee Bean" />
I just created this function and it works for my needs, creating a centered and cropped thumbnail image. It is streamlined and doesn't require multiple imagecopy calls like shown in webGautam's answer.
Provide the image path, the final width and height, and optionally the quality of the image. I made this for creating thumbnails, so all images are saved as JPGs, you can edit it to accommodate other image types if you require them. The main point here is the math and method of using imagecopyresampled to produce a thumbnail. Images are saved using the same name, plus the image size.
function resize_crop_image($image_path, $end_width, $end_height, $quality = '') {
if ($end_width < 1) $end_width = 100;
if ($end_height < 1) $end_height = 100;
if ($quality < 1 || $quality > 100) $quality = 60;
$image = false;
$dot = strrpos($image_path,'.');
$file = substr($image_path,0,$dot).'-'.$end_width.'x'.$end_height.'.jpg';
$ext = substr($image_path,$dot+1);
if ($ext == 'jpg' || $ext == 'jpeg') $image = #imagecreatefromjpeg($image_path);
elseif($ext == 'gif') $image = #imagecreatefromgif($image_path);
elseif($ext == 'png') $image = #imagecreatefrompng($image_path);
if ($image) {
$width = imagesx($image);
$height = imagesy($image);
$scale = max($end_width/$width, $end_height/$height);
$new_width = floor($scale*$width);
$new_height = floor($scale*$height);
$x = ($new_width != $end_width ? ($width - $end_width) / 2 : 0);
$y = ($new_height != $end_height ? ($height - $end_height) / 2 : 0);
$new_image = #imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($new_image,$image,0,0,$x,$y,$new_width,$new_height,$width - $x,$height - $y);
imagedestroy($image);
imagejpeg($new_image,$file,$quality);
imagedestroy($new_image);
return $file;
}
return false;
}
You can use below method to crop image,
/*parameters are
$image =source image name
$width = target width
$height = height of image
$scale = scale of image*/
function resizeImage($image,$width,$height,$scale) {
//generate new image height and width of source image
$newImageWidth = ceil($width * $scale);
$newImageHeight = ceil($height * $scale);
//Create a new true color image
$newImage = imagecreatetruecolor($newImageWidth,$newImageHeight);
//Create a new image from file
$source = imagecreatefromjpeg($image);
//Copy and resize part of an image with resampling
imagecopyresampled($newImage,$source,0,0,0,0,$newImageWidth,$newImageHeight,$width,$height);
//Output image to file
imagejpeg($newImage,$image,90);
//set rights on image file
chmod($image, 0777);
//return crop image
return $image;
}