<?php
/**
 * Support for image manipulation using [Imagick](http://php.net/Imagick).
 *
 * @package    KO7/Image
 * @category   Drivers
 * @author     Tamas Mihalik tamas.mihalik@gmail.com
 * @copyright  (c) Kohana Team
 * @license    https://koseven.ga/LICENSE.md
 */
class KO7_Image_Imagick extends Image {

	/**
	 * @var  Imagick  image magick object
	 */
	protected $im;

	/**
	 * Checks if ImageMagick is enabled.
	 *
	 * @throws  KO7_Exception
	 * @return  boolean
	 */
	public static function check()
	{
		if ( ! extension_loaded('imagick'))
		{
			throw new KO7_Exception('Imagick is not installed, or the extension is not loaded');
		}

		return Image_Imagick::$_checked = TRUE;
	}

	/**
	 * Runs [Image_Imagick::check] and loads the image.
	 *
	 * @return  void
	 * @throws  KO7_Exception
	 */
	public function __construct($file)
	{
		if ( ! Image_Imagick::$_checked)
		{
			// Run the install check
			Image_Imagick::check();
		}

		parent::__construct($file);

		$this->im = new Imagick;
		$this->im->readImage($file);

		if ( ! $this->im->getImageAlphaChannel())
		{
			// Force the image to have an alpha channel
			$this->im->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
		}
	}

	/**
	 * Destroys the loaded image to free up resources.
	 *
	 * @return  void
	 */
	public function __destruct()
	{
		$this->im->clear();
		$this->im->destroy();
	}

	protected function _do_resize($width, $height)
	{
		if ($this->im->scaleImage($width, $height))
		{
			// Reset the width and height
			$this->width = $this->im->getImageWidth();
			$this->height = $this->im->getImageHeight();

			return TRUE;
		}

		return FALSE;
	}

	protected function _do_crop($width, $height, $offset_x, $offset_y)
	{
		if ($this->im->cropImage($width, $height, $offset_x, $offset_y))
		{
			// Reset the width and height
			$this->width = $this->im->getImageWidth();
			$this->height = $this->im->getImageHeight();

			// Trim off hidden areas
			$this->im->setImagePage($this->width, $this->height, 0, 0);

			return TRUE;
		}

		return FALSE;
	}

	protected function _do_rotate($degrees)
	{
		if ($this->im->rotateImage(new ImagickPixel('transparent'), $degrees))
		{
			// Reset the width and height
			$this->width = $this->im->getImageWidth();
			$this->height = $this->im->getImageHeight();

			// Trim off hidden areas
			$this->im->setImagePage($this->width, $this->height, 0, 0);

			return TRUE;
		}

		return FALSE;
	}

	protected function _do_flip($direction)
	{
		if ($direction === Image::HORIZONTAL)
			return $this->im->flopImage();
		else
			return $this->im->flipImage();
	}

	protected function _do_sharpen($amount)
	{
		// IM not support $amount under 5 (0.15)
		$amount = ($amount < 5) ? 5 : $amount;

		// Amount should be in the range of 0.0 to 3.0
		$amount = ($amount * 3.0) / 100;

		return $this->im->sharpenImage(0, $amount);
	}

	protected function _do_reflection($height, $opacity, $fade_in)
	{
		// Clone the current image and flip it for reflection
		$reflection = $this->im->clone();
		$reflection->flipImage();

		// Crop the reflection to the selected height
		$reflection->cropImage($this->width, $height, 0, 0);
		$reflection->setImagePage($this->width, $height, 0, 0);

		// Select the fade direction
		$direction = ['transparent', 'black'];

		if ($fade_in)
		{
			// Change the direction of the fade
			$direction = array_reverse($direction);
		}

		// Create a gradient for fading
		$fade = new Imagick;
		$fade->newPseudoImage($reflection->getImageWidth(), $reflection->getImageHeight(), vsprintf('gradient:%s-%s', $direction));

		// Apply the fade alpha channel to the reflection
		$reflection->compositeImage($fade, Imagick::COMPOSITE_DSTOUT, 0, 0);

		// NOTE: Using setImageOpacity will destroy alpha channels!
		$reflection->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);

		// Create a new container to hold the image and reflection
		$image = new Imagick;
		$image->newImage($this->width, $this->height + $height, new ImagickPixel);

		// Force the image to have an alpha channel
		$image->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);

		// Force the background color to be transparent
		// $image->setImageBackgroundColor(new ImagickPixel('transparent'));

		// Match the colorspace between the two images before compositing
		$image->setColorspace($this->im->getColorspace());

		// Place the image and reflection into the container
		if ($image->compositeImage($this->im, Imagick::COMPOSITE_SRC, 0, 0)
		AND $image->compositeImage($reflection, Imagick::COMPOSITE_OVER, 0, $this->height))
		{
			// Replace the current image with the reflected image
			$this->im = $image;

			// Reset the width and height
			$this->width = $this->im->getImageWidth();
			$this->height = $this->im->getImageHeight();

			return TRUE;
		}

		return FALSE;
	}

	protected function _do_watermark(Image $image, $offset_x, $offset_y, $opacity)
	{
		// Convert the Image intance into an Imagick instance
		$watermark = new Imagick;
		$watermark->readImageBlob($image->render(), $image->file);

		if ($watermark->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE)
		{
			// Force the image to have an alpha channel
			$watermark->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE);
		}

		if ($opacity < 100)
		{
			// NOTE: Using setImageOpacity will destroy current alpha channels!
			$watermark->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
		}

		// Match the colorspace between the two images before compositing
		// $watermark->setColorspace($this->im->getColorspace());

		// Apply the watermark to the image
		return $this->im->compositeImage($watermark, Imagick::COMPOSITE_DISSOLVE, $offset_x, $offset_y);
	}

	protected function _do_background($r, $g, $b, $opacity)
	{
		// Create a RGB color for the background
		$color = sprintf('rgb(%d, %d, %d)', $r, $g, $b);

		// Create a new image for the background
		$background = new Imagick;
		$background->newImage($this->width, $this->height, new ImagickPixel($color));

		if ( ! $background->getImageAlphaChannel())
		{
			// Force the image to have an alpha channel
			$background->setImageAlphaChannel(Imagick::ALPHACHANNEL_SET);
		}

		// Clear the background image
		$background->setImageBackgroundColor(new ImagickPixel('transparent'));

		// NOTE: Using setImageOpacity will destroy current alpha channels!
		$background->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);

		// Match the colorspace between the two images before compositing
		$background->setColorspace($this->im->getColorspace());

		if ($background->compositeImage($this->im, Imagick::COMPOSITE_DISSOLVE, 0, 0))
		{
			// Replace the current image with the new image
			$this->im = $background;

			return TRUE;
		}

		return FALSE;
	}

	protected function _do_save($file, $quality)
	{
		// Get the image format and type
		list($format, $type) = $this->_get_imagetype(pathinfo($file, PATHINFO_EXTENSION));

		// Set the output image type
		$this->im->setFormat($format);

		// Set the output quality
		$this->im->setImageCompressionQuality($quality);

		if ($this->im->writeImage($file))
		{
			// Reset the image type and mime type
			$this->type = $type;
			$this->mime = $this->image_type_to_mime_type($type);

			return TRUE;
		}

		return FALSE;
	}

	protected function _do_render($type, $quality)
	{
		// Get the image format and type
		list($format, $type) = $this->_get_imagetype($type);

		// Set the output image type
		$this->im->setFormat($format);

		// Set the output quality
		$this->im->setImageCompressionQuality($quality);

		// Reset the image type and mime type
		$this->type = $type;
		$this->mime = $this->image_type_to_mime_type($type);

		return (string) $this->im;
	}

	/**
	 * Get the image type and format for an extension.
	 *
	 * @param   string  $extension  image extension: png, jpg, etc
	 * @return  string  IMAGETYPE_* constant
	 * @throws  KO7_Exception
	 */
	protected function _get_imagetype($extension)
	{
		// Normalize the extension to a format
		$format = strtolower($extension);

		switch ($format)
		{
			case 'jpg':
			case 'jpe':
			case 'jpeg':
				$type = IMAGETYPE_JPEG;
			break;
			case 'gif':
				$type = IMAGETYPE_GIF;
			break;
			case 'png':
				$type = IMAGETYPE_PNG;
			break;
			case 'webp':
				$type = SELF::IMAGETYPE_WEBP;
			break;
			default:
				throw new KO7_Exception('Installed ImageMagick does not support :type images',
					[':type' => $extension]);
			break;
		}

		return [$format, $type];
	}

}
