// Licensed under the GNU LGPL licence class imageLibException extends Exception { } class image { // Image aspect options const CROP = 'CROP'; const IGNORE_ASPECT_RATIO = 'IGNORE_ASPECT_RATIO'; const FORCE_SIZE_USING_BACKGROUND_COLOR = 'FORCE_SIZE_USING_BACKGROUND_COLOR'; // Libs options const IMAGE_LIB = 'IMAGE_LIB'; const FORCE_GD = 'FORCE_GD'; const FORCE_IMAGICK = 'FORCE_IMAGICK'; const FORCE_IMLIB = 'FORCE_IMLIB'; const USE_GD_FAST_RESIZE_TRICK = 'USE_GD_FAST_RESIZE_TRICK'; const ENABLE_REPORT = 'ENABLE_REPORT'; // Formats options const FORCE_OUTPUT_FORMAT = 'FORCE_OUTPUT_FORMAT'; const PROGRESSIVE_JPEG = 'PROGRESSIVE_JPEG'; const JPEG_QUALITY = 'JPEG_QUALITY'; const PNG_COMPRESSION = 'PNG_COMPRESSION'; // Libs const IMLIB = 1; const IMAGICK = 2; const GD = 3; const TRANSPARENT_COLOR = 'transparent'; static public $default_jpeg_quality = 75; static public $default_png_compression = 9; static public $default_background_color = '000000'; static private $report = array( ); static protected $options = array( ); static protected $cache = array( ); static public function canUseImlib() { return (extension_loaded('imlib') && function_exists('imlib_load_image')); } static public function canUseImagick() { return (extension_loaded('imagick') && class_exists('Imagick')); } static public function canUseGD() { return (extension_loaded('gd') && function_exists('imagecreatefromjpeg')); } static protected function option($id) { if (array_key_exists($id, self::$options)) return self::$options[$id]; else return false; } static protected function parseOptions($options) { self::$options = array(); foreach ($options as $key=>$value) { if (defined($key)) self::$options[$key] = $value; elseif (defined($value)) self::$options[$value] = true; elseif (defined('image::'.$value)) self::$options[$value] = true; elseif (defined('image::'.$key)) self::$options[constant('image::'.$key)] = $value; } unset($options); if (self::option(self::FORCE_IMLIB)) { if (!self::canUseImlib()) throw new imageLibException('Imlib seems not installed'); self::$options[self::IMAGE_LIB] = self::IMLIB; } elseif (self::option(self::FORCE_GD)) { if (!self::canUseGD()) throw new imageLibException('GD seems not installed'); self::$options[self::IMAGE_LIB] = self::GD; } elseif (self::option(self::FORCE_IMAGICK)) { if (!self::canUseImagick()) throw new imageLibException('Imagick seems not installed'); self::$options[self::IMAGE_LIB] = self::IMAGICK; } if ($format = self::option(self::FORCE_OUTPUT_FORMAT)) { if ($format != 'JPEG' && $format != 'PNG') { throw new imageLibException('FORCE_OUTPUT_FORMAT must be either JPEG or PNG'); } } return true; } static public function identify($src_file) { if (empty($src_file)) throw new imageLibException('No source file argument passed'); $hash = sha1($src_file); if (array_key_exists($hash, self::$cache)) { return self::$cache[$hash]; } $image = false; if (self::canUseImlib()) { $im = @imlib_load_image($src_file); if ($im) { $image = array( 'format' => strtoupper(imlib_image_format($im)), 'width' => imlib_image_get_width($im), 'height' => imlib_image_get_height($im), ); imlib_free_image($im); } unset($im); } if (!$image && self::canUseImagick()) { try { $im = new Imagick($src_file); if ($im) { $image = array( 'width' => $im->getImageWidth(), 'height' => $im->getImageHeight(), 'format' => strtoupper($im->getImageFormat()), ); $im->destroy(); } unset($im); } catch (ImagickException $e) { } } if (!$image && self::canUseGD()) { $gd_img = getimagesize($src_file); if (!$gd_img) return false; $image['width'] = $gd_img[0]; $image['height'] = $gd_img[1]; switch ($gd_img[2]) { case IMAGETYPE_GIF: $image['format'] = 'GIF'; break; case IMAGETYPE_JPEG: $image['format'] = 'JPEG'; break; case IMAGETYPE_PNG: $image['format'] = 'PNG'; break; default: $image['format'] = false; break; } } self::$cache[$hash] = $image; return $image; } static public function resize($src_file, $dst_file, $new_width, $new_height=null, $options=array()) { if (empty($src_file)) throw new imageLibException('No source file argument passed'); if (empty($dst_file)) throw new imageLibException('No destination file argument passed'); if (empty($new_width)) throw new imageLibException('Needs at least the new width as argument'); self::parseOptions($options); if (self::option(self::ENABLE_REPORT)) { self::$report = array( 'engine_used' => '', 'time_taken' => 0, 'start_time' => microtime(true), ); } if (!$new_height) { $new_height = $new_width; } $new_height = (int) $new_height; $new_width = (int) $new_width; $lib = false; if (self::option(self::FORCE_IMLIB)) { if (!self::canUseImlib()) throw new imageLibException('Imlib seems not installed'); $lib = self::IMLIB; } elseif (self::option(self::FORCE_GD)) { if (!self::canUseGD()) throw new imageLibException('GD seems not installed'); $lib = self::GD; } elseif (self::option(self::FORCE_IMAGICK)) { if (!self::canUseImagick()) throw new imageLibException('Imagick seems not installed'); $lib = self::IMAGICK; } if (self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR)) { if ($lib == self::IMLIB) { throw new imageLibException("You can't use Imlib to force image size width background color."); } if (!$lib && self::canUseImagick()) { $lib = self::IMAGICK; } elseif (!$lib && self::canUseGD()) { $lib = self::GD; } elseif (!$lib) { throw new imageLibException("You need GD or Imagick to force image size using background color."); } } if (!$lib) { if (self::canUseImlib()) $lib = self::IMLIB; elseif (self::canUseImagick()) $lib = self::IMAGICK; elseif (self::canUseGD()) $lib = self::GD; } if (empty($lib)) { throw new imageLibException('No usable image library found'); } if ($lib == self::IMLIB) { $res = self::imlibResize($src_file, $dst_file, $new_width, $new_height); } elseif ($lib == self::IMAGICK) { $res = self::imagickResize($src_file, $dst_file, $new_width, $new_height); } elseif ($lib == self::GD) { $res = self::gdResize($src_file, $dst_file, $new_width, $new_height); } if (self::option(self::ENABLE_REPORT)) { if ($lib == self::IMLIB) self::$report['engine_used'] = 'imlib'; elseif ($lib == self::IMAGICK) self::$report['engine_used'] = 'imagick'; elseif ($lib == self::GD) self::$report['engine_used'] = 'gd'; self::$report['time_taken'] = microtime(true) - self::$report['start_time']; unset(self::$report['start_time']); } return $res; } static public function getReport() { return self::$report; } static protected function getCropGeometry($w, $h, $new_width, $new_height) { $proportion_src = $w / $h; $proportion_dst = $new_width / $new_height; $x = $y = 0; $out_w = $w; $out_h = $h; if ($proportion_src > $proportion_dst) { $out_w = $h * $proportion_dst; $x = round(($w - $out_w) / 2); } else { $out_h = $w / $proportion_dst; $y = round(($h - $out_h) / 2); } return array($x, $y, round($out_w), round($out_h)); } static protected function imlibResize($src_file, $dst_file, $new_width, $new_height) { $src = @imlib_load_image($src_file); if (!$src) return false; if ($format = self::option(self::FORCE_OUTPUT_FORMAT)) $type = strtolower($format); else $type = strtolower(imlib_image_format($src)); $w = imlib_image_get_width($src); $h = imlib_image_get_height($src); if (self::option(self::CROP)) { list($x, $y, $w, $h) = self::getCropGeometry($w, $h, $new_width, $new_height); $dst = imlib_create_cropped_scaled_image($src, $x, $y, $w, $h, $new_width, $new_height); } elseif (self::option(self::IGNORE_ASPECT_RATIO)) { $dst = imlib_create_scaled_image($src, $new_width, $new_height); } else { if ($w > $h) $new_height = 0; else $new_width = 0; $dst = imlib_create_scaled_image($src, $new_width, $new_height); } imlib_free_image($src); if ($type == 'png') { $png_compression = (int) self::option(self::PNG_COMPRESSION); if (empty($png_compression)) $png_compression = self::$default_png_compression; imlib_image_set_format($dst, 'png'); $res = imlib_save_image($dst, $dst_file, $err, (int)$png_compression); } elseif ($type == 'gif') { imlib_image_set_format($dst, 'gif'); $res = imlib_save_image($dst, $dst_file); } else { $jpeg_quality = (int) self::option(self::JPEG_QUALITY); if (empty($jpeg_quality)) $jpeg_quality = self::$default_jpeg_quality; imlib_image_set_format($dst, 'jpeg'); $res = imlib_save_image($dst, $dst_file, $err, (int)$jpeg_quality); } $w = imlib_image_get_width($dst); $h = imlib_image_get_height($dst); imlib_free_image($dst); return ($res ? array($w, $h) : $res); } static protected function imagickResize($src_file, $dst_file, $new_width, $new_height) { try { $im = new Imagick($src_file); } catch (ImagickException $e) { return false; } if ($format = self::option(self::FORCE_OUTPUT_FORMAT)) $type = strtolower($format); else $type = strtolower($im->getImageFormat()); $im->setImageFormat($type); if (self::option(self::CROP)) { $im->cropThumbnailImage($new_width, $new_height); } elseif (self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR)) { if (self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR) == self::TRANSPARENT_COLOR) $c = new ImagickPixel('transparent'); elseif (strlen(self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR)) != 6) $c = new ImagickPixel('#' . self::$default_background_color); else $c = new ImagickPixel('#' . self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR)); $im->thumbnailImage($new_width, $new_height, true); $bg = new Imagick; $bg->newImage($new_width, $new_height, $c, 'png'); $geometry = $im->getImageGeometry(); /* The overlay x and y coordinates */ $x = ($new_width - $geometry['width']) / 2; $y = ($new_height - $geometry['height']) / 2; $bg->compositeImage($im, imagick::COMPOSITE_OVER, $x, $y); $im->destroy(); $im = $bg; unset($bg); } else { $im->thumbnailImage($new_width, $new_height, !self::option(self::IGNORE_ASPECT_RATIO)); } if ($type == 'png') { $png_compression = (int) self::option(self::PNG_COMPRESSION); if (empty($png_compression)) $png_compression = self::$default_png_compression; $im->setImageFormat('png'); $im->setCompression(Imagick::COMPRESSION_LZW); $im->setCompressionQuality($png_compression * 10); } elseif ($type == 'gif') { $im->setImageFormat('gif'); } else { $jpeg_quality = (int) self::option(self::JPEG_QUALITY); if (empty($jpeg_quality)) $jpeg_quality = self::$default_jpeg_quality; $im->setImageFormat('jpeg'); $im->setCompression(Imagick::COMPRESSION_JPEG); $im->setCompressionQuality($jpeg_quality); } $res = file_put_contents($dst_file, $im); $w = $im->getImageWidth(); $h = $im->getImageHeight(); $im->destroy(); return ($res ? array($w, $h) : $res); } static protected function gdResize($src_file, $dst_file, $new_width, $new_height) { $infos = self::identify($src_file); if (!$infos) return false; if (self::option(self::FORCE_OUTPUT_FORMAT)) $type = self::option(self::FORCE_OUTPUT_FORMAT); else $type = $infos['format']; try { switch ($infos['format']) { case 'JPEG': $src = imagecreatefromjpeg($src_file); break; case 'PNG': $src = imagecreatefrompng($src_file); break; case 'GIF': $src = imagecreatefromgif($src_file); break; default: return false; } if (!$src) throw new Exception("No source image created"); } catch (Exception $e) { throw new imageLibException("Invalid input format: ".$e->getMessage()); } $w = $infos['width']; $h = $infos['height']; $dst_x = 0; $dst_y = 0; $src_x = 0; $src_y = 0; $dst_w = $new_width; $dst_h = $new_height; $src_w = $w; $src_h = $h; $out_w = $new_width; $out_h = $new_height; if (self::option(self::CROP)) { list($src_x, $src_y, $src_w, $src_h) = self::getCropGeometry($w, $h, $new_width, $new_height); } elseif (!self::option(self::IGNORE_ASPECT_RATIO)) { if ($w <= $new_width && $h <= $new_height) { $dst_w = $out_w = $w; $dst_h = $out_h = $h; } else { $in_ratio = $w / $h; $out_ration = $new_width / $new_height; $pic_width = $new_width; $pic_height = $new_height; if ($in_ratio >= $out_ration) { $pic_height = $new_width / $in_ratio; } else { $pic_width = $new_height * $in_ratio; } $dst_w = $out_w = $pic_width; $dst_h = $out_h = $pic_height; } } if (self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR)) { $diff_width = $new_width - $dst_w; $diff_height = $new_height - $dst_h; $offset_x = $diff_width / 2; $offset_y = $diff_height / 2; $dst_x = round($offset_x); $dst_y = round($offset_y); $out_w = $new_width; $out_h = $new_height; } $dst = imagecreatetruecolor($out_w, $out_h); if (!$dst) { return false; } imageinterlace($dst, 0); $use_background = false; if (self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR)) { if (self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR) == self::TRANSPARENT_COLOR || strlen(self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR)) == 6) $use_background = self::option(self::FORCE_SIZE_USING_BACKGROUND_COLOR); else $use_background = self::$default_background_color; } if (!$use_background || $use_background == self::TRANSPARENT_COLOR) { imagealphablending($dst, false); imagesavealpha($dst, true); imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 0, 0, 0, 127)); } else { $color = imagecolorallocate($dst, hexdec(substr($use_background, 0, 2)), hexdec(substr($use_background, 2, 2)), hexdec(substr($use_background, 4, 2)) ); imagefill($dst, 0, 0, $color); } if (self::option(self::USE_GD_FAST_RESIZE_TRICK)) { fastimagecopyresampled($dst, $src, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, 2); } else { imagecopyresampled($dst, $src, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); } imagedestroy($src); try { if ($type == 'PNG') { $png_compression = (int) self::option(self::PNG_COMPRESSION); if (empty($png_compression)) $png_compression = self::$default_png_compression; $res = imagepng($dst, $dst_file, $png_compression, PNG_NO_FILTER); } elseif ($type == 'GIF') { $res = imagegif($dst, $dst_file); } else { $jpeg_quality = (int) self::option(self::JPEG_QUALITY); if (empty($jpeg_quality)) $jpeg_quality = self::$default_jpeg_quality; $res = imagejpeg($dst, $dst_file, $jpeg_quality); } imagedestroy($dst); } catch (Exception $e) { throw new imageLibException("Unable to create destination file: ".$e->getMessage()); } return ($res ? array($dst_w, $dst_h) : $res); } static public function getImageStreamFormat($bytes) { $b = substr($bytes, 0, 4); switch ($b) { case 'GIF8': return 'GIF'; case pack('H*', 'ffd8ffe0'): return 'JPEG'; case pack('H*', '89504e47'): return 'PNG'; default: return false; } } } function fastimagecopyresampled (&$dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality = 3) { // Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled. // Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled". // Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting. // Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain. // // Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero. // Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect. // 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized. // 2 = Up to 95 times faster. Images appear a little sharp, some prefer this over a quality of 3. // 3 = Up to 60 times faster. Will give high quality smooth results very close to imagecopyresampled, just faster. // 4 = Up to 25 times faster. Almost identical to imagecopyresampled for most images. // 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled. if (empty($src_image) || empty($dst_image) || $quality <= 0) { return false; } if ($quality < 5 && (($dst_w * $quality) < $src_w || ($dst_h * $quality) < $src_h)) { $temp = imagecreatetruecolor ($dst_w * $quality + 1, $dst_h * $quality + 1); imagecopyresized ($temp, $src_image, 0, 0, $src_x, $src_y, $dst_w * $quality + 1, $dst_h * $quality + 1, $src_w, $src_h); imagecopyresampled ($dst_image, $temp, $dst_x, $dst_y, 0, 0, $dst_w, $dst_h, $dst_w * $quality, $dst_h * $quality); imagedestroy ($temp); } else { imagecopyresampled ($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); } return true; } ?>