Skip to content

Instantly share code, notes, and snippets.

@eberfreitas
Created August 25, 2010 19:09
Show Gist options
  • Save eberfreitas/550080 to your computer and use it in GitHub Desktop.
Save eberfreitas/550080 to your computer and use it in GitHub Desktop.
A simple class to handle images in PHP with GD
<?php
/**
* EasyGD - A simple class to handle images in PHP with GD
*
* Basic Usage:
*
* $image = new EasyGD('image.jpg');
* $image->crop(100, 50)
* ->width(50)
* ->save('/path', 'newfile.' . $image->extension)
* ->display()
* ->end();
*
* @author Éber Freitas Dias <[email protected]>
**/
class EasyGD {
/******************************/
/* GLOBALS FOR ERROR HANDLING */
/******************************/
const ERROR_OPEN = "It wasn't possible to open the provided image.";
const ERROR_SUPPORT = "This file format is not suported by EasyGD. Use a jpg or png image instead.";
const ERROR_RESOURCE = "There is no resource for this function. Please, run EasyGD::startFrom* to define one.";
const ERROR_CROP = "One of the dimensions provided are bigger than the actual image.";
const ERROR_ARGUMENTS = "The function was called with missing arguments.";
const ERROR_SERVER = "The server doesn't support this function. Please, contact you server admin.";
const ERROR_DISPLAY = "It wasn't possible to display the image.";
const ERROR_SAVE = "It wasn't possible to save the image.";
/**********************/
/* GENERAL ATTRIBUTES */
/**********************/
/**
* @var array List of supported extensions
**/
private $ext2mime = array(
'jpg' => 'image/jpeg',
'png' => 'image/png'
);
/**
* @var array List of supported mime types
**/
private $mime2ext = array(
'image/jpeg' => 'jpg',
'image/pjpeg' => 'jpg',
'image/png' => 'png'
);
/**
* @var array List of positions for cropping. The first letter represents
* the X axis (Left, Center, Right) and the second letter
* represents the Y axis (Top, Center, Bottom)
**/
private $positionTypes = array(
'LT', 'CT', 'RT',
'LC', 'CC', 'RC',
'LB', 'CB', 'RB'
);
/**
* @var resource Variable where the current resource will be stored
**/
private $resource;
/**
* @var int Width of the image
**/
private $x;
/**
* @var int Height of the image
**/
private $y;
/**
* @var string Holds the original file name
**/
private $filename;
/**
* @var string Extension from the current image being manipulated
**/
private $extension;
/**
* @var array Holds the default values from the class in order to reset it
* properly
**/
private $defaultVals;
/**
* @var string Holds the filename assigned by EasyGD::save
**/
public $newFilename;
/**
* @var int This stores the image quality for JPG, from 0 to 100
* (http://php.net/imagejpeg)
**/
public $quality = 100;
/**
* @var int This stores the image compression for PNG, from 0 to 9
* (http://php.net/imagepng)
**/
public $compression = 0;
/****************/
/* CONSTRUCTORS */
/****************/
public function __construct($o = null, $ext = null, $filename = null) {
foreach ($this as $k => $v) {
$this->defaultVals[$k] = $v;
}
if ($o) {
if (is_array($o)) {
return $this->startFromFile($o);
}
if (is_resource($o)) {
return $this->startFromResource($o, $ext, $filename);
}
if (is_string($o)) {
if (filter_var($o, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
return $this->startFromUrl($o);
}
return $this->startFromFile($o);
}
}
}
public function __destruct() {
$this->_reset();
}
/*******************/
/* START FUNCTIONS */
/*******************/
/**
* Use this method to start EasyGD from a file.
* @param string|array $file This can receive a string with the filename
* Ex. 'image.jpg' or an array from the $_FILES
* global Ex. $_FILES['myfile']
**/
public function startFromFile($file) {
if (!is_array($file)) {
$ext = $this->_getExtension($file);
if (!file_exists($file)) {
$this->_error(self::ERROR_OPEN);
}
if (!$this->_checkSupport(null, $ext)) {
$this->_error(self::ERROR_SUPPORT);
}
$this->filename = $file;
$this->extension = $ext;
} else {
if ($file['error'] !== 0) {
$this->_error(self::ERROR_OPEN);
}
if (!$this->_checkSupport($file['type'])) {
$this->_error(self::ERROR_SUPPORT);
}
$this->filename = $file['name'];
$this->extension = $this->mime2ext[$file['type']];
$file = $file['tmp_name'];
}
$this->resource = $this->_create($file);
list($this->x, $this->y) = getimagesize($file);
return $this;
}
/**
* With this method you can start EasyGD from an already started resource
* @param resource $resource The image resource to be used
* @param string $ext The original image extension
* @param string $filename The original filename, not exactly necessary
**/
public function startFromResource($resource, $ext = null, $filename = null) {
if (is_null($ext)) {
$this->_error(self::ERROR_ARGUMENTS);
}
if (!$this->_checkSupport(null, $ext)) {
$this->_error(self::ERROR_SUPPORT);
}
$this->resource = $resource;
$this->filename = !is_null($filename) ? $filename : '';
$this->extension = $ext;
$this->x = imagesx($resource);
$this->y = imagesy($resource);
return $this;
}
/**
* With this method you can start EasyGD to use a remote image from a URL.
* @param string $url The URL of the remote image
**/
public function startFromUrl($url) {
$ext = $this->_getExtension($url);
if (!$this->_checkSupport(null, $ext)) {
$this->_error(self::ERROR_SUPPORT);
}
if (function_exists('file_get_contents')) {
$img = file_get_contents($url);
} elseif (function_exists('curl_init')) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1);
$img = curl_exec($ch);
curl_close($ch);
} else {
$this->_error(self::ERROR_SERVER);
}
$this->resource = imagecreatefromstring($img) or $this->_error(self::ERROR_OPEN);
$this->filename = substr(strrchr($url, "/"), 1);
$this->extension = $ext;
$this->x = imagesx($this->resource);
$this->y = imagesy($this->resource);
return $this;
}
/******************/
/* OUTPUT METHODS */
/******************/
/**
* Returns the x (width) dimension of the current image
**/
public function x() {
return $this->x;
}
/**
* Returns the y (height) dimension of the current image
**/
public function y() {
return $this->y;
}
/**
* Returns the image's filename, if any
**/
public function filename() {
return $this->filename;
}
/**
* Returns image's extension
**/
public function extension() {
return $this->extension;
}
/**
* Returns the image's resource
**/
public function resource() {
return $this->resource;
}
/**
* This method displays the image on the browser directly
**/
public function display() {
if (!$this->_checkResource()) {
$this->_error(self::ERROR_RESOURCE);
}
header('Content-Type: ' . $this->ext2mime[$this->extension]);
if ($this->_display()) {
return $this;
}
$this->_error(self::ERROR_DISPLAY);
}
/**
* This method saves the resource to an image file. You can set the path
* where the file will be saved as well as the file name. If none is given,
* the image will be saved on the current path and the file name will be
* automatically generated.
* @param string $path The path where the image will be stored or the
* filename you want the file to have. In this case, it
* will use the current path.
* @param string $filename The image name to be used. Ex. 'image.jpg'. Don't
* forget to insert the right extension. You can use
* EasyGD::extension to do that.
**/
public function save($path = '', $filename = null) {
$hasExtension = $this->_getExtension($path);
if ($hasExtension) {
$fullPath = $path;
} else {
if (!empty($path) && $path{strlen($path)-1} !== DIRECTORY_SEPARATOR) {
$path = $path . DIRECTORY_SEPARATOR;
}
if (is_null($filename)) {
$filename = time() . "." . $this->extension;
}
$this->newFilename = $filename;
$fullPath = $path . $filename;
}
if ($this->_display($fullPath)) {
return $this;
}
$this->_error(self::ERROR_SAVE);
}
/**********************/
/* ADJUSTMENT METHODS */
/**********************/
/**
* If you are using transparent PNG images you might want to activate its
* alpha channel. That is what this method do.
**/
public function setAlpha() {
if ($this->extension === 'png') {
imagealphablending($this->resource, false);
imagesavealpha($this->resource, true);
}
return $this;
}
/************************/
/* MANIPULATION METHODS */
/************************/
/**
* Method to resize an image
* @param int $width Width to be resized
* @param int $height Height to be resized
**/
public function resize($width, $height) {
if (!$this->_checkResource()) {
$this->_error(self::ERROR_RESOURCE);
}
$newResource = imagecreatetruecolor($width, $height);
imagecopyresampled(
$newResource,
$this->resource,
0, 0, 0, 0,
$width, $height, $this->x, $this->y
);
$this->startFromResource($newResource, $this->extension, $this->filename);
return $this;
}
/**
* Shortcut for the EasyGD::resize function where you can set a with and the
* height is calculated proportionally
* @param int $width New image width to be resized
* @param bool $force If false, returns the original image if the new width
* is larger than the original
**/
public function width($width, $force = true) {
if ($force === true || $width < $this->x) {
return $this->resize($width, round(($this->y / $this->x) * $width));
} else {
return $this;
}
}
/**
* Shortcut for the EasyGD::resize function where you can set a height and
* the width is calculated proportionally
* @param int $height New image height to be resized
* @param bool $force If false, returns the original image if the new height
* is larger than the original
**/
public function height($height, $force = true) {
if ($force === true || $height < $this->y) {
return $this->resize(round(($this->x / $this->y) * $height), $height);
} else {
return $this;
}
}
/**
* Method where you can crop an image. You can use the pre-defined patterns
* with $type or define your own position with $x and $y
* @param int $width Width of the crop
* @param int $height Height of the crop
* @param string $type One of the EasyGD::positionTypes
* @param int $x The X axis to start the crop
* @param int $y The Y axis to start the crop
* @param bool $force If the crop size is larger than the original image and
* $force === true, it will resize the image and then
* crop it
*/
public function crop($width, $height, $type = 'CC', $x = null, $y = null, $force = false) {
if (!$this->_checkResource()) {
$this->_error(self::ERROR_RESOURCE);
}
if (($force === false) && ($width > $this->x || $height > $this->y)) {
$this->_error(self::ERROR_CROP);
}
if ($width > $this->x || $height > $this->y) {
$max = max($width, $height);
if ($this->x > $this->y) {
$this->height($max);
} else {
$this->width($max);
}
}
if (!in_array($type, $this->positionTypes) || is_null($type)) {
$type = 'CC';
}
if (is_null($x) || is_null($y)) {
$axis = $this->_calcPosition($type, $this->x, $this->y, $width, $height);
extract($axis);
} else {
$leftX = $this->x - $width;
$leftY = $this->y - $height;
if ($x + $width > $this->x) {
$x = $leftX;
}
if ($y + $height > $this->y) {
$y = $leftY;
}
}
$newResource = imagecreatetruecolor($width, $height);
imagecopy($newResource, $this->resource, 0, 0, $x, $y, $this->x, $this->y);
$this->startFromResource($newResource, $this->extension, $this->filename);
return $this;
}
/**
* This will destroy the current resource and reset the variables so you can
* start it all over again! :)
**/
public function end() {
return $this->_reset();
}
/*********************/
/* EDITION FUNCTIONS */
/*********************/
/**
* This method will place a watermark on the current working image.
* @param string $file Path to the file being used as a watermark
* @param string $type One of the EasyGD::positionTypes
* @param int $padding The padding in pixels
* @param int|bool $alpha Sets the transparency level of the watermark or
* preserve PNG transparency if set to 'true'
**/
public function placeWatermark($file = '', $type = 'RB', $padding = 10, $alpha = 100) {
if (!in_array($type, $this->positionTypes) || is_null($type)) {
$type = 'RB';
}
$ext = $this->_getExtension($file);
$function = $this->_getFunction($ext);
$wmResource = $function['create']($file);
if ($ext === 'png' && $alpha === true) {
imagealphablending($wmResource, false);
imagesavealpha($wmResource, true);
}
$wmWidth = imagesx($wmResource);
$wmHeight = imagesy($wmResource);
$newResource = imagecreatetruecolor($this->x, $this->y);
imagecopyresampled(
$newResource,
$this->resource,
0, 0, 0, 0,
$this->x, $this->y, $this->x, $this->y
);
$axis = $this->_calcPosition(
$type,
$this->x, $this->y,
$wmWidth, $wmHeight,
$padding
);
if ($alpha === true) {
imagecopy(
$newResource,
$wmResource,
$axis['x'], $axis['y'],
0, 0,
$wmWidth, $wmHeight
);
} else {
imagecopymerge(
$newResource,
$wmResource,
$axis['x'], $axis['y'],
0, 0,
$wmWidth, $wmHeight,
$alpha
);
}
$this->startFromResource($newResource, $this->extension, $this->filename);
imagedestroy($wmResource);
return $this;
}
/********************/
/* INTERNAL METHODS */
/********************/
private function _calcPosition($type = 'CC', $x, $y, $width, $height, $padding = false) {
$leftX = $x - $width;
$leftY = $y - $height;
$return = array();
switch ($type) {
case "LT":
$return['x'] = $return['y'] = ($padding) ? $padding : 0;
break;
case "CT":
$return['x'] = round($leftX / 2);
$return['y'] = ($padding) ? $padding : 0;
break;
case "RT":
$return['x'] = ($padding) ? $leftX - $padding : $leftX;
$return['y'] = ($padding) ? $padding : 0;
break;
case "LC":
$return['x'] = ($padding) ? $padding : 0;
$return['y'] = round($leftY / 2);
break;
case "CC":
$return['x'] = round($leftX / 2);
$return['y'] = round($leftY / 2);
break;
case "RC":
$return['x'] = ($padding) ? $leftX - $padding : $leftX;
$return['y'] = round($leftY / 2);
break;
case "LB":
$return['x'] = ($padding) ? $padding : 0;
$return['y'] = ($padding) ? $leftY - $padding : $leftY;
break;
case "CB":
$return['x'] = round($leftX / 2);
$return['y'] = ($padding) ? $leftY - $padding : $leftY;
break;
case "RB":
$return['x'] = ($padding) ? $leftX - $padding : $leftX;
$return['y'] = ($padding) ? $leftY - $padding : $leftY;
break;
}
return $return;
}
private function _checkResource() {
return gettype($this->resource) === 'resource';
}
private function _checkSupport($mime = null, $ext = null) {
if (!is_null($mime)) {
$mimes = array_keys($this->mime2ext);
return in_array($mime, $mimes);
} elseif (!is_null($ext)) {
return in_array($ext, $this->mime2ext);
}
return false;
}
private function _create($file) {
switch ($this->extension) {
case 'jpg':
return imagecreatefromjpeg($file);
break;
case 'png':
return imagecreatefrompng($file);
break;
}
}
private function _display($path = null) {
switch ($this->extension) {
case 'jpg':
$quality = ($this->quality > 100 || $this->quality < 0)
? 100 : $this->quality;
return imagejpeg($this->resource, $path, $quality);
break;
case 'png':
$compression = ($this->compression > 9 || $this->compression < 0)
? 9 : $this->compression;
return imagepng($this->resource, $path, $compression);
break;
}
}
private function _getExtension($file) {
$file = basename($file);
return strtolower(substr(strrchr($file, "."), 1));
}
private function _reset() {
if ($this->_checkResource()) {
imagedestroy($this->resource);
}
foreach ($this as $k => $v) {
$this->{$k} = isset($this->defaultVals[$k]) ? $this->defaultVals[$k] : null;
}
return true;
}
private function _error($msg) {
$this->_reset();
throw new Exception($msg);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment