1<?php 2/** 3 * Class Ico 4 * Open ICO files and extract any size/depth to PNG format 5 * http://www.phpclasses.org/package/2369-PHP-Extract-graphics-from-ico-files-into-PNG-images.html 6 * @author Diogo Resende <me@diogoresende.net> 7 * @version 0.1 8 * @license GPL 9 **/ 10class ico { 11 /** 12 * Ico::bgcolor 13 * Background color on icon extraction 14 * 15 * @type array(R, G, B) = array(255, 255, 255) 16 * @var public 17 **/ 18 var $bgcolor = array(255, 255, 255); 19 20 /** 21 * Ico::bgcolor_transparent 22 * Is background color transparent? 23 * 24 * @type boolean = false 25 * @var public 26 **/ 27 var $bgcolor_transparent = false; 28 29 /** 30 * Class constructor 31 * 32 * @param optional string $path Path to ICO file 33 * @return void 34 **/ 35 function __construct($path = '') { 36 if (strlen($path) > 0) { 37 $this->LoadFile($path); 38 } 39 } 40 41 /** 42 * Ico::Ico() 43 * 44 * @param optional string $path Path to ICO file 45 * @return void 46 **/ 47 function Ico($path = '') { 48 self::__construct($path); 49 } 50 51 /** 52 * Ico::LoadFile() 53 * Load an ICO file (don't need to call this is if fill the 54 * parameter in the class constructor) 55 * 56 * @param string $path Path to ICO file 57 * @return boolean Success 58 **/ 59 function LoadFile($path) { 60 $this->_filename = $path; 61 if (($fp = @fopen($path, 'rb')) !== false) { 62 $data = ''; 63 while (!feof($fp)) { 64 $data .= fread($fp, 4096); 65 } 66 fclose($fp); 67 68 return $this->LoadData($data); 69 } 70 return false; 71 } 72 73 /** 74 * Ico::LoadData() 75 * Load an ICO data. If you prefer to open the file 76 * and return the binary data you can use this function 77 * directly. Otherwise use LoadFile() instead. 78 * 79 * @param string $data Binary data of ICO file 80 * @return boolean Success 81 **/ 82 function LoadData($data) { 83 $this->formats = array(); 84 85 /** 86 * ICO header 87 **/ 88 $icodata = unpack("vReserved/vType/vCount", $data); 89 $this->ico = $icodata; 90 $data = substr($data, 6); 91 92 // Accept ICO files only. 93 if (($this->ico['Reserved'] != 0) || ($this->ico['Type'] != 1)) { 94 return False; 95 } 96 97 /** 98 * Extract each icon header 99 **/ 100 for ($i = 0; $i < $this->ico['Count']; $i ++) { 101 if (strlen($data)) 102 { 103 $icodata = unpack("CWidth/CHeight/CColorCount/CReserved/SPlanes/SBitCount/LSizeInBytes/LFileOffset", $data); 104 $icodata['FileOffset'] -= ($this->ico['Count'] * 16) + 6; 105 if ($icodata['ColorCount'] == 0) $icodata['ColorCount'] = 256; 106 $this->formats[] = $icodata; 107 108 $data = substr($data, 16); 109 } 110 else 111 { 112 error_log(__METHOD__.__LINE__.' no data available:'.array2string($this->ico)); 113 break; 114 } 115 } 116 117 /** 118 * Extract aditional headers for each extracted icon header 119 **/ 120 for ($i = 0; $i < count($this->formats); $i++) { 121 $icodata = unpack("LSize/LWidth/LHeight/SPlanes/SBitCount/LCompression/LImageSize/LXpixelsPerM/LYpixelsPerM/LColorsUsed/LColorsImportant", substr($data, $this->formats[$i]['FileOffset'])); 122 123 $this->formats[$i]['header'] = $icodata; 124 $this->formats[$i]['colors'] = array(); 125 126 $this->formats[$i]['BitCount'] = $this->formats[$i]['header']['BitCount']; 127 128 switch ($this->formats[$i]['BitCount']) { 129 case 32: 130 case 24: 131 $length = $this->formats[$i]['header']['Width'] * $this->formats[$i]['header']['Height'] * ($this->formats[$i]['BitCount'] / 8); 132 $this->formats[$i]['data'] = substr($data, $this->formats[$i]['FileOffset'] + $this->formats[$i]['header']['Size'], $length); 133 break; 134 case 8: 135 case 4: 136 $icodata = substr($data, $this->formats[$i]['FileOffset'] + $icodata['Size'], $this->formats[$i]['ColorCount'] * 4); 137 $offset = 0; 138 for ($j = 0; $j < $this->formats[$i]['ColorCount']; $j++) { 139 $this->formats[$i]['colors'][] = array( 140 'red' => ord($icodata[$offset]), 141 'green' => ord($icodata[$offset + 1]), 142 'blue' => ord($icodata[$offset + 2]), 143 'reserved' => ord($icodata[$offset + 3]) 144 ); 145 $offset += 4; 146 } 147 $length = $this->formats[$i]['header']['Width'] * $this->formats[$i]['header']['Height'] * (1 + $this->formats[$i]['BitCount']) / $this->formats[$i]['BitCount']; 148 $this->formats[$i]['data'] = substr($data, $this->formats[$i]['FileOffset'] + ($this->formats[$i]['ColorCount'] * 4) + $this->formats[$i]['header']['Size'], $length); 149 break; 150 case 1: 151 $icodata = substr($data, $this->formats[$i]['FileOffset'] + $icodata['Size'], $this->formats[$i]['ColorCount'] * 4); 152 153 $this->formats[$i]['colors'][] = array( 154 'blue' => ord($icodata[0]), 155 'green' => ord($icodata[1]), 156 'red' => ord($icodata[2]), 157 'reserved' => ord($icodata[3]) 158 ); 159 $this->formats[$i]['colors'][] = array( 160 'blue' => ord($icodata[4]), 161 'green' => ord($icodata[5]), 162 'red' => ord($icodata[6]), 163 'reserved' => ord($icodata[7]) 164 ); 165 166 $length = $this->formats[$i]['header']['Width'] * $this->formats[$i]['header']['Height'] / 8; 167 $this->formats[$i]['data'] = substr($data, $this->formats[$i]['FileOffset'] + $this->formats[$i]['header']['Size'] + 8, $length); 168 break; 169 } 170 $this->formats[$i]['data_length'] = strlen($this->formats[$i]['data']); 171 } 172 173 return true; 174 } 175 176 /** 177 * Ico::TotalIcons() 178 * Return the total icons extracted at the moment 179 * 180 * @return integer Total icons 181 **/ 182 function TotalIcons() { 183 return count($this->formats); 184 } 185 186 /** 187 * Ico::GetIconInfo() 188 * Return the icon header corresponding to that index 189 * 190 * @param integer $index Icon index 191 * @return resource Icon header 192 **/ 193 function GetIconInfo($index) { 194 if (isset($this->formats[$index])) { 195 return $this->formats[$index]; 196 } 197 return false; 198 } 199 200 /** 201 * Ico::SetBackground() 202 * Changes background color of extraction. You can set 203 * the 3 color components or set $red = '#xxxxxx' (HTML format) 204 * and leave all other blanks. 205 * 206 * @param optional integer $red Red component 207 * @param optional integer $green Green component 208 * @param optional integer $blue Blue component 209 * @return void 210 **/ 211 function SetBackground($red = 255, $green = 255, $blue = 255) { 212 if (is_string($red) && preg_match('/^\#[0-9a-f]{6}$/', $red)) { 213 $green = hexdec($red[3] . $red[4]); 214 $blue = hexdec($red[5] . $red[6]); 215 $red = hexdec($red[1] . $red[2]); 216 } 217 218 $this->bgcolor = array($red, $green, $blue); 219 } 220 221 /** 222 * Ico::SetBackgroundTransparent() 223 * Set background color to be saved as transparent 224 * 225 * @param optional boolean $is_transparent Is Transparent or not 226 * @return boolean Is Transparent or not 227 **/ 228 function SetBackgroundTransparent($is_transparent = true) { 229 return ($this->bgcolor_transparent = $is_transparent); 230 } 231 232 /** 233 * Ico::GetImage() 234 * Return an image resource with the icon stored 235 * on the $index position of the ICO file 236 * 237 * @param integer $index Position of the icon inside ICO 238 * @return resource Image resource 239 **/ 240 function &GetIcon($index) { 241 if (is_null($index)) { 242 $index = count($this->formats)-1; 243 $index = 0; 244 } 245 if (!isset($this->formats[$index])) { 246 return false; 247 } 248 249 /** 250 * create image 251 **/ 252 $im = imagecreatetruecolor($this->formats[$index]['Width'], $this->formats[$index]['Height']); 253 254 /** 255 * paint background 256 **/ 257 $bgcolor = $this->AllocateColor($im, $this->bgcolor[0], $this->bgcolor[1], $this->bgcolor[2]); 258 imagefilledrectangle($im, 0 , 0, $this->formats[$index]['Width'], $this->formats[$index]['Height'], $bgcolor); 259 260 /** 261 * set background color transparent 262 **/ 263 if ($this->bgcolor_transparent) { 264 imagecolortransparent($im, $bgcolor); 265 } 266 267 /** 268 * allocate pallete and get XOR image 269 **/ 270 if (in_array($this->formats[$index]['BitCount'], array(1, 4, 8, 24))) { 271 if ($this->formats[$index]['BitCount'] != 24) { 272 /** 273 * color pallete 274 **/ 275 $c = array(); 276 for ($i = 0; $i < $this->formats[$index]['ColorCount']; $i++) { 277 $c[$i] = $this->AllocateColor($im, $this->formats[$index]['colors'][$i]['blue'], 278 $this->formats[$index]['colors'][$i]['green'], 279 $this->formats[$index]['colors'][$i]['red'], 280 round($this->formats[$index]['colors'][$i]['reserved'] / 255 * 127)); 281 } 282 } 283 284 /** 285 * XOR image 286 **/ 287 $width = $this->formats[$index]['Width']; 288 if (($width % 32) > 0) { 289 $width += (32 - ($this->formats[$index]['Width'] % 32)); 290 } 291 $offset = $this->formats[$index]['Width'] * $this->formats[$index]['Height'] * $this->formats[$index]['BitCount'] / 8; 292 $total_bytes = ($width * $this->formats[$index]['Height']) / 8; 293 $bits = ''; 294 $bytes = 0; 295 $bytes_per_line = ($this->formats[$index]['Width'] / 8); 296 $bytes_to_remove = (($width - $this->formats[$index]['Width']) / 8); 297 for ($i = 0; $i < $total_bytes; $i++) { 298 $bits .= str_pad(decbin(ord($this->formats[$index]['data'][$offset + $i])), 8, '0', STR_PAD_LEFT); 299 $bytes++; 300 if ($bytes == $bytes_per_line) { 301 $i += $bytes_to_remove; 302 $bytes = 0; 303 } 304 } 305 } 306 307 /** 308 * paint each pixel depending on bit count 309 **/ 310 switch ($this->formats[$index]['BitCount']) { 311 case 32: 312 /** 313 * 32 bits: 4 bytes per pixel [ B | G | R | ALPHA ] 314 **/ 315 $offset = 0; 316 for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) { 317 for ($j = 0; $j < $this->formats[$index]['Width']; $j++) { 318 $color = substr($this->formats[$index]['data'], $offset, 4); 319 if (ord($color[3]) > 0) { 320 $c = $this->AllocateColor($im, ord($color[2]), 321 ord($color[1]), 322 ord($color[0]), 323 127 - round(ord($color[3]) / 255 * 127)); 324 imagesetpixel($im, $j, $i, $c); 325 } 326 $offset += 4; 327 } 328 } 329 break; 330 case 24: 331 /** 332 * 24 bits: 3 bytes per pixel [ B | G | R ] 333 **/ 334 $offset = 0; 335 $bitoffset = 0; 336 for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) { 337 for ($j = 0; $j < $this->formats[$index]['Width']; $j++) { 338 if ($bits[$bitoffset] == 0) { 339 $color = substr($this->formats[$index]['data'], $offset, 3); 340 $c = $this->AllocateColor($im, ord($color[2]), ord($color[1]), ord($color[0])); 341 imagesetpixel($im, $j, $i, $c); 342 } 343 $offset += 3; 344 $bitoffset++; 345 } 346 } 347 break; 348 case 8: 349 /** 350 * 8 bits: 1 byte per pixel [ COLOR INDEX ] 351 **/ 352 $offset = 0; 353 for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) { 354 for ($j = 0; $j < $this->formats[$index]['Width']; $j++) { 355 if ($bits[$offset] == 0) { 356 $color = ord(substr($this->formats[$index]['data'], $offset, 1)); 357 imagesetpixel($im, $j, $i, $c[$color]); 358 } 359 $offset++; 360 } 361 } 362 break; 363 case 4: 364 /** 365 * 4 bits: half byte/nibble per pixel [ COLOR INDEX ] 366 **/ 367 $offset = 0; 368 $maskoffset = 0; 369 $leftbits = true; 370 for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) { 371 for ($j = 0; $j < $this->formats[$index]['Width']; $j++) { 372 if ($leftbits) { 373 $color = substr($this->formats[$index]['data'], $offset, 1); 374 $color = array( 375 'High' => bindec(substr(decbin(ord($color)), 0, 4)), 376 'Low' => bindec(substr(decbin(ord($color)), 4)) 377 ); 378 if ($bits[$maskoffset++] == 0) { 379 imagesetpixel($im, $j, $i, $c[$color['High']]); 380 } 381 $leftbits = false; 382 } else { 383 if ($bits[$maskoffset++] == 0) { 384 imagesetpixel($im, $j, $i, $c[$color['Low']]); 385 } 386 $offset++; 387 $leftbits = true; 388 } 389 } 390 } 391 break; 392 case 1: 393 /** 394 * 1 bit: 1 bit per pixel (2 colors, usually black&white) [ COLOR INDEX ] 395 **/ 396 $colorbits = ''; 397 $total = strlen($this->formats[$index]['data']); 398 for ($i = 0; $i < $total; $i++) { 399 $colorbits .= str_pad(decbin(ord($this->formats[$index]['data'][$i])), 8, '0', STR_PAD_LEFT); 400 } 401 402 $total = strlen($colorbits); 403 $offset = 0; 404 for ($i = $this->formats[$index]['Height'] - 1; $i >= 0; $i--) { 405 for ($j = 0; $j < $this->formats[$index]['Width']; $j++) { 406 if ($bits[$offset] == 0) { 407 imagesetpixel($im, $j, $i, $c[$colorbits[$offset]]); 408 } 409 $offset++; 410 } 411 } 412 break; 413 } 414 415 return $im; 416 } 417 418 /** 419 * Ico::AllocateColor() 420 * Allocate a color on $im resource. This function prevents 421 * from allocating same colors on the same pallete. Instead 422 * if it finds that the color is already allocated, it only 423 * returns the index to that color. 424 * It supports alpha channel. 425 * 426 * @param resource $im Image resource 427 * @param integer $red Red component 428 * @param integer $green Green component 429 * @param integer $blue Blue component 430 * @param optional integer $alphpa Alpha channel 431 * @return integer Color index 432 **/ 433 function AllocateColor(&$im, $red, $green, $blue, $alpha = 0) { 434 $c = imagecolorexactalpha($im, $red, $green, $blue, $alpha); 435 if ($c >= 0) { 436 return $c; 437 } 438 return imagecolorallocatealpha($im, $red, $green, $blue, $alpha); 439 } 440} 441