<?php
/**
* @author Kaloyan K. Tsvetkov <kaloyan@kaloyan.info>
* @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License Version 2.1
* @package Asido
* @subpackage Asido.Driver
* @version $Id: class.driver.php 7 2007-04-09 21:09:09Z mrasnika $
*/

/////////////////////////////////////////////////////////////////////////////

/**
* Asido abstract driver
*
* @package Asido
* @subpackage Asido.Driver
* @abstract
*/
Class Asido_Driver {

	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

	/**
	* Maps to supported mime types
	* @var array
	* @access protected
	*/
	var $__mime = array(

		// support reading
		//
		'read' => array(
			),

		// support writing
		//
		'write' => array(
			),
		);

	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

	/**
	* Checks whether the environment is compatible with this driver
	*
	* @return boolean
	* @access public
	* @abstract
	*/
	function is_compatible() {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}
	
	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
	
	/**
	* Resize an image
	*
	* @param Asido_TMP &$tmp
	* @param integer $width
	* @param integer $height
	* @param mixed $mode
	* @return boolean
	* @access public
	*/
	function resize(&$tmp, $width, $height, $mode) {
		
		// no params ?
		//
		if (!$width && !$height) {
			trigger_error(
				sprintf(
					'Neither width nor height provided '
						. ' for resizing operation '
						. ' of "%s" ',
					$tmp->source_filename
					),
				E_USER_WARNING
				);
			return false;
			}

		// resize by only one parameter ?
		//
		if (!$width || !$height) {
			$mode = ASIDO_RESIZE_PROPORTIONAL;
			
			if (!$width) {
				$width = floor(
					$tmp->image_width * $height / $tmp->image_height
					);
				}
			if (!$height) {
				$height = floor(
					$tmp->image_height * $width / $tmp->image_width
					);
				}
			}

		// stretch or proportional
		//
		switch ($mode) {

			case ASIDO_RESIZE_STRETCH:
				$p_width = $width;
				$p_height = $height;
				break;
				
			case ASIDO_RESIZE_FIT:
				
				if (($tmp->image_height <= $height) && ($tmp->image_width <= $width)) {
					$p_width = $tmp->image_width;
					$p_height = $tmp->image_height;
					
					// break the switch\case
					//
					break;
					}

				// if indeed has to be resized, fall
				// back to the proportional resize
				//
				;

			default:
			case ASIDO_RESIZE_PROPORTIONAL:
				
				$p1_width = $width;
				$p1_height = round(
					$tmp->image_height * $width / $tmp->image_width
					);

				if ($p1_height - $height > 1) {
					$p_height = $height;
					$p_width = round(
						$tmp->image_width * $height / $tmp->image_height
						);
					} else {
					$p_width = $p1_width;
					$p_height = $p1_height;
					}
				break;
			}

		// do the resize
		//
		$r = $this->__resize($tmp, $p_width, $p_height);
	
		// new dimensions ?
		//
		$tmp->image_width = $p_width;
		$tmp->image_height = $p_height;
		
		return $r;
		}

	/**
	* Convert an image from one file-type to another
	*
	* @param Asido_TMP &$tmp
	* @param string $mime_type
	* @return boolean
	* @access public
	*/
	function convert(&$tmp, $mime_type) {
		
		$mime_type = strToLower($mime_type);
		
		// supported format ? (for writing)
		//
		if (!$this->supported($mime_type, ASIDO_SUPPORT_WRITE)) {
			trigger_error(
				sprintf(
					'Requested conversion format "%s" is not supported',
					$mime_type
					),
				E_USER_WARNING
				);
			return false;
			}

		$tmp->save = $mime_type;
		return true;
		}

	/**
	* Watermark an image 
	*
	* @param Asido_TMP &$tmp
	* @param string $watermark_image
	* @param mixed $position
	* @param mixed $scalable
	* @param float $scalable_factor
	* @return boolean
	* @access public
	*/
	function watermark(&$tmp, $watermark_image, $position, $scalable, $scalable_factor) {
		
		// open
		//
		$wi = new Asido_Image();
		if (!@$wi->source($watermark_image)) {
			trigger_error(
				sprintf(
					'Watermark image "%s" is '
						. ' either missing or '
						. ' is not readable',
					$watermark_image
					),
				E_USER_WARNING
				);
			return false;
			}
		
		$wt = $this->prepare($wi);

		// dimensions
		//
		$target_width = $tmp->image_width;
		$target_height = $tmp->image_height;

		$watermark_width =& $wt->image_width;
		$watermark_height =& $wt->image_height;
		
		// watermark scalable ?
		//
		if ((ASIDO_WATERMARK_SCALABLE_ENABLED == $scalable)
				&& ($watermark_width > $target_width * $scalable_factor
					|| $watermark_height > $target_height * $scalable_factor)
			){
			
			// jump thru tha loop
			//
			$t2 = $this->__tmpimage($wt->source);
			
			if ($this->resize($t2,
					intval($target_width * $scalable_factor),
					intval($target_height * $scalable_factor),
					ASIDO_RESIZE_PROPORTIONAL)
				){

				@unlink($t2->source_filename);
				$this->__destroy_target($wt);

				// new watermark created, destroy old
				//
				$this->__destroy_source($wt);

				// copy new watermark
				//
				$wt->source = $t2->target;
				$this->__destroy_source($t2);
					// ^
					// DO NOT UNSET $t2->target!!!

				// adjust watermark dimensions
				//
				$watermark_width = $t2->image_width;
				$watermark_height =$t2->image_height;
				}
			}
		
		
		// position
		//
		switch ($position) {
			
			// tile watermark
			//
			case ASIDO_WATERMARK_TILE :
				$watermark_x = 1;
				$watermark_y = 1;
				
				// create tile
				//
				for ($x = 0; $x < ceil($target_width / $watermark_width); $x++) {
					for ($y = 0; $y < ceil($target_height / $watermark_height); $y++) {
						
						// skip the first one
						//
						if (!$x && !$y) continue;
						
						// copy the watermark
						//
						$this->__copy($tmp, $wt,
							$watermark_x + $x*$watermark_width,
							$watermark_y + $y*$watermark_height
							);
						}
					}
				break;
			
			// top left, north west
			//
			case ASIDO_WATERMARK_TOP_LEFT :
				$watermark_x = 1;
				$watermark_y = 1;
				break;
			
			// top center, north
			//
			case ASIDO_WATERMARK_TOP_CENTER :
				$watermark_x = ($target_width - $watermark_width)/2;
				$watermark_y = 1;
				break;
			
			// top right, north east
			//
			case ASIDO_WATERMARK_TOP_RIGHT :
				$watermark_x = $target_width - $watermark_width ;
				$watermark_y = 1;
				break;
			
			// middle left, west
			//
			case ASIDO_WATERMARK_MIDDLE_LEFT :
				$watermark_x = 1;
				$watermark_y = ($target_height - $watermark_height)/2;
				break;
			
			// middle center, center
			//
			case ASIDO_WATERMARK_MIDDLE_CENTER :
				$watermark_x = ($target_width - $watermark_width)/2;
				$watermark_y = ($target_height - $watermark_height)/2;
				break;
			
			// middle right, east
			//
			case ASIDO_WATERMARK_MIDDLE_RIGHT :

				$watermark_x = $target_width - $watermark_width ;
				$watermark_y = ($target_height - $watermark_height)/2;
				break;
			
			// bottom left, south west
			//
			case ASIDO_WATERMARK_BOTTOM_LEFT :
				$watermark_x = 1;
				$watermark_y = $target_height - $watermark_height ;
				break;
			
			// bottom center, south
			//
			case ASIDO_WATERMARK_BOTTOM_CENTER :
				$watermark_x = ($target_width - $watermark_width)/2;
				$watermark_y = $target_height - $watermark_height ;
				break;
			
			default :
			
			// bottom right, south east
			//
			case ASIDO_WATERMARK_BOTTOM_RIGHT :
				$watermark_x = $target_width - $watermark_width ;
				$watermark_y = $target_height - $watermark_height ;
				break;
			}

		// copy the watermark
		//
		$this->__copy($tmp, $wt, $watermark_x, $watermark_y);
		
		// destroy watermark image
		//
		$this->__destroy_source($wt);
		return true;
		}

	/**
	* Make the image greyscale
	*
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access public
	*/
	function grayscale(&$tmp) {
		return $this->__grayscale($tmp);
		}

	/**
	* Rotate the image clockwise
	*
	* @param Asido_TMP &$tmp
	* @param float $angle
	* @param Asido_Color $color
	* @return boolean
	* @access public
	*/
	function rotate(&$tmp, $angle, $color=null) {
		
		// color ?
		//
		if (!is_a($color, 'Asido_Color')) {
			$color = new Asido_Color;
			$color->set(255, 255, 255);
			}
		
		return $this->__rotate($tmp, $angle, $color);
		}

	/**
	* Resize an image by "framing" it with the provided width and height
	*
	* @param Asido_Tmp &$tmp
	* @param integer $width
	* @param integer $height
	* @param Asido_Color $color
	* @return boolean
	* @access public
	*/
	function frame(&$tmp, $width, $height, $color=null) {
		
		// color ?
		//
		if (!is_a($color, 'Asido_Color')) {
			$color = new Asido_Color;
			$color->set(255, 255, 255);
			}
		
		// resize it
		//
		if (!$this->resize($tmp, $width, $height, ASIDO_RESIZE_FIT)) {
			return false;
			}
		
		// get canvas
		//
		if (!$t2 = $this->__canvas($width, $height, $color)) {
			trigger_error(
				sprintf(
					'Unable to get a canvas '
						. ' with %s pixels '
						. ' width and %s '
						. ' pixels height',
					$width, $height
					),
				E_USER_WARNING
				);
			return false;
			}
		
		// target
		//
		$t3 = new Asido_TMP;
		$t3->source =& $tmp->target;
		$t3->image_width = $tmp->image_width;
		$t3->image_height = $tmp->image_height;
		
		// apply the image
		//
		if (!$this->__copy(
			$t2, $t3,
			round(($t2->image_width - $t3->image_width)/2),
			round(($t2->image_height - $t3->image_height)/2)
			)) {
			trigger_error(
				'Failed to copy to the passepartout image',
				E_USER_WARNING
				);
			return false;
			}
		
		// cook the result
		//
		$this->__destroy_target($tmp);
		$tmp->target = $t2->target;
		$tmp->image_width = $t2->image_width;
		$tmp->image_height = $t2->image_height;
		
		return true;
		}

	/**
	* Resize an image by "framing" it with the provided width and height
	*
	* @param Asido_Tmp &$tmp
	* @param string $applied_image	filepath to the image that is going to be copied
	* @param integer $x
	* @param integer $y
	* @return boolean
	* @access public
	*/
	function copy(&$tmp, $applied_image, $x, $y) {

		// open
		//
		$ci = new Asido_Image;
		if (!@$ci->source($applied_image)) {
			trigger_error(
				sprintf(
					'The image that is going to '
						. ' be copied "%s" is '
						. ' either missing or '
						. ' is not readable',
					$applied_image
					),
				E_USER_WARNING
				);
			return false;
			}
		
		$ct = $this->prepare($ci);

		if (!$this->__copy($tmp, $ct, $x, $y)) {
			trigger_error(
				'Failed to copy the image',
				E_USER_WARNING
				);
			return false;
			}
		
		$this->__destroy_source($ct);
		$this->__destroy_target($ct);

		return true;
		}

	/**
	* Crop the image 
	*
	* @param Asido_TMP &$tmp
	* @param integer $x
	* @param integer $y
	* @param integer $width
	* @param integer $height
	* @return boolean
	* @access public
	*/
	function crop(&$tmp, $x, $y, $width, $height) {
		return $this->__crop($tmp, $x, $y, $width, $height);
		}

	/**
	* Vertically mirror (flip) the image
	* 
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access public
	*/
	function flip(&$tmp) {
		return $this->__flip($tmp);
		}

	/**
	* Horizontally mirror (flop) the image
	* 
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access public
	*/
	function flop($tmp) {
		return $this->__flop($tmp);
		}

	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

	/**
	* Prepare an image for processing it
	*
	* @param Asido_Image &$image
	* @return boolean
	* @access public
	*/
	function prepare(&$image) {

		// create new temporary object
		//
		$tmp = new Asido_TMP;
		$tmp->source_filename = $image->source();
		$tmp->target_filename = $image->target();

		// failed opening ?
		//
		if (!$this->__open($tmp)) {
			trigger_error(
				'Unable to open source image',
				E_USER_WARNING
				);
			return false;
			}
		
		return $tmp;
		}

	/**
	* Save an image after being processed
	*
	* @param Asido_Image &$image
	* @return boolean
	* @access public
	*/
	function save(&$image) {
		return $this->__write($image);
		}

	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

	/**
	* Copy one image to another
	*
	* @param Asido_TMP &$tmp_target
	* @param Asido_TMP &$tmp_source
	* @param integer $destination_x
	* @param integer $destination_y
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __copy(&$tmp_target, &$tmp_source, $destination_x, $destination_y) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Do the actual resize of an image
	*
	* @param Asido_TMP &$tmp
	* @param integer $width
	* @param integer $height
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __resize(&$tmp, $width, $height) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Make the image greyscale
	*
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __grayscale(&$tmp) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Rotate the image clockwise
	*
	* @param Asido_TMP &$tmp
	* @param float $angle
	* @param Asido_Color $color
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __rotate(&$tmp, $angle, $color) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Get canvas
	*
	* @param integer $width
	* @param integer $height
	* @param Asido_Color $color
	* @return Asido_TMP
	* @access protected
	* @abstract
	*/
	function __canvas($width, $height, $color) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Crop the image 
	*
	* @param Asido_TMP &$tmp
	* @param integer $x
	* @param integer $y
	* @param integer $width
	* @param integer $height
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __crop(&$tmp, $x, $y, $width, $height) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Vertically mirror (flip) the image
	* 
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __flip(&$tmp) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Horizontally mirror (flop) the image
	* 
	* @param Asido_Image &$image
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __flop(&$tmp) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

	/**
	* Get supported mime-types
	*
	* @param mixed $mode
	* @return array
	* @access public
	*/	
	function get_supported_types($mode) {
		
		switch ($mode) {
			
			case 'ASIDO_SUPPORT_READ':
				return array_unique(
					array_values($this->__mime['read'])
					);
				break;
				
			case 'ASIDO_SUPPORT_WRITE':
				return array_unique(
					array_values($this->__mime['write'])
					);
				break;
				
			default :
			case 'ASIDO_SUPPORT_READ_WRITE' :
				return array_unique(
					array_intersect(
						array_values($this->__mime['write']),
						array_values($this->__mime['read'])
						)
					);
				break;
			}
		}

	/**
	* Returnes whether an image format is supported or not
	*
	* @param string $mime_type
	* @param mixed $mode
	* @return boolean
	* @access public
	*/
	function supported($mime_type, $mode) {
		return in_array(
			strToLower($mime_type),
			$this->get_supported_types($mode)
			);
		}

	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

	/**
	* Open the source and target image for processing it
	*
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __open(&$tmp) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Write the image after being processed
	*
	* @param Asido_Image &$image
	* @return boolean
	* @access protected
	* @abstract
	*/
	function __write(&$image) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}
	
	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
	
	/**
	* Return a name for a temporary file
	* @return string
	* @access protected
	*/
	function __tmpfile() {
		return tempnam(-1, null) . '.PNG';
		}
	
	/**
	* Generate a temporary object for the provided argument
	*
	* @param mixed &$handler
	* @param string $filename the filename will be automatically generated 
	*	on the fly, but if you want you can use the filename provided by 
	*	this argument
	* @return Asido_TMP
	* @access protected
	* @abstract
	*/
	function __tmpimage(&$handler, $filename=null) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

	/**
	* Destroy the source for the provided temporary object
	*
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access protected
	* @abstract
	*/	
	function __destroy_source(&$tmp) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}

	/**
	* Destroy the target for the provided temporary object
	*
	* @param Asido_TMP &$tmp
	* @return boolean
	* @access protected
	* @abstract
	*/	
	function __destroy_target(&$tmp) {
		asido::trigger_abstract_error(
			__CLASS__,
			__FUNCTION__
			);
		}
	
	// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
	
//--end-of-class--	
}

/////////////////////////////////////////////////////////////////////////////

?>