1*c609f1dcSAndreas Gohr<?php 2*c609f1dcSAndreas Gohr 3*c609f1dcSAndreas Gohr/* 4*c609f1dcSAndreas GohrCopyright 2011-2016 Chris Jean & iThemes 5*c609f1dcSAndreas GohrLicensed under GPLv2 or above 6*c609f1dcSAndreas Gohr*/ 7*c609f1dcSAndreas Gohr 8*c609f1dcSAndreas Gohrnamespace splitbrain\phpico; 9*c609f1dcSAndreas Gohr 10*c609f1dcSAndreas Gohrclass PhpIco 11*c609f1dcSAndreas Gohr{ 12*c609f1dcSAndreas Gohr /** 13*c609f1dcSAndreas Gohr * Images in the BMP format. 14*c609f1dcSAndreas Gohr * 15*c609f1dcSAndreas Gohr * @var array 16*c609f1dcSAndreas Gohr */ 17*c609f1dcSAndreas Gohr private $images = array(); 18*c609f1dcSAndreas Gohr 19*c609f1dcSAndreas Gohr /** 20*c609f1dcSAndreas Gohr * Constructor - Create a new ICO generator. 21*c609f1dcSAndreas Gohr * 22*c609f1dcSAndreas Gohr * If the constructor is not passed a file, a file will need to be supplied using the {@link PHP_ICO::add_image} 23*c609f1dcSAndreas Gohr * function in order to generate an ICO file. 24*c609f1dcSAndreas Gohr * 25*c609f1dcSAndreas Gohr * @param string $file Optional. Path to the source image file. 26*c609f1dcSAndreas Gohr * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) 27*c609f1dcSAndreas Gohr * that the source image should be rendered at in the generated ICO file. 28*c609f1dcSAndreas Gohr * If sizes are not supplied, the size of the source image will be used. 29*c609f1dcSAndreas Gohr */ 30*c609f1dcSAndreas Gohr public function __construct($file = false, $sizes = array()) 31*c609f1dcSAndreas Gohr { 32*c609f1dcSAndreas Gohr $required_functions = array( 33*c609f1dcSAndreas Gohr 'getimagesize', 34*c609f1dcSAndreas Gohr 'imagecreatefromstring', 35*c609f1dcSAndreas Gohr 'imagecreatetruecolor', 36*c609f1dcSAndreas Gohr 'imagecolortransparent', 37*c609f1dcSAndreas Gohr 'imagecolorallocatealpha', 38*c609f1dcSAndreas Gohr 'imagealphablending', 39*c609f1dcSAndreas Gohr 'imagesavealpha', 40*c609f1dcSAndreas Gohr 'imagesx', 41*c609f1dcSAndreas Gohr 'imagesy', 42*c609f1dcSAndreas Gohr 'imagecopyresampled', 43*c609f1dcSAndreas Gohr ); 44*c609f1dcSAndreas Gohr 45*c609f1dcSAndreas Gohr foreach ($required_functions as $function) { 46*c609f1dcSAndreas Gohr if (!function_exists($function)) { 47*c609f1dcSAndreas Gohr throw new \RuntimeException( 48*c609f1dcSAndreas Gohr "The PhpIco class was unable to find the $function function, which is part of the GD library. ". 49*c609f1dcSAndreas Gohr 'Ensure that the system has the GD library installed and that PHP has access to it through a PHP '. 50*c609f1dcSAndreas Gohr 'interface, such as PHP\'s GD module. Since this function was not found, the library will be '. 51*c609f1dcSAndreas Gohr 'unable to create ICO files.' 52*c609f1dcSAndreas Gohr ); 53*c609f1dcSAndreas Gohr } 54*c609f1dcSAndreas Gohr } 55*c609f1dcSAndreas Gohr 56*c609f1dcSAndreas Gohr if ($file) { 57*c609f1dcSAndreas Gohr $this->addImage($file, $sizes); 58*c609f1dcSAndreas Gohr } 59*c609f1dcSAndreas Gohr } 60*c609f1dcSAndreas Gohr 61*c609f1dcSAndreas Gohr /** 62*c609f1dcSAndreas Gohr * Add an image to the generator. 63*c609f1dcSAndreas Gohr * 64*c609f1dcSAndreas Gohr * This function adds a source image to the generator. It serves two main purposes: add a source image if one was 65*c609f1dcSAndreas Gohr * not supplied to the constructor and to add additional source images so that different images can be supplied for 66*c609f1dcSAndreas Gohr * different sized images in the resulting ICO file. For instance, a small source image can be used for the small 67*c609f1dcSAndreas Gohr * resolutions while a larger source image can be used for large resolutions. 68*c609f1dcSAndreas Gohr * 69*c609f1dcSAndreas Gohr * @param string $file Path to the source image file. 70*c609f1dcSAndreas Gohr * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source 71*c609f1dcSAndreas Gohr * image should be rendered at in the generated ICO file. If sizes are not supplied, the size 72*c609f1dcSAndreas Gohr * of the source image will be used. 73*c609f1dcSAndreas Gohr * @return boolean true on success and false on failure. 74*c609f1dcSAndreas Gohr */ 75*c609f1dcSAndreas Gohr public function addImage($file, $sizes = array()) 76*c609f1dcSAndreas Gohr { 77*c609f1dcSAndreas Gohr if (false === ($im = $this->loadImageFile($file))) { 78*c609f1dcSAndreas Gohr return false; 79*c609f1dcSAndreas Gohr } 80*c609f1dcSAndreas Gohr 81*c609f1dcSAndreas Gohr 82*c609f1dcSAndreas Gohr if (empty($sizes)) { 83*c609f1dcSAndreas Gohr $sizes = array(imagesx($im), imagesy($im)); 84*c609f1dcSAndreas Gohr } 85*c609f1dcSAndreas Gohr 86*c609f1dcSAndreas Gohr // If just a single size was passed, put it in array. 87*c609f1dcSAndreas Gohr if (!is_array($sizes[0])) { 88*c609f1dcSAndreas Gohr $sizes = array($sizes); 89*c609f1dcSAndreas Gohr } 90*c609f1dcSAndreas Gohr 91*c609f1dcSAndreas Gohr foreach ((array)$sizes as $size) { 92*c609f1dcSAndreas Gohr list($width, $height) = $size; 93*c609f1dcSAndreas Gohr 94*c609f1dcSAndreas Gohr $new_im = imagecreatetruecolor($width, $height); 95*c609f1dcSAndreas Gohr 96*c609f1dcSAndreas Gohr imagecolortransparent($new_im, imagecolorallocatealpha($new_im, 0, 0, 0, 127)); 97*c609f1dcSAndreas Gohr imagealphablending($new_im, false); 98*c609f1dcSAndreas Gohr imagesavealpha($new_im, true); 99*c609f1dcSAndreas Gohr 100*c609f1dcSAndreas Gohr $source_width = imagesx($im); 101*c609f1dcSAndreas Gohr $source_height = imagesy($im); 102*c609f1dcSAndreas Gohr 103*c609f1dcSAndreas Gohr if (false === imagecopyresampled( 104*c609f1dcSAndreas Gohr $new_im, 105*c609f1dcSAndreas Gohr $im, 106*c609f1dcSAndreas Gohr 0, 107*c609f1dcSAndreas Gohr 0, 108*c609f1dcSAndreas Gohr 0, 109*c609f1dcSAndreas Gohr 0, 110*c609f1dcSAndreas Gohr $width, 111*c609f1dcSAndreas Gohr $height, 112*c609f1dcSAndreas Gohr $source_width, 113*c609f1dcSAndreas Gohr $source_height) 114*c609f1dcSAndreas Gohr ) { 115*c609f1dcSAndreas Gohr continue; 116*c609f1dcSAndreas Gohr } 117*c609f1dcSAndreas Gohr 118*c609f1dcSAndreas Gohr $this->addImageData($new_im); 119*c609f1dcSAndreas Gohr } 120*c609f1dcSAndreas Gohr 121*c609f1dcSAndreas Gohr return true; 122*c609f1dcSAndreas Gohr } 123*c609f1dcSAndreas Gohr 124*c609f1dcSAndreas Gohr /** 125*c609f1dcSAndreas Gohr * Write the ICO file data to a file path. 126*c609f1dcSAndreas Gohr * 127*c609f1dcSAndreas Gohr * @param string $file Path to save the ICO file data into. 128*c609f1dcSAndreas Gohr * @return boolean true on success and false on failure. 129*c609f1dcSAndreas Gohr */ 130*c609f1dcSAndreas Gohr public function saveIco($file) 131*c609f1dcSAndreas Gohr { 132*c609f1dcSAndreas Gohr if (false === ($data = $this->getIcoData())) { 133*c609f1dcSAndreas Gohr return false; 134*c609f1dcSAndreas Gohr } 135*c609f1dcSAndreas Gohr 136*c609f1dcSAndreas Gohr if (false === ($fh = fopen($file, 'w'))) { 137*c609f1dcSAndreas Gohr return false; 138*c609f1dcSAndreas Gohr } 139*c609f1dcSAndreas Gohr 140*c609f1dcSAndreas Gohr if (false === (fwrite($fh, $data))) { 141*c609f1dcSAndreas Gohr fclose($fh); 142*c609f1dcSAndreas Gohr return false; 143*c609f1dcSAndreas Gohr } 144*c609f1dcSAndreas Gohr 145*c609f1dcSAndreas Gohr fclose($fh); 146*c609f1dcSAndreas Gohr 147*c609f1dcSAndreas Gohr return true; 148*c609f1dcSAndreas Gohr } 149*c609f1dcSAndreas Gohr 150*c609f1dcSAndreas Gohr /** 151*c609f1dcSAndreas Gohr * Generate the final ICO data by creating a file header and adding the image data. 152*c609f1dcSAndreas Gohr */ 153*c609f1dcSAndreas Gohr protected function getIcoData() 154*c609f1dcSAndreas Gohr { 155*c609f1dcSAndreas Gohr if (!is_array($this->images) || empty($this->images)) { 156*c609f1dcSAndreas Gohr return false; 157*c609f1dcSAndreas Gohr } 158*c609f1dcSAndreas Gohr 159*c609f1dcSAndreas Gohr 160*c609f1dcSAndreas Gohr $data = pack('vvv', 0, 1, count($this->images)); 161*c609f1dcSAndreas Gohr $pixel_data = ''; 162*c609f1dcSAndreas Gohr 163*c609f1dcSAndreas Gohr $icon_dir_entry_size = 16; 164*c609f1dcSAndreas Gohr 165*c609f1dcSAndreas Gohr $offset = 6 + ($icon_dir_entry_size * count($this->images)); 166*c609f1dcSAndreas Gohr 167*c609f1dcSAndreas Gohr foreach ($this->images as $image) { 168*c609f1dcSAndreas Gohr $data .= pack( 169*c609f1dcSAndreas Gohr 'CCCCvvVV', 170*c609f1dcSAndreas Gohr $image['width'], 171*c609f1dcSAndreas Gohr $image['height'], 172*c609f1dcSAndreas Gohr $image['color_palette_colors'], 173*c609f1dcSAndreas Gohr 0, 174*c609f1dcSAndreas Gohr 1, 175*c609f1dcSAndreas Gohr $image['bits_per_pixel'], 176*c609f1dcSAndreas Gohr $image['size'], 177*c609f1dcSAndreas Gohr $offset 178*c609f1dcSAndreas Gohr ); 179*c609f1dcSAndreas Gohr $pixel_data .= $image['data']; 180*c609f1dcSAndreas Gohr 181*c609f1dcSAndreas Gohr $offset += $image['size']; 182*c609f1dcSAndreas Gohr } 183*c609f1dcSAndreas Gohr 184*c609f1dcSAndreas Gohr $data .= $pixel_data; 185*c609f1dcSAndreas Gohr unset($pixel_data); 186*c609f1dcSAndreas Gohr 187*c609f1dcSAndreas Gohr 188*c609f1dcSAndreas Gohr return $data; 189*c609f1dcSAndreas Gohr } 190*c609f1dcSAndreas Gohr 191*c609f1dcSAndreas Gohr /** 192*c609f1dcSAndreas Gohr * Take a GD image resource and change it into a raw BMP format. 193*c609f1dcSAndreas Gohr */ 194*c609f1dcSAndreas Gohr protected function addImageData($im) 195*c609f1dcSAndreas Gohr { 196*c609f1dcSAndreas Gohr $width = imagesx($im); 197*c609f1dcSAndreas Gohr $height = imagesy($im); 198*c609f1dcSAndreas Gohr 199*c609f1dcSAndreas Gohr 200*c609f1dcSAndreas Gohr $pixel_data = array(); 201*c609f1dcSAndreas Gohr 202*c609f1dcSAndreas Gohr $opacity_data = array(); 203*c609f1dcSAndreas Gohr $current_opacity_val = 0; 204*c609f1dcSAndreas Gohr 205*c609f1dcSAndreas Gohr for ($y = $height - 1; $y >= 0; $y--) { 206*c609f1dcSAndreas Gohr for ($x = 0; $x < $width; $x++) { 207*c609f1dcSAndreas Gohr $color = imagecolorat($im, $x, $y); 208*c609f1dcSAndreas Gohr 209*c609f1dcSAndreas Gohr $alpha = ($color & 0x7F000000) >> 24; 210*c609f1dcSAndreas Gohr $alpha = (1 - ($alpha / 127)) * 255; 211*c609f1dcSAndreas Gohr 212*c609f1dcSAndreas Gohr $color &= 0xFFFFFF; 213*c609f1dcSAndreas Gohr $color |= 0xFF000000 & ($alpha << 24); 214*c609f1dcSAndreas Gohr 215*c609f1dcSAndreas Gohr $pixel_data[] = $color; 216*c609f1dcSAndreas Gohr 217*c609f1dcSAndreas Gohr 218*c609f1dcSAndreas Gohr $opacity = ($alpha <= 127) ? 1 : 0; 219*c609f1dcSAndreas Gohr 220*c609f1dcSAndreas Gohr $current_opacity_val = ($current_opacity_val << 1) | $opacity; 221*c609f1dcSAndreas Gohr 222*c609f1dcSAndreas Gohr if ((($x + 1) % 32) == 0) { 223*c609f1dcSAndreas Gohr $opacity_data[] = $current_opacity_val; 224*c609f1dcSAndreas Gohr $current_opacity_val = 0; 225*c609f1dcSAndreas Gohr } 226*c609f1dcSAndreas Gohr } 227*c609f1dcSAndreas Gohr 228*c609f1dcSAndreas Gohr if (($x % 32) > 0) { 229*c609f1dcSAndreas Gohr while (($x++ % 32) > 0) { 230*c609f1dcSAndreas Gohr $current_opacity_val = $current_opacity_val << 1; 231*c609f1dcSAndreas Gohr } 232*c609f1dcSAndreas Gohr 233*c609f1dcSAndreas Gohr $opacity_data[] = $current_opacity_val; 234*c609f1dcSAndreas Gohr $current_opacity_val = 0; 235*c609f1dcSAndreas Gohr } 236*c609f1dcSAndreas Gohr } 237*c609f1dcSAndreas Gohr 238*c609f1dcSAndreas Gohr $image_header_size = 40; 239*c609f1dcSAndreas Gohr $color_mask_size = $width * $height * 4; 240*c609f1dcSAndreas Gohr $opacity_mask_size = (ceil($width / 32) * 4) * $height; 241*c609f1dcSAndreas Gohr 242*c609f1dcSAndreas Gohr 243*c609f1dcSAndreas Gohr $data = pack('VVVvvVVVVVV', 40, $width, ($height * 2), 1, 32, 0, 0, 0, 0, 0, 0); 244*c609f1dcSAndreas Gohr 245*c609f1dcSAndreas Gohr foreach ($pixel_data as $color) { 246*c609f1dcSAndreas Gohr $data .= pack('V', $color); 247*c609f1dcSAndreas Gohr } 248*c609f1dcSAndreas Gohr 249*c609f1dcSAndreas Gohr foreach ($opacity_data as $opacity) { 250*c609f1dcSAndreas Gohr $data .= pack('N', $opacity); 251*c609f1dcSAndreas Gohr } 252*c609f1dcSAndreas Gohr 253*c609f1dcSAndreas Gohr 254*c609f1dcSAndreas Gohr $image = array( 255*c609f1dcSAndreas Gohr 'width' => $width, 256*c609f1dcSAndreas Gohr 'height' => $height, 257*c609f1dcSAndreas Gohr 'color_palette_colors' => 0, 258*c609f1dcSAndreas Gohr 'bits_per_pixel' => 32, 259*c609f1dcSAndreas Gohr 'size' => $image_header_size + $color_mask_size + $opacity_mask_size, 260*c609f1dcSAndreas Gohr 'data' => $data, 261*c609f1dcSAndreas Gohr ); 262*c609f1dcSAndreas Gohr 263*c609f1dcSAndreas Gohr $this->images[] = $image; 264*c609f1dcSAndreas Gohr } 265*c609f1dcSAndreas Gohr 266*c609f1dcSAndreas Gohr /** 267*c609f1dcSAndreas Gohr * Read in the source image file and convert it into a GD image resource. 268*c609f1dcSAndreas Gohr */ 269*c609f1dcSAndreas Gohr protected function loadImageFile($file) 270*c609f1dcSAndreas Gohr { 271*c609f1dcSAndreas Gohr if (!is_string($file) || empty($file)) { 272*c609f1dcSAndreas Gohr return false; 273*c609f1dcSAndreas Gohr } 274*c609f1dcSAndreas Gohr // Run a cheap check to verify that it is an image file. 275*c609f1dcSAndreas Gohr if (false === ($size = getimagesize($file))) { 276*c609f1dcSAndreas Gohr return false; 277*c609f1dcSAndreas Gohr } 278*c609f1dcSAndreas Gohr 279*c609f1dcSAndreas Gohr if (false === ($file_data = file_get_contents($file))) { 280*c609f1dcSAndreas Gohr return false; 281*c609f1dcSAndreas Gohr } 282*c609f1dcSAndreas Gohr 283*c609f1dcSAndreas Gohr if (false === ($im = imagecreatefromstring($file_data))) { 284*c609f1dcSAndreas Gohr return false; 285*c609f1dcSAndreas Gohr } 286*c609f1dcSAndreas Gohr 287*c609f1dcSAndreas Gohr unset($file_data); 288*c609f1dcSAndreas Gohr 289*c609f1dcSAndreas Gohr 290*c609f1dcSAndreas Gohr return $im; 291*c609f1dcSAndreas Gohr } 292*c609f1dcSAndreas Gohr} 293