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