<?php

class imageTransform {
	var $image;
	var $target;
	var $size;
	var $debug = false; // Show errors
	var $quality = 100; // Result image quality
	var $cache = false; // Cache disabled
	var $expire = 172800; // Cache expiration time in seconds
	var $enlarge = true; // Enlarge image if width or height needed is bigger than original
	var $data = array(); // Image data array
	var $forceType = false; // Force image type create
	var $allowed = array( // Valid image formats
		1 => 'gif',
		2 => 'jpg',
		3 => 'png'
	);

	/**
	* function debug (string $message)
	*
	* Print the error messages
	*/
	function debug ($message) {
		if ($this->debug) {
			$debug = debug_backtrace();

			rsort($debug);

			echo $message;

			foreach ($debug as $k => $v) {
				echo "\n\n".'<br /><br />'.str_repeat('&nbsp;', (2 + $k * 2)).'FILE: '.$v['file'];
				echo "\n".'<br />'.str_repeat('&nbsp;', (2 + $k * 2)).'LINE: '.$v['line'];
			}

			echo "\n".'<br />';
		}

		return false;
	}

	/**
	* function enlarge (boolean $value)
	*
	* When you resize or crop an image, you need the width and height
	* You can disable enlarge an image if the original size is lower than
	* the needed width and height
	*/
	function enlarge ($value) {
		$this->enlarge = $value;
	}

	/**
	* function forceType (integer $type)
	*
	* Force to generate the new image with a concrete type
	*
	* (1 => GIF, 2 => JPEG, 3 => PNG)
	*/
	function forceType ($type) {
		$this->forceType = $type;
	}

	/**
	* function itsImage (string $image)
	*
	* Check if it's a valid image
	*
	* return false:array
	*/
	function itsImage ($image) {
		if ($data = @getImageSize($image)) {
			return empty($this->allowed[$data[2]])?false:$data;
		} else {
			return false;
		}
	}

	/**
	* function cache (string $value)
	*
	* Enable/disable the image cache
	* To enable cache, needs the folder cache path
	*/
	function cache ($value) {
		if ($value === false) {
			$this->cache = false;
		} elseif (($value === true) && (empty($this->cache) || ($this->cache === true) || ($this->cache === false))) {
			$this->cache = false;
			
			$this->debug('The cache folder haven\'t a valid path: '.$this->cache);
		} elseif (!is_dir($value) || !is_writable($value)) {
			$this->cache = false;

			$this->debug('The cache folder isn\'t writable to me: '.$value);
		} else {
			$this->cache = preg_match('#/$#', $value)?$value:($value.'/');
		}
	}

	/**
	* function inlineHeaders (string $name)
	*
	* Print the inline headers to view function
	*/
	function inlineHeaders ($name) {
		header('Content-type: image/'.$this->allowed[$this->data[2]]);
		header('Content-Disposition: inline; filename="'.$name.'"');
	}

	/**
	* function view (string $mode, string $param, boolean $cache = true)
	*
	* View online an resize/crop image. The image result can be cached.
	*/
	function view ($mode, $image, $param,$target = null) {
		if (!($this->data = $this->itsImage($image))) {
			return $this->debug($image.' isn\'t a valid image');
		}
		$name = explode('/', $image);
		$name = end($name);
		$ext = explode('.', $name);
		$ext = strtolower(end($ext));

		if (($mode == 'resize') || ($mode == 'crop')) {
			list($width, $height) = explode('x', $param);

			$width = intval($width);
			$height = intval($height);

			if (($width <= 0) || ($height <= 0)) {
				return $this->debug('No size valid: '.$width.'x'.$height);
			}

			if ((($this->enlarge == false) && ($this->data[0] <= $width) && ($this->data[1] <= $height))
			|| (($this->data[0] == $width) && ($this->data[1] == $height))) {
				$this->inlineHeaders($name);

				echo file_get_contents($image);

				return true;
			}
		}

		$this->inlineHeaders($name);
        
		switch($mode) {
			case 'gray':
				$ok = $this->gray($image, $target, true);
				break;
			case 'rotate':
				$ok = $this->rotate($image, $param, $target, true);
				break;
			case 'resize':
			case 'crop':
				$ok = $this->$mode($image, $width, $height, $target, true);
				break;
			case 'flip':
			case 'flop':
				$ok = $this->flipflop($image, $mode, $target, true);
				break;
			default:
				return $this->debug($mode.' isn\'t a valid mode');
		}

		if ($target == false) {
			return $ok;
		} elseif ($ok == true) {
			echo file_get_contents($target);
		} else {
			return false;
		}
	}

	/**
	* function defineTarget (string $thumb, boolean $view)
	*
	* Assign a valid value to target image
	*
	* return boolean
	*/
	function defineTarget ($thumb, $view) {
		if ($view) {
			$this->target = $thumb ? $thumb : false;
		} elseif (empty($thumb)) {
			if (!is_writable($this->image)) {
				return $this->debug('The original image haven\'t write permissions to me: '.$this->image);
			}

			$this->target = $this->image;
		} else {
			$this->target = $thumb;
			$folder = (dirname($this->target) == '') ? './' : dirname($this->target);

			if (!$this->recursive_path($folder) || !is_writable($folder)) {
				return $this->debug('Can\'t write in the target folder: '.$folder);
			}
		}

		return true;
	}

	/**
	* function gray (string $image, string $thumb = '', string $view = false)
	*
	* Transform an image to grayscale
	*
	* If the parameter $thumb it's set, instead transform the image image,
	* create a new with the location in set.
	*
	* $view condition is used in the online image view funcion
	*
	* return boolean
	*/
	function gray ($image, $thumb = '', $view = false) {
		if (!($this->data = $this->itsImage($image))) {
			return $this->debug($image.' isn\'t a valid image');
		}

		$this->image = $image;

		$this->defineTarget($thumb, $view);

		$thumb = $this->imageDefine();

		$quality = true;

		if ($quality) {
			// More slower but best quality

			for ($c = 0; $c < 256; $c++) {
				$palette[$c] = imageColorAllocate($thumb, $c, $c, $c);
			}

			for ($x = 0; $x < $this->data[0]; $x++) {
				for ($y = 0; $y < $this->data[1]; $y++) {
					$gray = (ImageColorAt($thumb, $x, $y) >> 8) & 0xFF;
					$rgb = imagecolorat($thumb, $x, $y);
					$r = ($rgb >> 16) & 0xFF;
					$g = ($rgb >> 8) & 0xFF;
					$b = $rgb & 0xFF;

					$gs = ($r * 0.299) + ($g * 0.587) + ($b * 0.114);

					imagesetpixel($thumb, $x, $y, $palette[$gs]);
				}
			}
		} else {
			// More faster but less quality

			for ($x = 0; $x < $this->data[0]; $x++) {
				for ($y = 0; $y < $this->data[1]; $y++) {
					$gray = (ImageColorAt($thumb, $x, $y) >> 8) & 0xFF;

					imagesetpixel($thumb, $x, $y, imageColorAllocate($thumb, $gray,$gray,$gray));
				}
			}
		}

		if ($this->imageCreate($thumb)) {
			return true;
		} else {
			return $this->debug('A problem occours creating the image. I can\'t finish the task.');
		}
	}

	/**
	* function resize (string $image, integer $width, integer $height, string $thumb = '', boolean $view = false)
	*
	* This function resize an image maintaining the image proportions.
	*
	* If the parameter $thumb it's set, instead transform the image image,
	* create a new with the location in set.
	*
	* $whith and $height set the max sizes allowed to width and height, not set the
	* sizes in the result image, the result sizes are calculated with this values.
	*
	* $view condition is used in the online image view funcion
	*
	* return boolean
	*/
	function resize ($image, $width, $height, $thumb = '', $view = false) {
		if (!($this->data = $this->itsImage($image))) {
			return $this->debug($image.' isn\'t a valid image');
		}

		if ((($this->enlarge == false) && ($this->data[0] <= $width) && ($this->data[1] <= $height))
		|| (($this->data[0] == $width) && ($this->data[1] == $height))) {
			if (($thumb != '') && ($image != $thumb)) {
				if ($this->defineTarget($thumb, false)) {
					@copy($image, $thumb);
				} else {
					return false;
				}
			}

			return true;
		}

		$this->image = $image;
		$this->size = array($width, $height);

		$this->defineTarget($thumb, $view);

		return $this->_mainTransform();
	}

	/**
	* function flip (string $image, string $mode, string $thumb = '', boolean $view = false)
	*
	* This function create and vertical flip or horizontal flop image.
	*
	* If the parameter $thumb it's set, instead transform the image image,
	* create a new with the location in set.
	*
	* $view condition is used in the online image view funcion
	*
	* return boolean
	*/
	function flipflop ($image, $mode, $thumb = '', $view = false) {
		if (!($this->data = $this->itsImage($image))) {
			return $this->debug($image.' isn\'t a valid image');
		}

		$this->image = $image;

		$this->defineTarget($thumb, $view);

		$newImage = $this->imageDefine();

		$w = $this->data[0];
		$h = $this->data[1];
		$thumb = $this->transparency($newImage, imageCreateTrueColor($w, $h));

		if ($mode == 'flip') {
			for ($x = 0; $x < $w; $x++) {
				//imagecopy($thumb, $newImage, $x, 0, $w - $x - 1, 0, 1, $h); // Little more slow
				imagecopy($thumb, $newImage, $w - $x - 1, 0, $x, 0, 1, $h);
			}
		} elseif ($mode == 'flop') {
			for ($x = 0; $x < $h; $x++) {
				//imagecopy($thumb, $newImage, 0, $x, 0, $h - $x - 1, $w, 1); // Little more slow
				imagecopy($thumb, $newImage, 0, $h - $x - 1, 0, $x, $w, 1);
			}
		} else {
			for ($x = 0; $x < $w; $x++) {
				imagecopy($thumb, $newImage, $w - $x - 1, 0, $x, 0, 1, $h);
			}

			$buffer = $this->transparency($newImage, imageCreateTrueColor($w, 1));

			for ($y = 0; $y < ($h / 2); $y++) {
				imagecopy($buffer, $thumb, 0, 0, 0, $h - $y - 1, $w, 1);
				imagecopy($thumb, $thumb, 0, $h - $y - 1, 0, $y, $w, 1);
				imagecopy($thumb, $buffer, 0, $y, 0, 0, $w, 1);
			}

			imagedestroy($buffer);
		}

		if ($this->imageCreate($thumb)) {
			return true;
		} else {
			return $this->debug('A problem occours creating the image. I can\'t finish the task.');
		}
	}

	/**
	* function quality (string $image, integer $quality, string $thumb = '', boolean = boolean)
	*
	* Create or change one image with different quality
	*
	* return boolean
	*/

	function quality ($image, $quality, $thumb = '', $view = false) {
		if (!($this->data = $this->itsImage($image))) {
			return $this->debug($image.' isn\'t a valid image');
		}

		$this->image = $image;

		$this->defineTarget($thumb, $view);

		$newImage = $this->imageDefine();

		$thumb = $this->transparency($newImage, imageCreateTrueColor($this->data[0], $this->data[1]));

		imagecopy($thumb, $newImage, 0, 0, 0, 0, $this->data[0], $this->data[1]);

		$old_quality = $this->quality;

		$this->quality = intval($quality);

		if ($this->forceType !== false) {
			$this->data[2] = $this->forceType;
		}

		$ok = $this->imageCreate($thumb);

		$this->quality = $old_quality;

		if ($ok) {
			return true;
		} else {
			return $this->debug('A problem occours creating the image. I can\'t finish the task.');
		}
	}

	/**
	* function crop (string $image, integer $width, integer $height, string $thumb = '', boolean $view = false)
	*
	* Cut the image with the desired size. First the image is reduced or enlarged and after it's crop.
	*
	* If the parameter $thumb it's set, instead transform the image image,
	* create a new with the location in set.
	*
	* $view condition is used in the online image view funcion
	*
	* return boolean
	*/
	function crop ($image, $width, $height, $thumb = '', $view = false) {
		if (!($this->data = $this->itsImage($image))) {
			return $this->debug($image.' isn\'t a valid image');
		}

		if ((($this->enlarge == false) && ($this->data[0] <= $width) && ($this->data[1] <= $height))
		|| (($this->data[0] == $width) && ($this->data[1] == $height))) {
			if (($thumb != '') && ($image != $thumb)) {
				if ($this->defineTarget($thumb, false)) {
					@copy($image, $thumb);
				} else {
					return false;
				}
			}

			return true;
		}

		$this->image = $image;
		$this->size = array($width, $height);

		$this->defineTarget($thumb, $view);

		if ($width == $height) {
			$less = ($this->data[0] > $this->data[1]) ? $this->data[1] : $this->data[0];
			$width = $height = $less;
			$posX = intval(($this->data[0] / 2) - ($less / 2));
			$posY = intval(($this->data[1] / 2) - ($less / 2));
		} elseif ($width > $height) {
			list($height, $width, $posX, $posY) = $this->cropSize($this->data[1], $this->data[0], $height, $width);

			if ($posY < 0) {
				list($width, $height, $posY, $posX) = $this->cropSize($this->data[0], $this->data[1], $this->size[0], $this->size[1]);
			}
		} elseif ($width < $height) {
			list($width, $height, $posY, $posX) = $this->cropSize($this->data[0], $this->data[1], $width, $height);

			if ($posX < 0) {
				list($height, $width, $posX, $posY) = $this->cropSize($this->data[1], $this->data[0], $this->size[1], $this->size[0]);
			}
		}

		return $this->_mainTransform($posX, $posY, $width, $height, false);
	}

	/**
	* function cropSize (integer $sourceX, integer $sourceY, integer $targetX, integer $targetY)
	*
	* Calculate the size and coords to the crop action
	*
	* return array
	*/
	function cropSize ($sourceX, $sourceY, $targetX, $targetY) {
		$sizeX = round(($sourceX * $targetX) / (($sourceX * $targetY) / $sourceY));
		$sizeY = $sourceY;
		$coordX = 0;
		$coordY = intval(($sourceX / 2) - ($sizeX / 2));

		return array($sizeX, $sizeY, $coordX, $coordY);
	}

	/**
	* function rotate (string $image, integer $degrees, string $thumb = '', boolean $view = false)
	*
	* Rotate an image
	*
	* return boolean
	*/
	function rotate ($image, $degrees, $thumb = '', $view = false) {
		if (!($this->data = $this->itsImage($image))) {
			return $this->debug($image.' isn\'t a valid image');
		}

		$this->image = $image;

		$this->defineTarget($thumb, $view);

		$newImage = $this->imageDefine();

		if (function_exists('imagerotate')) {
			$thumb = $this->transparency($newImage, imagerotate($newImage, $degrees, 0));
		} else {
			$src_x = imagesx($newImage);
			$src_y = imagesy($newImage);

			if ($degrees == 180) {
				$dest_x = $src_x;
				$dest_y = $src_y;
			} elseif ($src_x <= $src_y) {
				$dest_x = $src_y;
				$dest_y = $src_x;
			} elseif ($src_x >= $src_y) {
				$dest_x = $src_y;
				$dest_y = $src_x;
			}

			$thumb = $this->transparency($newImage, imageCreateTrueColor($dest_x, $dest_y));

			imagealphablending($thumb, false);

			switch ($degrees) {
				case 270:
					for ($y = 0; $y < ($src_y); $y++) {
						for ($x = 0; $x < ($src_x); $x++) {
							$color = imagecolorat($newImage, $x, $y);
							imagesetpixel($thumb, $dest_x - $y - 1, $x, $color);
						}
					}

					break;
				case 90:
					for ($y = 0; $y < ($src_y); $y++) {
						for ($x = 0; $x < ($src_x); $x++) {
							$color = imagecolorat($newImage, $x, $y);
							imagesetpixel($thumb, $y, $dest_y - $x - 1, $color);
						}
					}

					break;
				case 180:
					for ($y = 0; $y < ($src_y); $y++) {
						for ($x = 0; $x < ($src_x); $x++) {
							$color = imagecolorat($newImage, $x, $y);
							imagesetpixel($thumb, $dest_x - $x - 1, $dest_y - $y - 1, $color);
						}
					}

					break;
				default:
					$thumb = $newImage;
			}
		}

		if ($this->imageCreate($thumb)) {
			return true;
		} else {
			return $this->debug('A problem occours creating the image. I can\'t finish the task.');
		}
	}

	/**
	* function _mainTransform (integer $posX=0, integer $posY=0, integer $width=0, integer $height=0, boolean $proportions = true)
	*
	* Process the image transform
	*
	* return boolean
	*/
	function _mainTransform ($posX=0, $posY=0, $width=0, $height=0, $proportions = true) {
		$newImage = $this->imageDefine();

		if (!$newImage) {
			return $this->debug($this->image.' isn\'t JPG, GIF nor PGN');
		}

		if ($proportions) {
			list($dX, $dY) = $this->proportions($this->data[0], $this->data[1]);
			list($width, $height) = array($this->data[0], $this->data[1]);
		} else {
			list($dX, $dY) = array($this->size[0], $this->size[1]);
		}

		list($posX, $posY) = $this->maxSize($posX, $posY, $width, $height);

		$thumb = $this->transparency($newImage, imageCreateTrueColor($dX, $dY));

		imageCopyResampled($thumb, $newImage, 0, 0, $posX, $posY, $dX, $dY, $width, $height);
		
		if ($this->imageCreate($thumb)) {
			return true;
		} else {
			return $this->debug('A problem occours creating the image. I can\'t finish the task.');
		}
	}

	/**
	* function imageDefine (void)
	*
	* Check the image format and create a new image with the same format
	*
	* return resource
	*/
	function imageDefine () {
		switch ($this->data[2]) {
			case 1:
				return imageCreateFromGIF($this->image);
			case 2:
				return imageCreateFromJPEG($this->image);
			case 3:
				return imageCreateFromPNG($this->image);
			default:
				return false;
		}
	}

	/**
	* function imageCreate (resource $thumb)
	*
	* Create a new image from the $thumb resource
	*
	* return booelan
	*/
	function imageCreate ($thumb) {
		if ($this->data[2] == 1) {
			$ok = imageGIF($thumb, $this->target, $this->quality);
		} elseif ($this->data[2] == 3) {
			$quality = 10 - round($this->quality / 10);
			$quality = (($quality < 0)?0:(($quality > 9)?9:$quality));

			$ok = imagepng($thumb, $this->target, $quality);
		} else {
			$ok = imageJPEG($thumb, $this->target, $this->quality);
		}

		if ($ok) {
			imageDestroy($thumb);

			return true;
		} else {
			return false;
		}
	}

	/**
	* function transparency (resource $original, resource $new)
	*
	* Check and aply the transparency to an image
	*
	* return resource
	*/
	function transparency ($original, $new) {
		if (($this->data[2] !== 1) && ($this->data[2] !== 3)) {
			return $new;
		}

		$trans_index = imagecolortransparent($original);

		if ($trans_index >= 0) {
			$trans_color = imagecolorsforindex($original, $trans_index);
			$trans_index = imagecolorallocate($new, $trans_index['red'], $trans_index['green'], $trans_index['blue']);

			imagefill($new, 0, 0, $trans_index);
			imagecolortransparent($new, $trans_index);
		} else if ($this->data[2] === 3) {
			imagealphablending($new, false);

			$colorTransparent = imagecolorallocatealpha($new, 0, 0, 0, 127);

			imagefill($new, 0, 0, $colorTransparent);
			imagesavealpha($new, true);
		}

		return $new;
	}

	/**
	* function proportions (integer $width, integer $height)
	*
	* Calculate the image proportions to a scalable image
	*
	* return array
	*/
	function proportions ($width, $height) {
		if ($width > $height) {
			$n_width = $this->size[0];
			$n_height = round(($n_width * $height) / $width);
		} elseif ($height > $width) {
			$n_height = $this->size[1];
			$n_width = round(($n_height * $width) / $height);
		} elseif ($this->size[0] > $this->size[1]) {
			$n_width = $this->size[1];
			$n_height = $this->size[1];
		} else {
			$n_width = $this->size[0];
			$n_height = $this->size[0];
		}

		if ($n_width > $this->size[0]) {
			$n_width = $this->size[0];
			$n_height = round(($n_width * $height) / $width);
		}

		if ($n_height > $this->size[1]) {
			$n_height = $this->size[1];
			$n_width = round(($n_height * $width) / $height);
		}

		return array($n_width,$n_height);
	}

	/**
	* function maxSize (integer $posX, integer $posY, integer $width, integer $height)
	*
	* Calculate the image position from the image area
	*
	* return array
	*/
	function maxSize ($posX, $posY, $width, $height) {
		$posX = (($posX + $width) > $this->data[0])?($this->data[0] - $width):$posX;
		$posX = ($posX < 0)?0:$posX;
		$posY = (($posY + $height) > $this->data[1])?($this->data[1] - $height):$posY;
		$posY = ($posY < 0)?0:$posY;
		
		return array($posX,$posY);
	}

	/**
	* function recursive_path (string $target)
	*
	* Create a recursive folder
	*
	* return boolean
	*/
	function recursive_path ($target) {
		if (is_dir($target)) {
			return true;
		}

		$dirs = explode('/', $target);
		$dir = '';

		foreach ($dirs as $part) {
			if (empty($part) || ($part == '.')) {
				continue;
			}

			$dir .= '/'.$part;

			if (($part == '..') || is_dir($dir)) {
				continue;
			} else if (!@mkdir($dir, 0755)) {
				return $this->debug('Can\'t write in the target folder: '.$dir);
			}
		}

		clearstatcache();

		return is_dir($target);
	}
}


?>