Image resizing is almost mandatory for any web app. Let's take, for example, a web store where each item has one or more images related to it (e.g. a thumbnail, full size image, medium size, etc.).
There should exist an intelligent and easy way of doing this; especially, if you're dealing with different file types or transparency is involved. For this reason, I decided to build my own class, so I don't have to retype repetitive routines every time I start a new project.
Your PHP installation must have access to the GD2 library in order to successfully run most of the code shown here. More information about image manipulation with the GD library and about the library itself can be found in the PHP official docs.
<?php
class img {
public $image;
private $target;
/*
Stores image data in an array.
---
String $source: Path to the image to be copied.
String $target: Target directory for the image.
*/
function __construct($source,$target=null) {
// Retrieve image information (size, type, etc.).
$this->image = getimagesize($source);
if (!is_array($this->image)) {
// Probably not an image file, or the file type is not supported by getimagesize().
return false;
}
switch ($this->image['mime']) {
case 'image/jpeg':
$this->image['ext'] = '.jpg';
break;
case 'image/png':
$this->image['ext'] = '.png';
break;
case 'image/gif':
$this->image['ext'] = '.gif';
break;
default:
// File type not supported by this class. Bye.
return false;
}
First thigs first. The img class is defined, and the attributes $image and $target are defined as well. The $image attribute is an array that holds image data while the $target attribute is the path to the directory where the image will be moved.
As usual, the first function to define inside a class is the constructor; in PHP, __construct is the reserved word used to declare a constructor. The constructor receives the path of the image to be resized ($source) and a $target path if the user wants to move the image file from its current location.
getimagesize($source) returns an array with the dimensions of the image, its file type, etc. This is done to identify whether the image is a jpeg, a png or a gif. If the image type matches any of these supported formats by the class, the corresponding file extension is saved in $this->image['ext']. If none of the supported types by the class is matched, a false is returned by the constructor.
// Name of the file without extension.
$name = preg_replace('/..*$/i','',basename($source));
$this->image['name'] = $name;
$this->image['target'] = $target;
$this->image['source'] = $source;
$source is the full path to the source image. All directories and the file extension are stripped away from the $source path, so the only thing left is the name of the file. The name, the target directory, and the source are saved into the $image array. In this way, they will be available anywhere within or outside the class ($image is a public attribute).
$i = 0; // Initialize counter.
if ($this->image['target'] != null) {
// Give a URL-friendly name to the image.
$this->image['name'] = strtolower(preg_replace('/[^0-9a-z-]+/i','',str_replace(' ','-',$name)));
// Check if there's an image with the same name in the target directory.
while (file_exists($this->image['target'] . $this->image['name'] . '.gif') or file_exists($this->image['target'] . $this->image['name'] . '.jpg') or file_exists($this->image['target'] . $this->image['name'] . '.png')) {
// If there's already one, append a number to the end of the name, until we get an available one.
$this->image['name'] = $name . '-' . $i;
$i++;
}
$this->image['path'] = $this->image['target'] . $this->image['name'] . $this->image['ext'];
// If the path doesn't exist.
if (!file_exists($this->image['target'])) {
// Create it.
mkdir($this->image['target'], 0777, TRUE);
}
// Moves image from current to target location.
if (!rename($this->image['source'],$this->image['path'])) {
return false; // Return false on error.
}
} else {
$this->image['target'] = dirname($this->image['source']) . '/';
$this->image['path'] = $this->image['source'];
}
If a target path is provided, a URL-friendly name is generated for the image (to avoid unwanted characters and/or spaces). Then, the target directory is repeatedly checked for files having the same name as the one just generated. If an image with the same name is matched, the script appends a number to the end of the file until no image is matched. For example, if the name generated for an image is LoL-Cat.jpg and the target folder already has an image with this name, the script will try with other names like LoL-Cat-0.jpg or LoL-Cat-1.jpg, until it finds an available one. The image is then moved to the target path.
The folder of the source image is asigned to $this->image['target'] if no target path is provided.
// Stores an image handler into the attribute $this->image.
switch ($this->image['mime']) {
case 'image/jpeg':
$this->image['handler'] = imagecreatefromjpeg($this->image['path']);
break;
case 'image/png':
$this->image['handler'] = imagecreatefrompng($this->image['path']);
break;
case 'image/gif':
$this->image['handler'] = imagecreatefromgif($this->image['path']);
break;
}
}
$this->image['path'] is the final name for the image. That is: new location + new name + true extension.
An image handler is created based on the type of the image and its path. If the image is a jpeg file, for example, the function imagecreatefromjpeg() is used. The handler (or identifier) allows the operation over a copy of the image.
/*
Copies and resizes an image.
---
String $path: Target path for the image copy. This must include the name of the file! (e.g. c:/target_path/target_name.png)
String $suffix: A suffix appended to name of the copy.
Integer $width, $height: Limits OR dimensions for the new copy.
Bool $scale: If TRUE, the image is scaled. $width and $height are taken as size limits rather than final dimensions.
Integer $cWidth, $cHeight: These are the dimensions of the canvas of the image copy (i.e. The ultimate size of the image).
*/
function imgCopy($path,$suffix,$width=NULL,$height=NULL,$scale=FALSE,$x=0,$y=0,$cWidth=NULL,$cHeight=NULL) {
This is where the magic happens. This function basically makes a copy of an image and moves it to a $path location. Naturally, a new $width and $height for the image can be asigned. These $width and $height parameters can either be the new dimensions for the image or dimension limits.
// If $path is null, generate a default $path and modify it until there's no other image with the same name.
if (is_null($path)) {
if (is_null($suffix) == true) {
$suffix = '';
}
$i = 0;
// Check if there's an image with the same name in the target directory.
while (file_exists($this->image['target'] . $this->image['name'] . $suffix . '.gif') or file_exists($this->image['target'] . $this->image['name'] . $suffix . '.jpg') or file_exists($this->image['target'] . $this->image['name'] . $suffix . '.png')) {
// If there's already one, append a number to the end of the name, until we get an available one.
$this->image['name'] = $this->image['name'] . '-' . $i;
$i++;
}
$path = $this->image['target'] . $this->image['name'] . $suffix . '.png';
} else { // This "else" can be tremendously improved! [Improve me *_* plzz]
// If the path doesn't exist.
if (!file_exists(dirname($path))) {
// Create it.
mkdir(dirname($path), 0777, TRUE);
}
}
// If both $width and $height aren't numeric OR one of them is zero, there's nothing to do. Bye.
if (((!is_numeric($width) and !is_null($width)) and (!is_numeric($height) and is_null($height))) || $width === 0 || $height === 0 || $cWidth === 0 || $cHeight === 0)
return FALSE;
What have we done so far? We received an image to be resized/copied, and then we moved that image to a target place or just left it where it was. Also, we checked if that image had a supported file type by this class (jpeg, png or gif).
If a $path is passed to the imgCopy function, then the copy of the image will be placed there. If no $path is passed, the copy is placed in the directory of the source image, and an available name is automatically created.
if ($scale) { // If $scale is true, then $width and $height are limits, not final dimensions.
// If $width is the only size limit and the image is bigger...
if (($this->image['0'] > $width) and is_null($height) and is_numeric($width)) {
$new_width = $width;
$new_height = (int)(($new_width / $this->image['0']) * $this->image['1']);
// If $height is the only size limit and the image is bigger...
} elseif (($this->image['1'] > $height) and is_null($width) and is_numeric($height)) {
$new_height = $height;
$new_width = (int)(($new_height / $this->image['1']) * $this->image['0']);
// If both $height and $width are limits and either of the image dimensions exceeds them...
} elseif ((($this->image['0'] > $width) || ($this->image['1'] > $height)) and is_numeric($width) and is_numeric($height)) {
// Is the ratio between the original width and original height of the image greater than
// the ratio between $width and $height restrictions?
if (($this->image['0'] / $this->image['1']) > ($width / $height)) {
$new_width = $width;
$new_height = (int)(($new_width / $this->image['0']) * $this->image['1']);
} else {
$new_height = $height;
$new_width = (int)(($new_height / $this->image['1']) * $this->image['0']);
}
} else {
// The image is fine. Don't resize it.
$new_width = $this->image['0'];
$new_height = $this->image['1'];
}
} else { // Don't scale the image.
// $width and $height are the new dimensions.
$new_width = $width;
$new_height = $height;
}
// Asign default dimensions for the canvas.
if (is_null($cWidth)) {
$cWidth = $new_width;
}
if (is_null($cHeight)) {
$cHeight = $new_height;
}
If $scale is true, $width and $height are limits. The dimensions of the copy β $new_width and $new_height β are calculated based on these restrictions so they won't exceed any of them. Remember that $this->image['0'] and $this->image['1'] are the original dimensions of the source image.
The next step is dealing with transparency. If the image is a png file, then we're going to preserve its transparency; if it's a gif, we'll replace the transparent pixels with white ones.
Firstly, a black canvas is created with the dimensions specified by the user (or the ones set by default). If it's a png image, the canvas is filled with a fully transparent "color", and "the flag to save full alpha channel information is set". That means that the transparency of the image will be saved. Finally, if the image is a gif file, the canvas is filled with a cute white color.
$canvas = imagecreatetruecolor($cWidth,$cHeight); // Creates a black canvas with input dimensions.
if($this->image['ext'] == '.png') {
imagealphablending($canvas, FALSE);
$colorTransparent = imagecolorallocatealpha($canvas, 0, 0, 0, 127);
imagefill($canvas, 0, 0, $colorTransparent);
// Save transparency.
imagesavealpha($canvas, TRUE);
} elseif($this->image['ext'] == '.gif') {
$white = imagecolorallocate($canvas, 255, 255, 255);
imagefill($canvas, 0, 0, $white);
}
The source image is merged with the (black, white or transparent) canvas and finally saved in $path.
$x_dest = 0;
$y_dest = 0;
$dst_width = $new_width;
$dst_height = $new_height;
$src_width = $this->image['0'];
$src_height = $this->image['1'];
if (!imagecopyresampled($canvas,$this->image['handler'],$x_dest,$y_dest,$x,$y,$dst_width,$dst_height,$src_width,$src_height) || !imagepng($canvas,$path,9)) {
return FALSE; // Error merging or saving output image.
} else {
return TRUE; // Success!
}
}
}
?>
imagecopyresampled() copies a portion of an image to another. You can obviously find more info about this function in the PHP official documentation.
You'll find a zip file with this class and a working example under the downloads section of this post. The class you'll find there has an extra method for generating thumbnails. It's a shorter function that internally calls the imgCopy() method.
Hope you found this article useful!