. */ /** * This file contains the Image class. * * @author Dinu Florin * @package Modules * @subpackage Images */ /** * Image class. * * @author Dinu Florin * @package Modules * @subpackage Images */ class Image extends Pt { protected $width = null; protected $height = null; protected $img = null; protected static $imageTypes = array('image/jpeg', 'image/gif', 'image/png', 'image/wbmp'); static public function LabToLch($color) { $h = atan2($color['b'], $color['a']); //Quadrant by signs if($h > 0) $h = ($h/pi())*180; else $h = 360 - (abs($h)/pi())*180; $c = sqrt($color['a']*$color['a'] + $color['b']*$color['b']); return array('L'=>$color['L'], 'c'=>$c, 'h'=>$h); } static public function RgbToLab($color) { return self::XYZToLab(self::RgbToXYZ($color)); } static public function LabToRgb($color) { return self::XYZToRgb(self::LabToXYZ($color)); } static public function LabToXYZ($color) { $Y = ($color['L'] + 16)/116; $X = $color['a']/500 + $Y; $Z = $Y - $color['b']/200; $py = pow($Y, 3); $px = pow($X, 3); $pz = pow($Z, 3); if($py > 0.008856) $Y = $py; else $Y = ($Y - 16/116)/7.787; if($px > 0.008856 ) $X = $px; else $X = ($X - 16/116)/7.787; if($pz > 0.008856) $Z = $pz; else $Z = ($Z - 16/116)/7.787; $X = 95.047*$X; //ref_X = 95.047 Observer= 2°, Illuminant= D65 $Y = 100*$Y; //ref_Y = 100.000 $Z = 108.833*$Z; //ref_Z = 108.883 return array('X'=>$X, 'Y'=>$Y, 'Z'=>$Z); } static public function XYZToLab($color) { //CIE-L*a*b* $color['X'] /= 95.047; //ref_X = 95.047 Observer= 2°, Illuminant= D65 $color['Y'] /= 100; //ref_Y = 100.000 $color['Z'] /= 108.883; //ref_Z = 108.883 if($color['X'] > 0.008856) $color['X'] = pow($color['X'], 1/3); else $color['X'] = 7.787*$color['X'] + 16/116; if($color['Y'] > 0.008856) $color['Y'] = pow($color['Y'], 1/3); else $color['Y'] = 7.787*$color['Y'] + 16/116; if($color['Z'] > 0.008856) $color['Z'] = pow($color['Z'], 1/3); else $color['Z'] = 7.787*$color['Z'] + 16/116; $L = 116*$color['Y'] - 16; $a = 500*($color['X'] - $color['Y']); $b = 200*($color['Y'] - $color['Z']); return array('L'=>$L, 'a'=>$a, 'b'=>$b); } static public function XYZToRgb($color) { $color['X'] /= 100; //X from 0 to 95.047 (Observer = 2°, Illuminant = D65) $color['Y'] /= 100; //Y from 0 to 100.000 $color['Z'] /= 100; //Z from 0 to 108.883 $R = $color['X']*3.2406 + $color['Y']*(-1.5372) + $color['Z']*(-0.4986); $G = $color['X']*(-0.9689) + $color['Y']*1.8758 + $color['Z']*0.0415; $B = $color['X']*0.0557 + $color['Y']*(-0.2040) + $color['Z']*1.0570; if($R > 0.0031308) $R = 1.055*pow($R, 1/2.4 ) - 0.055; else $R = 12.92*$R; if($G > 0.0031308) $G = 1.055*pow($G, 1/2.4) - 0.055; else $G = 12.92*$G; if($B > 0.0031308) $B = 1.055*pow($B, 1/2.4) - 0.055; else $B = 12.92*$B; return array('r'=>$R*255, 'g'=>$G*255, 'b'=>$B*255); } static public function RgbToXYZ($color) { $color['r'] /= 255; $color['g'] /= 255; $color['b'] /= 255; if($color['r'] > 0.04045 ) $color['r'] = pow(($color['r'] + 0.055)/1.055, 2.4); else $color['r'] = $color['r']/12.92; if($color['g'] > 0.04045) $color['g'] = pow(($color['g']+ 0.055)/1.055, 2.4); else $color['g'] = $color['g']/12.92; if($color['b'] > 0.04045) $color['b'] = pow(($color['b'] + 0.055)/1.055, 2.4); else $color['b'] = $color['b']/12.92; $color['r'] *= 100; $color['g'] *= 100; $color['b'] *= 100; //Observer. = 2°, Illuminant = D65 $X = $color['r']*0.4124 + $color['g']*0.3576 + $color['b']*0.1805; $Y = $color['r']*0.2126 + $color['g']*0.7152 + $color['b']*0.0722; $Z = $color['r']*0.0193 + $color['g']*0.1192 + $color['b']*0.9505; return array('X'=>$X, 'Y'=>$Y, 'Z'=>$Z); } /** * Encode a a color and return CSS style string. * * @param array The RGB color to encode. * @return string The encoded color. */ static public function encodeColor($color) { $r = dechex($color['r']); $g = dechex($color['g']); $b = dechex($color['b']); return sprintf('#%02s%02s%02s', $r, $g, $b); } /** * Decode a CSS style color and return an array with the components. * * @param string $color A string representing the hex color, eg. #FF0000 or #F00 * @return array The decoded color: array('r'=>int, 'g'=>int, 'b'=>int) */ static public function decodeColor($color) { assert('is_string($color)'); $color = trim($color); $color = ltrim($color, '#'); assert('strlen($color) == 6 or strlen($color) == 3'); if(strlen($color) == 6) { return array( 'r'=>hexdec('0x'.$color{0}.$color{1}), 'g'=>hexdec('0x'.$color{2}.$color{3}), 'b'=>hexdec('0x'.$color{4}.$color{5}) ); } else { return array( 'r'=>hexdec('0x'.$color{0}.$color{0}), 'g'=>hexdec('0x'.$color{1}.$color{1}), 'b'=>hexdec('0x'.$color{2}.$color{2}) ); } } /** * Calculate RGB color from HSV color, reverse of Image::rgbToHsv() * * @param array $color An array of hsv values: array('h'=>number, 's'=>number, 'v'=>number). * Hue is in degrees * Lightness is between 0 and 1 * Saturation is between 0 and 1 * @return array The coresponding rgb values: array('r'=>[0-255], 'g'=>[0-255], 'b'=>[0-255]). */ public static function hsvToRgb($color) { $sat = array(); $c2 = array(); while($color['h'] < 0) { $color['h'] += 360; } while($color['h'] > 360) { $color['h'] -= 360; } if($color['h'] < 120) { $sat['r'] = (120 - $color['h']) / 60.0; $sat['g'] = $color['h'] / 60.0; $sat['b'] = 0; } elseif($color['h'] < 240) { $sat['r'] = 0; $sat['g'] = (240 - $color['h']) / 60.0; $sat['b'] = ($color['h'] - 120) / 60.0; } else { $sat['r'] = ($color['h'] - 240) / 60.0; $sat['g'] = 0; $sat['b'] = (360 - $color['h']) / 60.0; } $sat['r'] = min($sat['r'], 1); $sat['g'] = min($sat['g'], 1); $sat['b'] = min($sat['b'], 1); $c2['r'] = (1 - $color['s'] + $color['s'] * $sat['r']) * $color['v']; $c2['g'] = (1 - $color['s'] + $color['s'] * $sat['g']) * $color['v']; $c2['b'] = (1 - $color['s'] + $color['s'] * $sat['b']) * $color['v']; $c2['r'] *= 255; $c2['g'] *= 255; $c2['b'] *= 255; return $c2; } /** * Calculate HSV from RGB. Reverse of Image::hsvToRgb() * * @param array $color A rgb color: array('r'=>[0-255], 'g'=>[0-255], 'b'=>[0-255]). * @return array A hsv color: array('h'=>number, 's'=>number, 'v'=>number). * Hue is in degrees * Lightness is betweeen 0 and 1 * Saturation is between 0 and 1 */ public static function rgbToHsv($color) { $color['r'] /= 255; $color['g'] /= 255; $color['b'] /= 255; $c2 = array(); $themin = min($color['r'],min($color['g'], $color['b'])); $themax = max($color['r'],max($color['g'], $color['b'])); $delta = $themax - $themin; $c2['v'] = $themax; $c2['s'] = 0; if($themax > 0) { $c2['s'] = $delta / $themax; } $c2['h'] = 0; if($delta > 0) { if($themax == $color['r'] && $themax != $color['g']) { $c2['h'] += ($color['g'] - $color['b']) / $delta; } if($themax == $color['g'] && $themax != $color['b']) { $c2['h'] += (2 + ($color['b'] - $color['r']) / $delta); } if($themax == $color['b'] && $themax != $color['r']) { $c2['h'] += (4 + ($color['r'] - $color['g']) / $delta); } $c2['h'] *= 60; } return($c2); } /** * Constructor. * * @param number $width The width of the image, default = 0. * @param number $height The height of the image, default = 0. */ public function __construct($width=0, $height=0) { assert('is_numeric($width)'); assert('is_numeric($height)'); $this->width = $width; $this->height = $height; } /** * Load an image from file. * * @param string $file The image file. * @param string $type The file type, if it's null it will try to load the image by trying all the image types. * @return mixed A GD image resource, or false if the function failed. */ protected static function loadFromFile($file, $type=null) { switch($type) { case 'image/jpeg': $img = @imagecreatefromjpeg($file); break; case 'image/gif': $img = @imagecreatefromgif($file); break; case 'image/png': $img = @imagecreatefrompng($file); @imagealphablending($img, true); break; case 'image/wbmp': $img = @imagecreatefromwbmp($file); break; default: foreach(self::$imageTypes as $type) { $img = self::loadFromFile($file, $type); if($img) { break; } } } return $img; } /** * Load an image from file. The changes made to this image will be lost. * * @param string $file The filename. * @param string $type The mime-type of the image (default: auto) * @throws ImageException * @throws GenericException * @warning The changes made to this image will be lost. */ public function load($file, $type=NULL) { assert('is_string($file)'); assert('is_string($type) or is_null($type)'); $file = Util::makeStdPath($file); if(file_exists($file)) { if(is_readable($file)) { if(isset($this->img)) @imagedestroy($this->img); $img = self::loadFromFile($file, $type); if($img === false) { throw new ImageException('Could not load image'); } $this->img = $img; } else { throw new GenericException("File {$file} is not readable"); } } else { throw new GenericException("File {$file} does not exist"); } } /** * Save the image to a file. * * @param string $file The file name. * @param string $type The mime-type to save to (default: image/jpeg). * @throws ImageException */ public function save($file, $type='image/jpeg') { assert('is_string($file)'); assert('is_string($type)'); switch($type) { case 'image/jpeg': $res = imagejpeg($this->img, $file, 100); break; case 'image/gif': $res = imagegif($this->img, $file); break; case 'image/png': $res = imagepng($this->img, $file); imagealphablending($this->img, false); imagesavealpha($this->img, true); break; case 'image/wbmp': $res = imagewbmp($this->img, $file); break; default: throw new ImageException('Unknown image type'); break; } if($res === false) { throw new ImageException('Could not save the image'); } } /** * Output image data. * * @param string $type The image data type (default: image/jpeg). * @throws ImageException */ public function output($type='image/jpeg') { assert('is_string($type)'); switch($type) { case 'image/jpeg': $res = imagejpeg($this->img, null, 100); break; case 'image/gif': $res = imagegif($this->img); break; case 'image/png': $res = imagepng($this->img); break; case 'image/wbmp': $res = imagewbmp($this->img); break; default: throw new ImageException('Unknown image type'); break; } if($res === false) { throw new ImageException('Could not print the image'); } } /** * Destructor. */ public function __destruct() { } /** * Get the width of the image. * * @return number * @throws ImageException */ public function getWidth() { $this->width = imagesx($this->img); return $this->width; } /** * Get the height of the image. * * @return number * @throws ImageException */ public function getHeight($what=NULL) { $this->height = imagesy($this->img); return $this->height; } /** * Apply filter. * * @param AbstractImageFilter $filter The filter to apply. */ public function applyFilter(AbstractImageFilter $filter) { if(!$this->img) { $this->img = imagecreatetruecolor($this->width, $this->height); } $filter->applyTo($this); } /** * Get native PHP GD2 resource. * * @return resource */ public function getResource() { if(!$this->img) { $this->img = imagecreatetruecolor($this->width, $this->height); } return $this->img; } /** * Set the native PHP GD2 resource. This will be called by the filters. * * @param resource $img The new image resource. */ public function setResource($img) { $this->img = $img; } } /** * Image exception. * * @package Modules * @subpackage Images */ class ImageException extends GenericException { protected $message = 'Image exception'; } ?>