<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2012 - 2017 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flImageFilter Class
 *
 * Base class for image filters.
 *
 * @package fusionLib
 */
abstract class flImageFilter {

	/**
	 * Apply the filter to the image.
	 *
	 * @param flImage $image The image object.
	 */
	abstract function apply($image);
}

/**
 * flImage Class
 *
 * Class access image information and manipulate images.
 *
 * @package fusionLib
 */
class flImage {

	/**
	 * The image type
	 *
	 * @var  int
	 */
	protected $type;

	/**
	 * The width of the image.
	 *
	 * @var int
	 */
	protected $height;

	/**
	 * The width of the image.
	 *
	 * @var  int
	 */
	protected $width;

	/**
	 * The image resource.
	 *
	 * @var resource
	 */
	protected $image = null;

	/**
	 * The file to load.
	 *
	 * @var string
	 */
	protected $file;

	/**
	 * Construct the object.
	 *
	 * @param string $file The image file to load or null if not loading.
	 * @param int $width The width of the image to create.
	 * @param int $height The height of the image to create.
	 * @param int $type One of IMAGETYPE_JPEG, IMAGETYPE_PNG or IMAGETYPE_GIF
	 * @throws flImageException
	 */
	function __construct($file, $width = null, $height = null, $type = null) {

		// If creating an image
		if($file === null) {
			if($width === null || $height === null || $type === null)
				throw new flImageException("Width, Height and Type must be given to create a new image.");

			$this->image = imagecreatetruecolor($this->width = $width, $this->height = $height);
			$this->type = $type;
			$this->file = null;
		}
		else {
			// Check the file is readable
			if(!is_readable($file))
				throw new flImageException("Unknown image file $file.");

			// Get the image information
			list($this->width, $this->height, $this->type) = getimagesize($file);

			if($this->type != IMAGETYPE_GIF && $this->type != IMAGETYPE_JPEG && $this->type != IMAGETYPE_PNG)
				throw new flImageException('flImage only supports GIF, JPEG and PNG file formats.');

			$this->file = $file;
		}
	}

	/**
	 * Get the image resource loading it if required.
	 *
	 * @return resource The image resource.
	 */
	function getImageResource() {
		if($this->image === null) {
			switch($this->type) {
				case IMAGETYPE_JPEG:
					$this->image = imagecreatefromjpeg($this->file);
					break;
				case IMAGETYPE_PNG:
					$this->image = imagecreatefrompng($this->file);
					break;
				case IMAGETYPE_GIF:
					$this->image = imagecreatefromgif($this->file);
					break;
			}
		}
		return $this->image;
	}

	/**
	 * Get the dimensions of the image.
	 *
	 * @return array The width and height of the image.
	 */
	function getDimensions() {
		return array($this->width, $this->height);
	}

	/**
	 * Get the width of the image.
	 *
	 * @return int The width.
	 */
	function getWidth() {
		return $this->width;
	}

	/**
	 * Get the height of the image.
	 *
	 * @return int The height.
	 */
	function getHeight() {
		return $this->height;
	}

	/**
	 * The image type.
	 *
	 * @return int One of IMAGETYPE_JPEG, IMAGETYPE_PNG or IMAGETYPE_GIF
	 */
	function getType() {
		return $this->type;
	}

	/**
	 * Resize the image to fit the given constraints while maintaining the aspect ratio.
	 *
	 * @param int $w The width constraint or 0 to if no constraint.
	 * @param int $h The height constraint or 0 to if no constraint.
	 * @param bool $scaleUp If true then the image will be scaled up or down to best fit the box, if false only scaling down will occur.
	 * @return flImage This object.
	 */
	function resize($w, $h, $scaleUp = false) {
		// Calculate new size
		if($w == 0) {
			if(!$scaleUp && $h >= $this->height)
				return $this;

			$w = ($h / $this->height) * $this->width;
		}
		else if($h == 0) {
			if(!$scaleUp && $w >= $this->width)
				return $this;

			$h = ($w / $this->width) * $this->height;
		}
		else {
			if(!$scaleUp && $this->width <= $w && $this->height <= $h)
				return $this;

			// Compute horizontal and vertical aspect ratios
			$vertRatio = $h / $this->height;
			$horizRatio = $w / $this->width;

			// Calculate the new size of the image
			if($horizRatio * $this->height < $h)
				$h = $horizRatio * $this->height;
			else
				$w = $vertRatio * $this->width;
		}

		// Round to whole pixels
		$w = (int)round($w);
		$h = (int)round($h);

		// Resize to correct size
		$im = imagecreatetruecolor($w, $h);
		imagealphablending($im, false);
		imagecopyresampled($im, $this->getImageResource(), 0, 0, 0, 0, $w, $h, $this->width, $this->height);

		imagedestroy($this->image);

		$this->width = $w;
		$this->height = $h;
		$this->image = $im;

		return $this;
	}

	/**
	 * Resize the image to fit the given size while maintaining the aspect ratio and cropping off excess.
	 *
	 * @param int $w The required width of the image.
	 * @param int $h The required height of the image.
	 * @return flImage This object.
	 */
	function resizeCrop($w, $h) {
		if($this->width == $w && $this->height == $h)
			return $this;

		$src_x = $src_y = 0;
		$src_w = $this->width;
		$src_h = $this->height;

		$cmp_x = $this->width  / $w;
		$cmp_y = $this->height / $h;

		if($cmp_x > $cmp_y) {
			$src_w = round(($this->width / $cmp_x * $cmp_y));
			$src_x = round(($this->width - ($this->width / $cmp_x * $cmp_y)) / 2);
		}
		else if($cmp_y > $cmp_x) {
			$src_h = round(($this->height / $cmp_y * $cmp_x));
			$src_y = round(($this->height - ($this->height / $cmp_y * $cmp_x)) / 2);
		}

		$im = imagecreatetruecolor($w, $h);
		imagealphablending($im, false);
		imagecopyresampled($im, $this->getImageResource(), 0, 0, $src_x, $src_y, $w, $h, $src_w, $src_h);

		imagedestroy($this->image);

		$this->width = $w;
		$this->height = $h;
		$this->image = $im;

		return $this;
	}

	/**
	 * Crop the selected region of the image out and keep it.
	 *
	 * @param int $x The top left of the region.
	 * @param int $y The top left of the region.
	 * @param int $w The width of the region.
	 * @param int $h The height of the region.
	 * @return flImage This object.
	 */
	function crop($x, $y, $w, $h) {
		$im = imagecreatetruecolor($w, $h);
		imagealphablending($im, false);
		imagecopy($im, $this->getImageResource(), 0, 0, $x, $y, $w, $h);

		imagedestroy($this->image);

		$this->width = $w;
		$this->height = $h;
		$this->image = $im;

		return $this;
	}

	/**
	 * Rotate the image.
	 *
	 * @param float $angle The angle in degrees.
	 * @param int $r The red for the background fill, 0 - 255
	 * @param int $g The green for the background fill, 0 - 255
	 * @param int $b The blue for the background fill, 0 - 255
	 * @param int $a The alpha for the background fill, 0 - 127 or null if not used.
	 * @return flImage This image object.
	 */
	function rotate($angle, $r, $g, $b, $a = null) {
		$img = $this->getImageResource();
		$this->image = imagerotate($img, $angle, $a === null ? imagecolorallocate($img, $r, $g, $b) : imagecolorallocatealpha($img, $r, $g, $b, $a));
		$this->width = imagesx($this->image);
		$this->height = imagesy($this->image);
		return $this;
	}

	/**
	 * Draw a rectangle.
	 *
	 * @param bool $fill True to fill the rectangle or false to outline.
	 * @param int $x1 Top left x coordinate.
	 * @param int $y1 Top left y coordinate.
	 * @param int $x2 Bottom right x coordinate.
	 * @param int $y2 Bottom right y coordinate.
	 * @param int $r The red for the background fill, 0 - 255
	 * @param int $g The green for the background fill, 0 - 255
	 * @param int $b The blue for the background fill, 0 - 255
	 * @param int $a The alpha for the background fill, 0 - 127 or null if not used.
	 * @return flImage This image object.
	 */
	function rectangle($fill, $x1, $y1, $x2, $y2, $r, $g, $b, $a = null) {
		$img = $this->getImageResource();
		if($fill)
			imagefilledrectangle($img, $x1, $y1, $x2, $y2, $a === null ? imagecolorallocate($img, $r, $g, $b) : imagecolorallocatealpha($img, $r, $g, $b, $a));
		else
			imagerectangle($img, $x1, $y1, $x2, $y2, $a === null ? imagecolorallocate($img, $r, $g, $b) : imagecolorallocatealpha($img, $r, $g, $b, $a));
		return $this;
	}

	/**
	 * Copy a region of the source image into this image.
	 *
	 * @param flImage $srcImage The source image.
	 * @param int $dx Destination x coordinate.
	 * @param int $dy Destination y coordinate.
	 * @param int $dw Destination width.
	 * @param int $dh Destination height.
	 * @param int $sx Source x coordinate.
	 * @param int $sy Source y coordinate.
	 * @param int $sw Source width.
	 * @param int $sh Source height.
	 * @return flImage This image object.
	 */
	function copyResampled($srcImage, $dx, $dy, $dw, $dh, $sx, $sy, $sw, $sh) {
		$im = $this->getImageResource();
		imagealphablending($im, false);
		imagecopyresampled($im, $srcImage->getImageResource(), $dx, $dy, $sx, $sy, $dw, $dh, $sw, $sh);
		return $this;
	}

	/**
	 * Apply a filter to this image.
	 *
	 * @param flImageFilter $filter The filter object.
	 * @return flImage This image object.
	 */
	function applyFilter($filter) {
		$filter->apply($this);
		return $this;
	}

	/**
	 * Save the image to disk.
	 *
	 * @param string $fileName The name to save the file under or null for the existing name.
	 * @param int $quality For jpg the quality, default is 90.
	 * @return bool true if the image was saved; else false.
	 * @throws flImageException
	 */
	function save($fileName = null, $quality = 90) {
		if($fileName === null) {
			$fileName = $this->file;
			$format = $this->type;
		}
		else {
			switch(strtolower(pathinfo($fileName, PATHINFO_EXTENSION))) {
				case 'png': $format = IMAGETYPE_PNG; break;
				case 'jpg': $format = IMAGETYPE_JPEG; break;
				case 'gif': $format = IMAGETYPE_GIF; break;
				default: $format = null; break;
			}
		}

		// Save the image
		imagesavealpha($res = $this->getImageResource(), true);
		switch($format) {
			case IMAGETYPE_PNG: $r = imagepng($res, $fileName); break;
			case IMAGETYPE_JPEG: $r = imagejpeg($res, $fileName, $quality); break;
			case IMAGETYPE_GIF: $r = imagegif($res, $fileName); break;
			default:
				throw new flImageException(__('Unsupported image type.', 'fusionLib'));
		}

		return $r;
	}

	/**
	 * Output the image to the browser.
	 *
	 * @param int $format null to use the current format or one of IMAGETYPE_PNG, IMAGETYPE_JPEG or IMAGETYPE_GIF
	 * @param int $quality For jpg the quality, default is 90.
	 * @throws flImageException
	 */
	function output($format = null, $quality = 90) {
		if($format === null)
			$format = $this->type;

		// Save the image
		imagesavealpha($res = $this->getImageResource(), true);
		switch($format) {
			case IMAGETYPE_PNG: imagepng($res); break;
			case IMAGETYPE_JPEG: imagejpeg($res, null, $quality); break;
			case IMAGETYPE_GIF: imagegif($res); break;
			default:
				throw new flImageException(__('Unsupported image type.', 'fusionLib'));
		}
	}
}
