= 8 && \PHP_MINOR_VERSION >= 1; private bool $strip_redraw; private static function imageCreateFrom(string $file, string $mime): mixed { switch ($mime) { case 'image/jpeg': return \imagecreatefromjpeg($file); case 'image/png': return \imagecreatefrompng($file); case 'image/gif': return \imagecreatefromgif($file); case 'image/webp': return \imagecreatefromwbmp($file); case 'image/bmp': return \imagecreatefrombmp($file); case 'image/avif': return self::PHP81 ? \imagecreatefromavif($file) : false; } return false; } public static function imageSaveTo(mixed $gd, string $file, string $mime) { // Somebody should tune the quality and speed values... // Won't be me. switch ($mime) { case 'image/jpeg': return \imagejpeg($gd, $file, 50); case 'image/png': return \imagepng($gd, $file, 7); case 'image/gif': return \imagegif($gd, $file); case 'image/webp': return \imagewebp($gd, $file, 50); case 'image/bmp': return \imagebmp($gd, $file, true); case 'image/avif': return self::PHP81 ? \imageavif($gd, $file, 30, 6) : false; } return false; } private static function createCanvas(string $mime, int $width, int $height) { $gd = \imagecreatetruecolor($width, $height); switch ($mime) { case 'image/png': \imagecolortransparent($gd, \imagecolorallocatealpha($gd, 0, 0, 0, 0)); \imagesavealpha($gd, true); \imagealphablending($gd, false); break; case 'image/gif': \imagecolortransparent($gd, \imagecolorallocatealpha($gd, 0, 0, 0, 0)); \imagesavealpha($gd, true); break; } return $gd; } private function generateThumbImpl( mixed $gd, string $source_file_path, string $source_file_mime, string $source_file_kind, string $preferred_out_file_dir, string $preferred_out_file_name, string $preferred_out_mime, int $max_width, int $max_height ) { $width = \imagesx($gd); $height = \imagesy($gd); if ($width <= $max_width && $height <= $max_height) { $out_path = $preferred_out_file_dir . DIRECTORY_SEPARATOR . $preferred_out_file_name . '.' . Mime\mime_to_ext($source_file_mime); if ($source_file_kind === self::FILE_KIND_UPLOADED) { if (!Fs\move_or_copy_uploaded($source_file_path, $out_path)) { throw new \RuntimeException("Could not move or copy uploaded file '$source_file_path' to '$out_path'"); } } else { if (!Fs\link_or_copy($source_file_path, $out_path)) { throw new \RuntimeException("Could not link or copy '$source_file_path' to '$out_path'"); } } return new ThumbGenerationResult( $out_path, $source_file_mime, false, $width, $height ); } else { $out_path = $preferred_out_file_dir . DIRECTORY_SEPARATOR . $preferred_out_file_name . '.' . Mime\mime_to_ext($preferred_out_mime); $gd_other = self::createCanvas($preferred_out_mime, $max_width, $max_height); \imagecopyresampled($gd_other, $gd, 0, 0, 0, 0, $max_width, $max_height, $width, $height); if (!self::imageSaveTo($gd_other, $out_path, $preferred_out_mime)) { \imagedestroy($gd_other); throw new \RuntimeException("Could not create thumbnail file at '$out_path'"); } \imagedestroy($gd_other); return new ThumbGenerationResult( $out_path, $preferred_out_mime, false, $max_width, $max_height ); } } /** * @param bool $strip_redraw If the EXIF metadata should be stripped by redrawing it. * May cause the loss of color profiles. Orientation is still handled. */ public function __construct(bool $strip_redraw) { $this->strip_redraw = $strip_redraw; } public function supportsMime(string $mime): bool { $info = \gd_info(); return ($mime === 'image/jpeg' && $info['JPEG Support']) || ($mime === 'image/png' && $info['PNG Support']) || ($mime === 'image/gif' && $info['GIF Read Support'] && $info['GIF Create Support']) || ($mime === 'image/webp' && $info['WebP Support']) || $mime === 'image/bmp' || ($mime === 'image/avif' && self::PHP81 && $info['AVIF Support']); } public function openHandle(string $file_path, string $file_mime, int $file_kind): mixed { $gd = self::imageCreateFrom($file_path, $file_mime); if ($gd === false) { throw new \RuntimeException("Could not open '$file_path'"); } return [ $gd, $file_path, $file_mime, $file_kind ]; } public function closeHandle(mixed $handle) { \imagedestroy($handle[0]); } public function installMediaAndGenerateThumb( mixed $handle, string $media_preferred_out_file_dir, string $media_preferred_out_file_name, string $thumb_preferred_out_file_dir, string $thumb_preferred_out_file_name, string $thumb_preferred_out_mime, int $thumb_max_width, int $thumb_max_height ) { list($gd, $source_file_path, $source_file_mime, $source_file_kind) = $handle; $out_path = $media_preferred_out_file_dir . DIRECTORY_SEPARATOR . $media_preferred_out_file_name . '.' . Mime\mime_to_ext($source_file_mime); if ($this->strip_redraw) { if (!self::imageSaveTo($gd, $out_path, $source_file_mime)) { throw new \RuntimeException("Could not create media file at '$out_path'"); } } else { if ($source_file_kind === self::FILE_KIND_UPLOADED) { if (!Fs\move_or_copy_uploaded($source_file_path, $out_path)) { throw new \RuntimeException("Could not move or copy uploaded file '$source_file_path' to '$out_path'"); } } else { if (!Fs\link_or_copy($source_file_path, $out_path)) { throw new \RuntimeException("Could not link or copy '$source_file_path' to '$out_path'"); } } } return $this->generateThumbImpl( $gd, $source_file_path, $source_file_mime, $source_file_kind, $thumb_preferred_out_file_dir, $thumb_preferred_out_file_name, $thumb_preferred_out_mime, $thumb_max_width, $thumb_max_height ); } public function generateThumb( mixed $handle, string $preferred_out_file_dir, string $preferred_out_file_name, string $preferred_out_mime, int $max_width, int $max_height ): ThumbGenerationResult { list($gd, $source_file_path, $source_file_mime, $source_file_kind) = $handle; return $this->generateThumbImpl( $gd, $source_file_path, $source_file_mime, $source_file_kind, $preferred_out_file_dir, $preferred_out_file_name, $preferred_out_mime, $max_width, $max_height ); } }