├── LICENSE ├── README.md ├── composer.json ├── example.php └── src └── FaviconGenerator.php /LICENSE: -------------------------------------------------------------------------------- 1 | Favicon Generator 2 | 3 | Copyright (c) 2015, Dmitry Mamontov . 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | * Neither the name of Dmitry Mamontov nor the names of his 19 | contributors may be used to endorse or promote products derived 20 | from this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/dmamontov/favicon/v/stable.svg)](https://packagist.org/packages/dmamontov/favicon) 2 | [![License](https://poser.pugx.org/dmamontov/favicon/license.svg)](https://packagist.org/packages/dmamontov/favicon) 3 | [![Total Downloads](https://poser.pugx.org/dmamontov/favicon/downloads)](https://packagist.org/packages/dmamontov/favicon) 4 | [![PHP Classes](https://img.shields.io/badge/php-classes-blue.svg)](http://www.phpclasses.org/package/9265-PHP-Create-Favicon-images-for-sites-and-mobile-devices.html) 5 | 6 | Favicon Generator 7 | ================= 8 | 9 | This class can create Favicon images for sites and mobile devices. 10 | 11 | It takes a give base icon image and creates multiple versions of the image for use as favicon on Web sites or be displayed by mobile devices like those using systems of Apple, Microsoft, and Android. 12 | 13 | The class can generate all the versions of the icon images with the different sizes, as well the necessary HTML to reference the icon images in a Web page. 14 | 15 | The margins, color, compression, crop method and screen orientation are configurable parameters. 16 | 17 | ## Requirements 18 | * PHP version ~5.3.3 19 | * Module installed Imagick 20 | 21 | ## Installation 22 | 23 | 1) Install [composer](https://getcomposer.org/download/) 24 | 25 | 2) Follow in the project folder: 26 | ```bash 27 | composer require dmamontov/favicon ~1.0.0 28 | ``` 29 | 30 | In config `composer.json` your project will be added to the library `dmamontov/favicon`, who settled in the folder `vendor/`. In the absence of a config file or folder with vendors they will be created. 31 | 32 | If before your project is not used `composer`, connect the startup file vendors. To do this, enter the code in the project: 33 | ```php 34 | require 'path/to/vendor/autoload.php'; 35 | ``` 36 | 37 | ### Example of work 38 | ```php 39 | $fav = new FaviconGenerator(__DIR__ . '/tests.png'); 40 | 41 | $fav->setCompression(FaviconGenerator::COMPRESSION_VERYHIGH); 42 | 43 | $fav->setConfig(array( 44 | 'apple-background' => FaviconGenerator::COLOR_BLUE, 45 | 'apple-margin' => 15, 46 | 'android-background' => FaviconGenerator::COLOR_GREEN, 47 | 'android-margin' => 15, 48 | 'android-name' => 'My app', 49 | 'android-url' => 'http://slobel.ru', 50 | 'android-orientation' => FaviconGenerator::ANDROID_PORTRAIT, 51 | 'ms-background' => FaviconGenerator::COLOR_GREEN, 52 | )); 53 | 54 | echo $fav->createAllAndGetHtml(); 55 | ``` 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dmamontov/favicon", 3 | "description": "Class generation favicon for browsers and devices Android, Apple, Windows and display of html code. It supports a large number of settings such as margins, color, compression, three different methods of crop and screen orientation.", 4 | "type": "library", 5 | "keywords": ["generation", "favicon", "browsers", "android", "apple", "windows"], 6 | "version": "1.0.0", 7 | "license": "BSD-3-Clause", 8 | "homepage": "http://www.slobel.ru/", 9 | "authors": [ 10 | { 11 | "name": "Dmitry Mamontov", 12 | "email": "hello@slobel.ru", 13 | "role": "lead" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.3.3" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "4.6.*", 21 | "apigen/apigen": "~4.1@dev" 22 | }, 23 | "support": { 24 | "email": "support@slobel.ru" 25 | }, 26 | "autoload": { 27 | "files": ["src/FaviconGenerator.php"] 28 | }, 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "1.0.0.x-dev" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | . 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 15 | * * Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in 17 | * the documentation and/or other materials provided with the 18 | * distribution. 19 | * 20 | * * Neither the name of Dmitry Mamontov nor the names of his 21 | * contributors may be used to endorse or promote products derived 22 | * from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 | * POSSIBILITY OF SUCH DAMAGE. 36 | * 37 | * @package favicon 38 | * @author Dmitry Mamontov 39 | * @copyright 2015 Dmitry Mamontov 40 | * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 41 | * @since File available since Release 1.0.0 42 | */ 43 | 44 | require __DIR__ . '/src/FaviconGenerator.php'; 45 | 46 | $fav = new FaviconGenerator(__DIR__ . '/tests.png'); 47 | 48 | $fav->setCompression(FaviconGenerator::COMPRESSION_VERYHIGH); 49 | 50 | $fav->setConfig(array( 51 | 'apple-background' => FaviconGenerator::COLOR_BLUE, 52 | 'apple-margin' => 15, 53 | 'android-background' => FaviconGenerator::COLOR_GREEN, 54 | 'android-margin' => 15, 55 | 'android-name' => 'My app', 56 | 'android-url' => 'http://slobel.ru', 57 | 'android-orientation' => FaviconGenerator::ANDROID_PORTRAIT, 58 | 'ms-background' => FaviconGenerator::COLOR_GREEN, 59 | )); 60 | 61 | echo $fav->createAllAndGetHtml(); 62 | -------------------------------------------------------------------------------- /src/FaviconGenerator.php: -------------------------------------------------------------------------------- 1 | . 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * 15 | * * Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in 17 | * the documentation and/or other materials provided with the 18 | * distribution. 19 | * 20 | * * Neither the name of Dmitry Mamontov nor the names of his 21 | * contributors may be used to endorse or promote products derived 22 | * from this software without specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 | * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 | * POSSIBILITY OF SUCH DAMAGE. 36 | * 37 | * @package favicon 38 | * @author Dmitry Mamontov 39 | * @copyright 2015 Dmitry Mamontov 40 | * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 41 | * @since File available since Release 1.0.0 42 | */ 43 | 44 | /** 45 | * FaviconGenerator - Class generation favicon for browsers and devices Android, Apple, Windows and display of html code. It supports a large number of settings such as margins, color, compression, three different methods of crop and screen orientation. 46 | * 47 | * @author Dmitry Mamontov 48 | * @copyright 2015 Dmitry Mamontov 49 | * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 50 | * @version Release: 1.0.0 51 | * @link https://github.com/dmamontov/favicon 52 | * @since Class available since Release 1.0.0 53 | */ 54 | class FaviconGenerator 55 | { 56 | /* 57 | * No compression. 58 | */ 59 | const COMPRESSION_ORIGINAL = 100; 60 | 61 | /* 62 | * Low compression. 63 | */ 64 | const COMPRESSION_LOW = 75; 65 | 66 | /* 67 | * High compression. 68 | */ 69 | const COMPRESSION_HIGH = 50; 70 | 71 | /* 72 | * Very high compression. 73 | */ 74 | const COMPRESSION_VERYHIGH = 25; 75 | 76 | /* 77 | * Crop the image centered. 78 | */ 79 | const CROPMETHOD_CENTER = 0; 80 | 81 | /* 82 | * Balanced crop image. 83 | */ 84 | const CROPMETHOD_BALANCED = 1; 85 | 86 | /* 87 | * Entropy crop image. 88 | */ 89 | const CROPMETHOD_ENTROPY = 2; 90 | 91 | /* 92 | * Color Teal. 93 | */ 94 | const COLOR_TEAL = '00aba9'; 95 | 96 | /* 97 | * Color Dark Blue. 98 | */ 99 | const COLOR_DARKBLUE = '2b5797'; 100 | 101 | /* 102 | * Color LIght Purple. 103 | */ 104 | const COLOR_LIGHTPURPLE = '9f00a7'; 105 | 106 | /* 107 | * Color Dark Purple. 108 | */ 109 | const COLOR_DARKPURPLE = '603cba'; 110 | 111 | /* 112 | * Color Dark Red. 113 | */ 114 | const COLOR_DARKRED = 'b91d47'; 115 | 116 | /* 117 | * Color Dark Orange. 118 | */ 119 | const COLOR_DARKORANGE = 'da532c'; 120 | 121 | /* 122 | * Color Yellow. 123 | */ 124 | const COLOR_YELLOW = 'ffc40d'; 125 | 126 | /* 127 | * Color Green. 128 | */ 129 | const COLOR_GREEN = '00a300'; 130 | 131 | /* 132 | * Color Blue. 133 | */ 134 | const COLOR_BLUE = '2d89ef'; 135 | 136 | /* 137 | * Portrait screen orientation. 138 | */ 139 | const ANDROID_PORTRAIT = 'portrait'; 140 | 141 | /* 142 | * Landscape screen orientation. 143 | */ 144 | const ANDROID_LANDSCAPE = 'landscape'; 145 | 146 | /** 147 | * Root directory. 148 | * @var string 149 | * @access private 150 | */ 151 | private $root; 152 | 153 | /** 154 | * Flag of forced re-create files. 155 | * @var boolean 156 | * @access private 157 | */ 158 | private $created; 159 | 160 | /** 161 | * Settings generation. 162 | * @var array 163 | * @access private 164 | */ 165 | private $settings = array(); 166 | 167 | /** 168 | * Validation and installation defaults. 169 | * @param string $icon 170 | * @param boolean $created 171 | * @return void 172 | * @access public 173 | * @final 174 | */ 175 | final public function __construct($icon = '', $created = false) 176 | { 177 | $this->created = $created; 178 | $this->root = php_sapi_name() == 'cli' ? __DIR__ : $_SERVER['DOCUMENT_ROOT']; 179 | 180 | if (empty($icon)) { 181 | $icon = "{$this->root}/favicon/.original"; 182 | } 183 | 184 | if (file_exists($icon) === false) { 185 | throw new RuntimeException('File not found', 404); 186 | } 187 | if (class_exists('Imagick') === false) { 188 | throw new RuntimeException('Class Imagick not found'); 189 | } 190 | 191 | if (file_exists("{$this->root}/favicon/.settings")) { 192 | $this->settings = json_decode(file_get_contents("{$this->root}/favicon/.settings"), true); 193 | } else { 194 | $this->settings = array( 195 | 'compression' => self::COMPRESSION_ORIGINAL, 196 | 'cropmethod' => self::CROPMETHOD_CENTER 197 | ); 198 | } 199 | 200 | if ( 201 | file_exists("{$this->root}/favicon/.original") === false || 202 | filesize($icon) != filesize("{$this->root}/favicon/.original") 203 | ) { 204 | @mkdir("{$this->root}/favicon", 0755); 205 | @copy($icon, "{$this->root}/favicon/.original"); 206 | $this->created == true; 207 | } 208 | } 209 | 210 | /** 211 | * Saving settings. 212 | * @return void 213 | * @access public 214 | * @final 215 | */ 216 | final public function __destruct() 217 | { 218 | file_put_contents("{$this->root}/favicon/.settings", json_encode($this->settings)); 219 | } 220 | 221 | /** 222 | * Sets the compression ratio favicon. 223 | * @param integer $compression 224 | * @return boolean 225 | * @access public 226 | * @final 227 | */ 228 | final public function setCompression($compression) 229 | { 230 | if ( 231 | in_array( 232 | $compression, 233 | array( 234 | self::COMPRESSION_ORIGINAL, 235 | self::COMPRESSION_LOW, 236 | self::COMPRESSION_HIGH, 237 | self::COMPRESSION_VERYHIGH 238 | ) 239 | ) == false 240 | ) { 241 | throw new RuntimeException('Unacceptable degree of compression'); 242 | } 243 | 244 | if (isset($this->settings['compression']) && $this->settings['compression'] != $compression) { 245 | $this->created == true; 246 | $this->settings['compression'] = $compression; 247 | } 248 | 249 | return true; 250 | } 251 | 252 | /** 253 | * Gets the compression ratio favicon. 254 | * @return integer 255 | * @access public 256 | * @final 257 | */ 258 | final public function getCompression() 259 | { 260 | return $this->settings['compression']; 261 | } 262 | 263 | /** 264 | * Sets the method of crop images favicon. 265 | * @param integer $method 266 | * @return boolean 267 | * @access public 268 | * @final 269 | */ 270 | final public function setCropMethod($method) 271 | { 272 | if ( 273 | in_array( 274 | $method, 275 | array( 276 | self::CROPMETHOD_CENTER, 277 | self::CROPMETHOD_BALANCED, 278 | self::CROPMETHOD_ENTROPY 279 | ) 280 | ) == false 281 | ) { 282 | throw new RuntimeException('Illegal crop method'); 283 | } 284 | 285 | if (isset($this->settings['cropmethod']) && $this->settings['cropmethod'] != $method) { 286 | $this->created == true; 287 | $this->settings['cropmethod'] = $method; 288 | } 289 | 290 | return true; 291 | } 292 | 293 | /** 294 | * Is produced by the image crop favicon. 295 | * @return integer 296 | * @access public 297 | * @final 298 | */ 299 | final public function getCropMethod() 300 | { 301 | return $this->settings['cropmethod']; 302 | } 303 | 304 | /** 305 | * Set options for generating favicon. 306 | * 307 | * array( 308 | * 'apple-background' => FaviconGenerator::COLOR_BLUE, 309 | * 'apple-margin' => 15, 310 | * 'android-background' => FaviconGenerator::COLOR_GREEN, 311 | * 'android-margin' => 15, 312 | * 'android-name' => 'My app', 313 | * 'android-url' => 'http://test.ru"', 314 | * 'android-orientation' => FaviconGenerator::ANDROID_PORTRAIT, 315 | * 'ms-background' => FaviconGenerator::COLOR_GREEN, 316 | * ) 317 | * 318 | * @param array $config 319 | * @return boolean 320 | * @access public 321 | * @final 322 | */ 323 | final public function setConfig($config = array()) 324 | { 325 | if (is_array($config) === false || count($config) < 1) { 326 | throw new RuntimeException('Invalid configuration'); 327 | } 328 | 329 | foreach ($config as $key => $value) { 330 | if ( 331 | array_key_exists($key, $this->settings) === false || 332 | (array_key_exists($key, $this->settings) && $this->settings[$key] !== $value) 333 | ) { 334 | $this->created == true; 335 | $this->settings = array_merge($this->settings, $config); 336 | break; 337 | } 338 | } 339 | 340 | return true; 341 | } 342 | 343 | /** 344 | * Creates basic favicon. 345 | * @return boolean 346 | * @access public 347 | * @final 348 | */ 349 | final public function createBasic() 350 | { 351 | foreach (array('16x16', '32x32', '96x96') as $size) { 352 | if ($this->created || file_exists("{$this->root}/favicon/favicon-{$size}.png") == false) { 353 | $image = $this->createImage($size); 354 | 355 | $image->writeimage("{$this->root}/favicon/favicon-{$size}.png"); 356 | } 357 | } 358 | 359 | return true; 360 | } 361 | 362 | /** 363 | * Creates a favicon devices Apple. 364 | * @return boolean 365 | * @access public 366 | * @final 367 | */ 368 | final public function createApple() 369 | { 370 | foreach ( 371 | array('57x57', '60x60', '72x72', '76x76', '114x114', '120x120', '144x144', '152x152', '180x180') 372 | as $size 373 | ) { 374 | if ($this->created || file_exists("{$this->root}/favicon/apple-touch-icon-{$size}.png") == false) { 375 | $image = $this->createImage($size); 376 | $image = $this->setColorAndMargin($image, 'apple-background', 'apple-margin'); 377 | 378 | $image->writeimage("{$this->root}/favicon/apple-touch-icon-{$size}.png"); 379 | } 380 | } 381 | 382 | return true; 383 | } 384 | 385 | /** 386 | * Creates a favicon for devices Android. 387 | * @return boolean 388 | * @access public 389 | * @final 390 | */ 391 | final public function createAndroid() 392 | { 393 | $replace = false; 394 | 395 | $manifest = file_exists("{$this->root}/favicon/manifest.json") ? 396 | json_decode(file_get_contents("{$this->root}/favicon/manifest.json"), true) : 397 | array(); 398 | 399 | if ( 400 | isset($this->settings['android-name']) && 401 | empty($this->settings['android-name']) === false && 402 | ( 403 | isset($manifest['name']) === false || 404 | ( 405 | isset($manifest['name']) && 406 | $manifest['name'] != $this->settings['android-name'] 407 | ) 408 | ) 409 | ) { 410 | $replace = true; 411 | $manifest['name'] = $this->settings['android-name']; 412 | } 413 | 414 | if ( 415 | isset($this->settings['android-url']) && 416 | empty($this->settings['android-url']) === false && 417 | ( 418 | isset($manifest['start_url']) === false || 419 | ( 420 | isset($manifest['start_url']) && 421 | $manifest['start_url'] != $this->settings['android-url'] 422 | ) 423 | ) 424 | ) { 425 | $replace = true; 426 | $manifest['start_url'] = $this->settings['android-url']; 427 | } 428 | 429 | if ( 430 | isset($this->settings['android-orientation']) && 431 | empty($this->settings['android-orientation']) === false && 432 | in_array( 433 | $this->settings['android-orientation'], 434 | array(self::ANDROID_LANDSCAPE, self::ANDROID_PORTRAIT) 435 | ) && 436 | ( 437 | isset($manifest['orientation']) === false || 438 | ( 439 | isset($manifest['orientation']) && 440 | $manifest['orientation'] != $this->settings['android-orientation'] 441 | ) 442 | ) 443 | ) { 444 | $replace = true; 445 | $manifest['display'] = 'standalone'; 446 | $manifest['orientation'] = $this->settings['android-orientation']; 447 | } 448 | 449 | $mapDensity = array( 450 | '36x36' => '0.75', 451 | '48x48' => '1.0', 452 | '72x72' => '1.5', 453 | '96x96' => '2.0', 454 | '144x144' => '3.0', 455 | '192x192' => '4.0' 456 | ); 457 | foreach ( 458 | array('36x36', '48x48', '72x72', '96x96', '144x144', '192x192') 459 | as $size 460 | ) { 461 | if ($this->created || file_exists("{$this->root}/favicon/android-chrome-{$size}.png") == false) { 462 | $image = $this->createImage($size); 463 | $image = $this->setColorAndMargin($image, 'android-background', 'android-margin'); 464 | 465 | $image->writeimage("{$this->root}/favicon/android-chrome-{$size}.png"); 466 | } 467 | 468 | $manifest['icons'][] = array( 469 | 'src' => "/favicon/android-chrome-{$size}.png", 470 | 'sizes' => $size, 471 | 'type' => 'image/png', 472 | 'density' => $mapDensity[$size] 473 | ); 474 | } 475 | 476 | if ($replace && count($manifest) > 0) { 477 | file_put_contents("{$this->root}/favicon/manifest.json", json_encode($manifest)); 478 | } 479 | 480 | return true; 481 | } 482 | 483 | /** 484 | * Creates a favicon for devices Microsoft. 485 | * @return boolean 486 | * @access public 487 | * @final 488 | */ 489 | final public function createMicrosoft() 490 | { 491 | foreach (array('70x70', '144x144', '150x150', '310x310', '310x150') as $size) { 492 | if ($this->created || file_exists("{$this->root}/favicon/mstile-{$size}.png") == false) { 493 | if ($size == '310x150') { 494 | $image = $this->createImage('150x150'); 495 | $image->borderImage(new ImagickPixel('none'), 80, 0); 496 | } else { 497 | $image = $this->createImage($size); 498 | } 499 | 500 | $image->writeimage("{$this->root}/favicon/mstile-{$size}.png"); 501 | } 502 | } 503 | 504 | if (file_exists("{$this->root}/favicon/browserconfig.xml") === false || $this->created) { 505 | $browserconfig = 506 | " 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | " . 515 | ( 516 | isset($this->settings['ms-background']) ? 517 | "#{$this->settings['ms-background']}" : 518 | '' 519 | ) . 520 | " 521 | 522 | 523 | "; 524 | 525 | file_put_contents("{$this->root}/favicon/browserconfig.xml", $browserconfig); 526 | } 527 | 528 | return true; 529 | } 530 | 531 | /** 532 | * It creates a favicon for all devices. 533 | * @return boolean 534 | * @access public 535 | * @final 536 | */ 537 | final public function createAll() 538 | { 539 | $this->createBasic(); 540 | $this->createApple(); 541 | $this->createAndroid(); 542 | $this->createMicrosoft(); 543 | 544 | return true; 545 | } 546 | 547 | /** 548 | * Generates meta tags and links to connect favicon. 549 | * @return mixed 550 | * @access public 551 | * @final 552 | */ 553 | final public function getHtml() 554 | { 555 | $html = ''; 556 | 557 | foreach (array('16x16', '32x32', '96x96') as $size) { 558 | if (file_exists("{$this->root}/favicon/favicon-{$size}.png")) { 559 | $html .= "\n"; 560 | } 561 | } 562 | 563 | foreach ( 564 | array('57x57', '60x60', '72x72', '76x76', '114x114', '120x120', '144x144', '152x152', '180x180') 565 | as $size 566 | ) { 567 | if (file_exists("{$this->root}/favicon/apple-touch-icon-{$size}.png")) { 568 | $html .= "\n"; 569 | } 570 | } 571 | 572 | if (file_exists("{$this->root}/favicon/android-chrome-192x192.png")) { 573 | $html .= "\n"; 574 | } 575 | if (file_exists("{$this->root}/favicon/manifest.json")) { 576 | $html .= "\n"; 577 | } 578 | 579 | if (file_exists("{$this->root}/favicon/mstile-144x144.png")) { 580 | $html .= "\n"; 581 | } 582 | if (isset($this->settings['ms-background'])) { 583 | $html .= "settings['ms-background']}\">\n"; 584 | $html .= "settings['ms-background']}\">\n"; 585 | } 586 | 587 | return strlen($html) > 0 ? $html : false; 588 | } 589 | 590 | /** 591 | * It creates a favicon for all devices and generates meta tags and links to connect favicon. 592 | * @return mixed 593 | * @access public 594 | * @final 595 | */ 596 | final public function createAllAndGetHtml() 597 | { 598 | $this->createBasic(); 599 | $this->createApple(); 600 | $this->createAndroid(); 601 | $this->createMicrosoft(); 602 | 603 | return $this->getHtml(); 604 | } 605 | 606 | /** 607 | * Set the fill and margins of the image. 608 | * @param Imagick $method 609 | * @param string $colorKey 610 | * @param string $marginKey 611 | * @return Imagick 612 | * @access private 613 | * @final 614 | */ 615 | final private function setColorAndMargin(Imagick $image, $colorKey = '', $marginKey = '') 616 | { 617 | if (isset($this->settings[$colorKey]) && empty($this->settings[$colorKey]) === false) { 618 | $image->setImageBackgroundColor("#{$this->settings[$colorKey]}"); 619 | 620 | if ( 621 | isset($this->settings[$marginKey]) && 622 | empty($this->settings[$marginKey]) === false && 623 | (int) $this->settings[$marginKey] <= 15 624 | ) { 625 | $source = $image->getImageGeometry(); 626 | $image->resizeImage( 627 | $source['width'] - ($this->settings[$marginKey] * 2), 628 | $source['height'] - ($this->settings[$marginKey] * 2), 629 | Imagick::FILTER_CUBIC, 630 | 1 631 | ); 632 | $image->borderImage( 633 | "#{$this->settings[$colorKey]}", 634 | $this->settings[$marginKey], 635 | $this->settings[$marginKey] 636 | ); 637 | } 638 | } 639 | 640 | return $image; 641 | } 642 | 643 | /** 644 | * It creates an image of a given size. 645 | * @param string $size 646 | * @return Imagick 647 | * @access private 648 | * @final 649 | */ 650 | final private function createImage($size) 651 | { 652 | list($sizes['width'], $sizes['height']) = explode('x', $size); 653 | 654 | $original = new Imagick("{$this->root}/favicon/.original"); 655 | 656 | $source = $original->getImageGeometry(); 657 | 658 | if (($source['width'] / $source['height']) < ($sizes['width'] / $sizes['height'])) { 659 | $scale = $source['width'] / $sizes['width']; 660 | } else { 661 | $scale = $source['height'] / $sizes['height']; 662 | } 663 | 664 | $original->setImageFormat('png'); 665 | $original->setImageCompressionQuality($this->getCompression()); 666 | 667 | $source['width'] = (int) ($source['width'] / $scale); 668 | $source['height'] = (int) ($source['height'] / $scale); 669 | 670 | $original->resizeImage($source['width'], $source['height'], Imagick::FILTER_CUBIC, 1); 671 | 672 | switch ($this->getCropMethod()) { 673 | case self::CROPMETHOD_CENTER: 674 | $offset = $this->getOffsetCenter($original, $sizes['width'], $sizes['height']); 675 | break; 676 | case self::CROPMETHOD_BALANCED: 677 | $offset = $this->getOffsetBalanced($original, $sizes['width'], $sizes['height']); 678 | break; 679 | case self::CROPMETHOD_ENTROPY: 680 | $offset = $this->getOffsetEntropy($original, $sizes['width'], $sizes['height']); 681 | break; 682 | } 683 | 684 | $original->cropImage($sizes['width'], $sizes['height'], $offset['x'], $offset['y']); 685 | 686 | return $original; 687 | } 688 | 689 | /** 690 | * He gets the coordinates of the center of the shift. 691 | * @param Imagick $image 692 | * @param string $width 693 | * @param string $height 694 | * @return array 695 | * @access private 696 | * @final 697 | */ 698 | final private function getOffsetCenter(Imagick $image, $width, $height) 699 | { 700 | $size = $image->getImageGeometry(); 701 | 702 | return array( 703 | 'x' => (int) (($size['width'] - $width) / 2), 704 | 'y' => (int) (($size['height'] - $height)/2) 705 | ); 706 | } 707 | 708 | /** 709 | * Gets a balanced shift coordinates. 710 | * @param Imagick $image 711 | * @param string $width 712 | * @param string $height 713 | * @return array 714 | * @access private 715 | * @final 716 | */ 717 | final private function getOffsetBalanced(Imagick $image, $width, $height) 718 | { 719 | $size = $image->getImageGeometry(); 720 | 721 | $points = array(); 722 | $halfWidth = ceil($size['width'] / 2); 723 | $halfHeight = ceil($size['height'] / 2); 724 | 725 | $clone = clone($image); 726 | $clone->cropimage($halfWidth, $halfHeight, 0, 0); 727 | 728 | $cloneSize = $clone->getImageGeometry(); 729 | $tmpFile = sys_get_temp_dir() . '/image' . rand(); 730 | $clone->writeimage($tmpFile); 731 | $tmp = imagecreatefromjpeg($tmpFile); 732 | 733 | $xcenter = $ycenter = $sum = 0; 734 | 735 | $tmpSize = round($cloneSize['height'] * $cloneSize['width']) / 50; 736 | 737 | for ($k=0; $k < $tmpSize; $k++) { 738 | $i = mt_rand(0, $cloneSize['width'] - 1); 739 | $j = mt_rand(0, $cloneSize['height'] - 1); 740 | 741 | $rgb = imagecolorat($tmp, $i, $j); 742 | $r = ($rgb >> 16) & 0xFF; 743 | $g = ($rgb >> 8) & 0xFF; 744 | $b = $rgb & 0xFF; 745 | 746 | $val = ($r * 0.299) + ($g * 0.587) + ($b * 0.114); 747 | $sum += $val; 748 | $xcenter += ($i + 1) * $val; 749 | $ycenter += ($j + 1) * $val; 750 | } 751 | 752 | if ($sum > 0) { 753 | $xcenter /= $sum; 754 | $ycenter /= $sum; 755 | } 756 | 757 | $points[] = array( 758 | 'x' => $xcenter, 759 | 'y' => $ycenter, 760 | 'sum' => $sum / round($cloneSize['height'] * $cloneSize['width']) 761 | ); 762 | 763 | $totalWeight = array_reduce( 764 | $points, 765 | function ($result, $array) { 766 | return $result + $array['sum']; 767 | } 768 | ); 769 | 770 | $xcenter = $ycenter = 0; 771 | 772 | $totalPoints = count($points); 773 | for ($idx = 0; $idx < $totalPoints; $idx++) { 774 | $xcenter += $points[$idx]['x'] * ($points[$idx]['sum'] / $totalWeight); 775 | $ycenter += $points[$idx]['y'] * ($points[$idx]['sum'] / $totalWeight); 776 | } 777 | 778 | $topleftX = max(0, ($xcenter - $width / 2)); 779 | $topleftY = max(0, ($ycenter - $height / 2)); 780 | 781 | if ($topleftX + $width > $size['width']) { 782 | $topleftX -= ($topleftX + $width) - $size['width']; 783 | } 784 | 785 | if ($topleftY + $height > $size['height']) { 786 | $topleftY -= ($topleftY + $height) - $size['height']; 787 | } 788 | 789 | return array('x' => $topleftX, 'y' => $topleftY); 790 | } 791 | 792 | /** 793 | * Gets a entropy shift coordinates. 794 | * @param Imagick $image 795 | * @param string $width 796 | * @param string $height 797 | * @return array 798 | * @access private 799 | * @final 800 | */ 801 | final private function getOffsetEntropy(Imagick $image, $width, $height) 802 | { 803 | $clone = clone($image); 804 | $clone->edgeimage(1); 805 | $clone->modulateImage(100, 0, 100); 806 | $clone->blackThresholdImage('#070707'); 807 | $clone->blurImage(3, 2); 808 | 809 | return array( 810 | 'x' => $this->sliceEntropy($clone, $width, 'h'), 811 | 'y' => $this->sliceEntropy($clone, $height, 'v') 812 | ); 813 | } 814 | 815 | /** 816 | * Gets the offset point relative to the grid. 817 | * @param Imagick $image 818 | * @param string $size 819 | * @param string $axis 820 | * @return integer 821 | * @access private 822 | * @final 823 | */ 824 | final private function sliceEntropy(Imagick $image, $size, $axis) 825 | { 826 | $rank = array(); 827 | 828 | $imageSize = $image->getImageGeometry(); 829 | $originalSize = $axis == 'h' ? $imageSize['width'] : $imageSize['height']; 830 | $longSize = $axis == 'h' ? $imageSize['height'] : $imageSize['width']; 831 | 832 | if ($originalSize == $targetSize) { 833 | return 0; 834 | } 835 | 836 | $number = 25; 837 | $sliceSize = ceil($originalSize / $number); 838 | 839 | $requiredSlices = ceil($size / $sliceSize); 840 | 841 | $start = 0; 842 | while ($start < $originalSize) { 843 | $slice = clone($image); 844 | switch ($axis) { 845 | case 'h': 846 | $slice->cropImage($sliceSize, $longSize, $start, 0); 847 | break; 848 | case 'v': 849 | $slice->cropImage($longSize, $sliceSize, 0, $start); 850 | break; 851 | } 852 | 853 | $histogram = $slice->getImageHistogram(); 854 | $area = $slice->getImageGeometry(); 855 | 856 | $value = 0.0; 857 | 858 | $colors = count($histogram); 859 | for ($idx = 0; $idx < $colors; $idx++) { 860 | $p = $histogram[$idx]->getColorCount() / $area['height'] * $area['width']; 861 | $value = $value + $p * log($p, 2); 862 | } 863 | 864 | $rank[] = array('offset' => $start, 'entropy' => -$value); 865 | $start += $sliceSize; 866 | } 867 | 868 | $max = $maxIndex = 0; 869 | for ($i = 0; $i < $number - $requiredSlices; $i++) { 870 | $temp = 0; 871 | for ($j = 0; $j < $requiredSlices; $j++) { 872 | $temp += $rank[$i + $j]['entropy']; 873 | } 874 | 875 | if ($temp > $max) { 876 | $maxIndex = $i; 877 | $max = $temp; 878 | } 879 | } 880 | 881 | return $rank[$maxIndex]['offset']; 882 | } 883 | } 884 | --------------------------------------------------------------------------------