<?php
declare(strict_types = 1);

/**
 * Manipulation d'images avec GD.
 *
 * @license https://www.gnu.org/licenses/gpl-3.0.html
 * @link https://www.igalerie.org/
 */
class GD
{
	/**
	 * Crée une image GD à partir du fichier $file.
	 *
	 * @param string $file
	 *   Chemin du fichier.
	 *
	 * @return mixed
	 */
	public static function createFrom(string $file)
	{
		switch ($type = getimagesize($file)[2] ?? 0)
		{
			case IMAGETYPE_GIF :
				return imagecreatefromgif($file);

			case IMAGETYPE_JPEG :
				return imagecreatefromjpeg($file);

			case IMAGETYPE_PNG :
				return imagecreatefrompng($file);

			case IMAGETYPE_WEBP :
				return imagecreatefromwebp($file);

			default :
				if (function_exists('imagecreatefromavif') && $type == IMAGETYPE_AVIF)
				{
					return imagecreatefromavif($file);
				}
				break;
		}

		return FALSE;
	}

	/**
	 * Enregistre une image GD dans un fichier.
	 *
	 * @param object $image
	 *   Objet GdImage.
	 * @param string $file
	 *   Chemin du fichier.
	 * @param int $type
	 *   Type du fichier.
	 * @param int $quality
	 *   Qualité de l'image enregistrée.
	 *
	 * @return bool
	 */
	public static function file(&$image, string $file, int $type, int $quality): bool
	{
		File::makeWritable(dirname($file));

		if ($quality > 100)
		{
			$quality = 100;
		}

		switch ($type)
		{
			case IMAGETYPE_GIF :
				if (!imagegif($image, $file))
				{
					return FALSE;
				}
				break;

			case IMAGETYPE_JPEG :
				if (!imagejpeg($image, $file, $quality))
				{
					return FALSE;
				}
				break;

			case IMAGETYPE_PNG :
				if (!imagepng($image, $file, (int) round($quality / 11)))
				{
					return FALSE;
				}
				break;

			case IMAGETYPE_WEBP :
				if (!imagewebp($image, $file, $quality))
				{
					return FALSE;
				}
				break;

			default :
				if (function_exists('imageavif') && $type == IMAGETYPE_AVIF)
				{
					if (!imageavif($image, $file, $quality))
					{
						return FALSE;
					}
				}
				break;
		}

		return TRUE;
	}

	/**
	 * Inverse une image verticalement ou horizontalement.
	 *
	 * @param object $src_img
	 *   Objet GdImage.
	 * @param string $type
	 *   Type d'inversement : 'horizontal' ou 'vertical'.
	 *
	 * @return bool
	 */
	public static function flip(&$src_img, string $type): bool
	{
		$width = imagesx($src_img);
		$height = imagesy($src_img);

		$dst_img = imagecreatetruecolor($width, $height);

		self::transparency($dst_img);

		switch ($type)
		{
			case 'horizontal':
				for ($i = 0; $i < $width; $i++)
				{
					if (!imagecopy($dst_img, $src_img,
					($width - $i - 1), 0, $i, 0, 1, $height))
					{
						return FALSE;
					}
				}
				break;

			case 'vertical':
				for ($i = 0; $i < $height; $i++)
				{
					if (!imagecopy($dst_img, $src_img, 0,
					($height - $i - 1), 0, $i, $width, 1))
					{
						return FALSE;
					}
				}
				break;
		}

		$src_img = $dst_img;
		return TRUE;
	}

	/**
	 * Transforme une image selon le paramètre Exif 'Orientation'.
	 *
	 * @param object $image
	 *   Objet GdImage.
	 * @param int $orientation
	 *   Valeur du paramètre Exif 'Orientation'.
	 *
	 * @return bool
	 */
	public static function orientation(&$image, int $orientation): bool
	{
		switch ($orientation)
		{
			case 2 :
				return self::flip($image, 'horizontal');

			case 3 :
				return self::rotate($image, -180);

			case 4 :
				return self::flip($image, 'vertical');

			case 5 :
				if (self::rotate($image, -90))
				{
					return self::flip($image, 'horizontal');
				}
				return FALSE;

			case 6 :
				return self::rotate($image, -90);

			case 7 :
				if (self::rotate($image, -90))
				{
					return self::flip($image, 'vertical');
				}
				return FALSE;

			case 8 :
				return self::rotate($image, -270);

			default :
				return TRUE;
		}
	}

	/**
	 * Découpe et redimensionne une image.
	 *
	 * @param object $src_img
	 *   Objet GdImage.
	 * @param int $src_x
	 *   Coordonnée X de l'image source.
	 * @param int $src_y
	 *   Coordonnée Y de l'image source.
	 * @param int $src_w
	 *   Largeur de l'image source.
	 * @param int $src_h
	 *   Hauteur de l'image source.
	 * @param int $dst_x
	 *   Coordonnée X de l'image destination.
	 * @param int $dst_y
	 *   Coordonnée Y de l'image destination.
	 * @param int $dst_w
	 *   Largeur de l'image destination.
	 * @param int $dst_h
	 *   Hauteur de l'image destination.
	 * @param int $dst_canvas_w
	 *   Largeur de l'image finale.
	 * @param int $dst_canvas_h
	 *   Hauteur de l'image finale.
	 * @param int $bg_color_red
	 *   Valeur pour le composant rouge de la couleur de fond.
	 * @param int $bg_color_green
	 *   Valeur pour le composant vert de la couleur de fond.
	 * @param int $bg_color_blue
	 *   Valeur pour le composant bleu de la couleur de fond.
	 *
	 * @return bool
	 */
	public static function resize(&$src_img, int $src_x, int $src_y, int $src_w, int $src_h,
	int $dst_x, int $dst_y, int $dst_w, int $dst_h, int $dst_canvas_w = 0, int $dst_canvas_h = 0,
	int $bg_color_red = 0, int $bg_color_green = 0, int $bg_color_blue = 0): bool
	{
		// Dimensions de l'image finale.
		$dst_canvas_w = ($dst_canvas_w == 0) ? $dst_w : $dst_canvas_w;
		$dst_canvas_h = ($dst_canvas_h == 0) ? $dst_h : $dst_canvas_h;
		$dst_img = imagecreatetruecolor((int) round($dst_canvas_w), (int) round($dst_canvas_h));

		// Conservation de la transparence.
		self::transparency($dst_img);

		// Couleur de fond.
		$bg_color = imagecolorallocatealpha($dst_img,
			$bg_color_red, $bg_color_green, $bg_color_blue, 127);
		imagefill($dst_img, 0, 0, $bg_color);

		// Redimensionnement.
		imagecopyresampled($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y,
			(int) round($dst_w), (int) round($dst_h), (int) round($src_w), (int) round($src_h));

		$src_img = $dst_img;
		return TRUE;
	}

	/**
	 * Effectue une rotation d'une image.
	 *
	 * @param object $image
	 *   Objet GdImage.
	 * @param float|int $angle
	 *   Angle de rotation, en degrés.
	 *
	 * @return bool
	 */
	public static function rotate(&$image, $angle): bool
	{
		if (!function_exists('imagerotate'))
		{
			return FALSE;
		}

		$image = imagerotate($image, (float) $angle, 0);

		self::transparency($image);

		return TRUE;
	}

	/**
	 * Permet de conserver la transparence lors de transformations sur une image.
	 *
	 * @param object $image
	 *   Objet GdImage.
	 *
	 * @return bool
	 */
	public static function transparency(&$image): bool
	{
		if (CONF_GD_TRANSPARENCY
		&& function_exists('imagealphablending')
		&& function_exists('imagecolorallocatealpha')
		&& function_exists('imagecolortransparent')
		&& function_exists('imagesavealpha')

		// Les fonctions GD utilisent
		// un type "resource" avec PHP 7, mais "object" depuis PHP 8.
		&& (is_resource($image) || is_object($image)))
		{
			imagealphablending($image, FALSE);
			$color = imagecolorallocatealpha($image, 0, 0, 0, 127);
			imagecolortransparent($image, $color);
			imagesavealpha($image, TRUE);
			return TRUE;
		}

		return FALSE;
	}
}
?>