<?php

/**
 * Image editing
 * 
 * @version 1.5.01-08-2007
 * @copyright http://creativecommons.org/licenses/by-sa/2.5/
 * @author Boaz den Besten
 * @contact www.n3rd.nl
 * @PHPversion 5.2.3
 */
class clsImage{
	
	/**
	 * Current image resource
	 *
	 * @var resource
	 */
	private $m_rImage;
	
	/**
	 * Original filename
	 *
	 * @var string
	 */
	private $m_sFileName;
	
	/**
	 * Original file location
	 *
	 * @var string
	 */
	private $m_sFileLocation;
	
	/**
	 * Current image type
	 *
	 * @var integer
	 */
	private $m_iImageType;
	
	/**
	 * Watermark positioning constant
	 * 
	 * Y-POSITION: TOP
	 */
	const TOP = 1;
	
	/**
	 * Watermark positioning constant
	 * 
	 * Y-POSITION: BOTTOM
	 */
	const BOTTOM = 2;
	
	/**
	 * Watermark positioning constant
	 * 
	 * X-POSITION: LEFT
	 */
	const LEFT = 4;
	
	/**
	 * Watermark positioning constant
	 * 
	 * X-POSITION: RIGHT
	 */
	const RIGHT = 8;
	
	/**
	 * Watermark positioning constant
	 * 
	 * XY-POSITION: CENTER
	 */
	const CENTER = 16;
	
	/**
	 * Constructor, loads an image file
	 *
	 * @param String path to image file
	 * @throws Exception if IMAGETYPE not supported
	 * @throws Exception if imagecreate fails
	 */
	public function __construct($p_sImage){
		$aImageInfo = @getimagesize($p_sImage);
		$this->m_iImageType = $aImageInfo[2];
		if(!(imagetypes() & $this->m_iImageType)){
			throw new Exception('Image type is not supported.', 102);
		}
		
		$this->m_rImage = @imagecreatefromstring(file_get_contents($p_sImage));
		if(!$this->m_rImage){
			throw new Exception('imagecreatefromstring faild.', 101);
		}
		
		$aPathinfo = pathinfo($p_sImage);
		$this->m_sFileLocation = $aPathinfo['dirname'];
		$this->m_sFileName = $aPathinfo['basename'];
	}
	
	/**
	 * Clean up the mess
	 *
	 */
	public function __destruct(){
		imagedestroy($this->m_rImage);
	}
	
	/**
	 * Get the original filename
	 *
	 * @return String FileName
	 */
	public function getFileName(){
		return $this->m_sFileName;
	}
	
	/**
	 * Get the original location
	 *
	 * @return String File Location
	 */
	public function getFileLocation(){
		return $this->m_sFileLocation;
	}
	
	/**
	 * Get the resource
	 *
	 * @return resource Image
	 */
	public function getResource(){
		return $this->m_rImage;
	}
	
	/**
	 * Get the current IMAGETYPE
	 *
	 * @return integer
	 */
	public function getImageType(){
		return $this->m_iImageType;
	}
	
	/**
	 * Change the IMAGETYPE
	 * This will result in a new content-type (and extension) when saving the file.
	 *
	 * @param integer IMAGETYPE_XXX constant
	 * @throws Exception if IMAGETYPE not supported
	 */
	public function setImageType($p_iImageType){
		if(!(imagetypes() & $p_iImageType)){
			throw new Exception('Image type is not supported.', 102);
		}
		$this->m_iImageType = $p_iImageType;
	}
	
	/**
	 * Resize the image
	 *
	 * @param int max image size in px
	 * @param boolean Constant image width & height
	 * @param boolean Make not bigger (the image wil not be made bigger than the original)
	 */
	public function resize($p_iDimension, $p_bConstant=false, $p_bNotBigger=true){
		$iWidth = imagesx($this->m_rImage);
		$iHeight = imagesy($this->m_rImage);
		
		if($p_bNotBigger && ($iWidth < $p_iDimension && $iHeight < $p_iDimension)){
			$p_iDimension = ($iWidth > $iHeight) ? $iWidth : $iHeight;
		}
		
		if($p_bConstant){
			$iSizeX = $p_iDimension;
			$iSizeY = $p_iDimension;
		}else{
			if($iWidth > $iHeight){
				$iSizeX = $p_iDimension;
				$iSizeY = ($p_iDimension/$iWidth)*$iHeight;
			}else{
				$iSizeX = ($p_iDimension/$iHeight)*$iWidth;
				$iSizeY = $p_iDimension;
			}
		}

		$rResisedImage = imagecreatetruecolor($iSizeX, $iSizeY);
		imagecopyresampled($rResisedImage, $this->m_rImage, 0, 0, 0, 0, $iSizeX, $iSizeY, $iWidth, $iHeight);
		
		$this->m_rImage = $rResisedImage;
	}
	
	/**
	 * Rotate the image (to the right)
	 *
	 * @param float degrees to rotate 
	 */
	public function rotate($p_fDegrees){
		$this->m_rImage = imagerotate($this->m_rImage, $p_fDegrees, 0);
	}
	
	/**
	 * Crop Image
	 *
	 * @param Integer Start pixels from left
	 * @param Integer Start pixels from top
	 * @param Integer Stop pixels from right
	 * @param Integer Stop pixels from bottom
	 */
	public function crop($p_iLeft, $p_iTop, $p_iRight, $p_iBottom){
		$iWidth = imagesx($this->m_rImage)-$p_iRight-$p_iLeft;
		$iHeight = imagesy($this->m_rImage)-$p_iBottom-$p_iTop;
		
		$rNewImage = imagecreatetruecolor($iWidth, $iHeight);
		imagecopy($rNewImage, $this->m_rImage, 0, 0, $p_iLeft, $p_iTop, imagesx($this->m_rImage), imagesy($this->m_rImage));
		
		imagedestroy($this->m_rImage);
		$this->m_rImage = $rNewImage;
		$rNewImage = null;
	}
	
	/**
	 * Apply a filter to the image
	 *
	 * @param integer IMG_FILTER (predefined constants)
	 * @param mixed optional (can be a float, intger between -255 and 255, intger between -100 and 100 or a hex color.)
	 * @throws Exception if IMG_FILTER not exists
	 * @throws Exception if param 2 has invalid value
	 */
	public function filter($p_iFiler, $p_mArg=null){
		switch($p_iFiler){
			case IMG_FILTER_NEGATE:
			case IMG_FILTER_GRAYSCALE:
			case IMG_FILTER_EDGEDETECT:
			case IMG_FILTER_EMBOSS:
			case IMG_FILTER_GAUSSIAN_BLUR:
			case IMG_FILTER_SELECTIVE_BLUR:
			case IMG_FILTER_MEAN_REMOVAL:
				imagefilter($this->m_rImage, $p_iFiler);
				break;
			case IMG_FILTER_BRIGHTNESS:
				if($p_mArg < -255 || $p_mArg > 255){
					throw new Exception('Integer between -255 and +255 expected as second argument.', 305);
				}
				imagefilter($this->m_rImage, $p_iFiler, $p_mArg);
				break;
			case IMG_FILTER_CONTRAST:
				if($p_mArg < -100 || $p_mArg > 100){
					throw new Exception('Integer between -100 and +100 expected as second argument.', 304);
				}
				imagefilter($this->m_rImage, $p_iFiler, $p_mArg);
				break;
			case IMG_FILTER_SMOOTH:
				if(!is_float($p_mArg)){
					throw new Exception('Float expected as second argument.', 303);
				}
				imagefilter($this->m_rImage, $p_iFiler, $p_mArg);
				break;
			case IMG_FILTER_COLORIZE:
				if(empty($p_mArg) || !preg_match('_(#?)[0-9A-Fa-f]{6}_', $p_mArg)){
					throw new Exception('Hex color expected as second argument.', 302);
				}
				$sColor = str_replace('#', '', $p_mArg);
				imagefilter($this->m_rImage, $p_iFiler, hexdec(substr($sColor,0,2)), hexdec(substr($sColor,2,2)), hexdec(substr($sColor,4,2)));
				break;
			default:
				throw new Exception('Invalid IMG_FILTER constant', 301);
		}
	}
	
	/**
	 * Place a watermark on top of the image
	 *
	 * @param clsImage WaterMark
	 * @param Integer PositionX (LEFT, CENTER, RIGHT)
	 * @param Integer PositionY (TOP, CENTER, BOTTOM)
	 */
	public function addWaterMark(clsImage $p_oWaterMark, $p_iX=self::LEFT, $p_iY=self::TOP){
		$rWaterMark = $p_oWaterMark->getResource();
		
		switch($p_iX){
			case self::RIGHT:
				$iX = imagesx($this->m_rImage)-imagesx($rWaterMark);
				break;
			case self::CENTER:
				$iX = (imagesx($this->m_rImage)/2)-(imagesx($rWaterMark)/2);
				break;
			case self::LEFT:
			default:
				$iX = 0;
		}
		
		switch($p_iY){
			case self::BOTTOM:
				$iY = imagesy($this->m_rImage)-imagesy($rWaterMark);
				break;
			case self::CENTER:
				$iY = (imagesy($this->m_rImage)/2)-(imagesy($rWaterMark)/2);
				break;
			case self::TOP:
			default:
				$iY = 0;
		}
			
		imagecopymerge($this->m_rImage, $rWaterMark, $iX, $iY, 0, 0, imagesx($rWaterMark), imagesy($rWaterMark), 100);
	}
	
	/**
	 * Add some text to the image
	 *
	 * @param String Font file location. (eg. ./fonts/font.ttf)
	 * @param String Text to add
	 * @param String HEX color code (eg. #FF0000)
	 * @param float Font size in pixels
	 * @param Integer PositionX (LEFT, CENTER, RIGHT)
	 * @param Integer PositionY (TOP, CENTER, BOTTOM)
	 * @throws Exception if imagettfbbox faild.
	 * @throws Exception if a invalid HEX color code is passed
	 */
	public function addText($p_sFontFile, $p_sText, $p_sColor, $p_fSize=12, $p_iX=self::LEFT, $p_iY=self::TOP){
		if(!($aTextBox = @imagettfbbox($p_fSize, 0, $p_sFontFile, $p_sText))){
			throw new Exception('Imagettfbbox failed.', 501);
		}
		
		switch($p_iX){
			case self::RIGHT:
				$iX = imagesx($this->m_rImage)-$aTextBox[4];
				break;
			case self::CENTER:
				$iX = (imagesx($this->m_rImage)/2)-($aTextBox[4]/2);
				break;
			case self::LEFT:
			default:
				$iX = 0;
		}
		
		switch($p_iY){
			case self::BOTTOM:
				$iY = imagesy($this->m_rImage) - $aTextBox[3];
				break;
			case self::CENTER:
				$iY = (imagesy($this->m_rImage)/2)+(abs($aTextBox[5]+$aTextBox[3])/2);
				break;
			case self::TOP:
			default:
				$iY = 0 + abs($aTextBox[5]);
		}
		
		if(empty($p_sColor) || !preg_match('_(#?)[0-9A-Fa-f]{6}_', $p_sColor)){
			throw new Exception('Invalid HEX color code.', 502);
		}
		$sColor = str_replace('#', '', $p_sColor);
		
		imagettftext($this->m_rImage, $p_fSize, 0, $iX, $iY, imagecolorallocate($this->m_rImage, hexdec(substr($sColor, 0, 2)), hexdec(substr($sColor, 2, 2)), hexdec(substr($sColor, 4, 2))), $p_sFontFile, $p_sText);
	}
	
	/**
	 * Create the image depending on the IMAGETYPE
	 *
	 */
	private function createImage(){
		switch($this->m_iImageType){
			case IMAGETYPE_GIF:
				imagegif($this->m_rImage);
				break;
			case IMAGETYPE_PNG:
				imagepng($this->m_rImage);
				break;
			case IMAGETYPE_WBMP:
				imagewbmp($this->m_rImage);
				break;
			case IMAGETYPE_JPEG:
			case IMAGETYPE_XBM:
			default:
				imagejpeg($this->m_rImage);
		}
	}
	
	/**
	 * save the image (overwrite)
	 *
	 */
	public function save(){
		$this->saveAs(null, null, true);
	}
	
	/**
	 * save the image as
	 *
	 * @param String Loaction path (optional)
	 * @param resource Image resource (optional)
	 * @param boolean overwrite (default: false)
	 * @throws Exception if target location is not writable.
	 */
	public function saveAs($p_sFileLocation = null, $p_sFileName = null, $p_bOverWrite=false){
		$sFileLocation = is_null($p_sFileLocation) ? $this->m_sFileLocation : $p_sFileLocation;
		$sFileName = is_null($p_sFileName) ? $this->m_sFileName : $p_sFileName.image_type_to_extension($this->m_iImageType);
		
		if(!$p_bOverWrite && file_exists($sFileLocation.'/'.$sFileName)){
			throw new Exception('File already exists', 202);
		}
		
		ob_start();
		$this->createImage();
		if(!(file_put_contents($sFileLocation.'/'.$sFileName, ob_get_clean()) > 0)){
			throw new Exception('File cannot be saved [location: '.$sFileLocation.'] - [file: '.$sFileName.']', 201);
		}
	}
	
	/**
	 * Display the image directly to the screen
	 * 
	 * note: exceptions and other output wil not be handled correctly
	 */
	public function display(){
		header('Content-type: '.image_type_to_mime_type($this->m_iImageType));
		$this->createImage();
	}
	
	/**
	 * BluePrint of the Image
	 *
	 */
	public function __toString(){		
		return print_r($this, true);
	}
	
}

?>