29 |
-------------------------------------------------------------------------------- /Test/slirTestCase.class.php: -------------------------------------------------------------------------------- 1 | slir = new SLIR(); 21 | \SLIRConfig::$defaultImagePath = null; 22 | \SLIRConfig::$forceQueryString = false; 23 | \SLIRConfig::$enableErrorImages = false; 24 | \SLIRConfig::$defaultCropper = SLIR::CROP_CLASS_CENTERED; 25 | \SLIRConfig::$copyEXIF = false; 26 | \SLIRConfig::$maxMemoryToAllocate = -1; 27 | 28 | // Try to fix documentRoot for CLI 29 | \SLIRConfig::$documentRoot = realpath(__DIR__ . '/../'); 30 | \SLIRConfig::$urlToSLIR = '/slir'; 31 | 32 | } 33 | 34 | /** 35 | * @return void 36 | */ 37 | protected function tearDown() 38 | { 39 | unset($this->slir); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /install/index.php: -------------------------------------------------------------------------------- 1 | . 19 | * 20 | * @copyright Copyright © 2011, Joe Lencioni 21 | * @license http://opensource.org/licenses/gpl-3.0.html GNU General Public License version 3 (GPLv3) 22 | * @since 2.0 23 | * @package SLIR 24 | */ 25 | 26 | require_once '../vendor/autoload.php'; 27 | new \SLIR\SLIRInstaller(); -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Joe Lencioni 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rezozero/slir", 3 | "type": "library", 4 | "keywords": ["image","resizing","resampling","image processing"], 5 | "authors": [ 6 | { 7 | "name": "Joe Lencioni", 8 | "email": "joe.lencioni@gmail.com", 9 | "homepage":"http://joelencioni.com", 10 | "role": "Developer" 11 | }, 12 | { 13 | "name": "Ambroise Maupate", 14 | "email": "ambroise@rezo-zero.com", 15 | "homepage":"http://www.rezo-zero.com", 16 | "role": "Developer" 17 | } 18 | ], 19 | "description" : "SLIR (Smart Lencioni Image Resizer) resizes images, intelligently sharpens, crops based on width:height ratios, color fills transparent GIFs and PNGs, and caches variations for optimal performance.", 20 | "homepage": "https://github.com/rezozero/SLIR", 21 | "version": "2.1.3", 22 | "license": "MIT", 23 | "autoload": { 24 | "psr-4":{ 25 | "SLIR\\" : "core/", 26 | "Risko\\" : "icc/" 27 | } 28 | }, 29 | "require": { 30 | "php": ">=5.3.3" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "~4.3.5" 34 | } 35 | } -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | 33 | 34 | require_once 'vendor/autoload.php'; 35 | 36 | $slir = new \SLIR\SLIR(); 37 | $slir->processRequestFromURL(); -------------------------------------------------------------------------------- /core/FatalSLIRInstallerResponse.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * @copyright Copyright © 2014, Joe Lencioni 24 | * @license MIT 25 | */ 26 | 27 | namespace SLIR; 28 | 29 | /** 30 | * @package SLIR 31 | * @subpackage Installer 32 | */ 33 | class FatalSLIRInstallerResponse extends NegativeSLIRInstallerResponse 34 | { 35 | /** 36 | * @var string 37 | * @since 2.0 38 | */ 39 | protected $type = 'Fatal'; 40 | } -------------------------------------------------------------------------------- /core/NegativeSLIRInstallerResponse.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * @copyright Copyright © 2014, Joe Lencioni 24 | * @license MIT 25 | */ 26 | namespace SLIR; 27 | 28 | /** 29 | * @package SLIR 30 | * @subpackage Installer 31 | */ 32 | class NegativeSLIRInstallerResponse extends SLIRInstallerResponse 33 | { 34 | /** 35 | * @var string 36 | * @since 2.0 37 | */ 38 | protected $type = 'Negative'; 39 | 40 | /** 41 | * @var string 42 | * @since 2.0 43 | */ 44 | protected $message = 'Failed!'; 45 | } -------------------------------------------------------------------------------- /core/PositiveSLIRInstallerResponse.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * @copyright Copyright © 2014, Joe Lencioni 24 | * @license MIT 25 | */ 26 | 27 | namespace SLIR; 28 | /** 29 | * @package SLIR 30 | * @subpackage Installer 31 | * @since 2.0 32 | */ 33 | class PositiveSLIRInstallerResponse extends SLIRInstallerResponse 34 | { 35 | /** 36 | * @var string 37 | * @since 2.0 38 | */ 39 | protected $type = 'Positive'; 40 | 41 | /** 42 | * @var string 43 | * @since 2.0 44 | */ 45 | protected $message = 'Success!'; 46 | } -------------------------------------------------------------------------------- /slirconfig-sample.class.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | 33 | /** 34 | * SLIR Config Class 35 | * 36 | * @since 2.0 37 | * @author Joe Lencioni 38 | * @package SLIR 39 | */ 40 | class SLIRConfig extends SLIR\SLIRConfigDefaults 41 | { 42 | // override configuration values here 43 | 44 | public static function init() 45 | { 46 | // This must be the last line of this function 47 | parent::init(); 48 | } 49 | } 50 | 51 | \SLIRConfig::init(); -------------------------------------------------------------------------------- /core/Libs/GD/Croppers/SLIRCropper.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | * @subpackage Croppers 32 | */ 33 | namespace SLIR\Libs\GD\Croppers; 34 | 35 | 36 | use \SLIR\Libs\SLIRImage; 37 | /** 38 | * SLIR cropper interface 39 | * 40 | * @since 2.0 41 | * @author Joe Lencioni 42 | * @package SLIR 43 | * @subpackage Croppers 44 | */ 45 | interface SLIRCropper 46 | { 47 | /** 48 | * @since 2.0 49 | * @param SLIRImage $image 50 | * @return array Associative array with the keys of x, y, width, and height that specify the box that should be cropped 51 | */ 52 | public function getCrop(SLIRImage $image); 53 | } 54 | -------------------------------------------------------------------------------- /core/Libs/GD/Croppers/SLIRCropperTopcentered.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | * @subpackage Croppers 32 | */ 33 | 34 | namespace SLIR\Libs\GD\Croppers; 35 | 36 | 37 | use \SLIR\Libs\SLIRImage; 38 | /** 39 | * Top/centered SLIR cropper 40 | * 41 | * Calculates the crop offset anchored in the top of the image if the top and bottom are being cropped, or the center of the image if the left and right are being cropped 42 | * 43 | * @since 2.0 44 | * @author Joe Lencioni 45 | * @package SLIR 46 | * @subpackage Croppers 47 | */ 48 | class SLIRCropperTopcentered extends SLIRCropperCentered 49 | { 50 | /** 51 | * @since 2.0 52 | * @param SLIRImage $image 53 | * @return integer 54 | */ 55 | public function getCropY(SLIRImage $image) 56 | { 57 | return 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /core/Libs/SLIRCoordinates.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * @copyright Copyright © 2014, Joe Lencioni 24 | * @license MIT 25 | */ 26 | 27 | namespace SLIR\Libs; 28 | 29 | class SLIRCoordinates 30 | { 31 | /** 32 | * @var integer 33 | */ 34 | private $xa; 35 | 36 | /** 37 | * @var integer 38 | */ 39 | private $ya; 40 | 41 | /** 42 | * @var integer 43 | */ 44 | private $xb; 45 | 46 | /** 47 | * @var integer 48 | */ 49 | private $yb; 50 | 51 | /** 52 | * @param integer $xa 53 | * @param integer $ya 54 | * @param integer $xb 55 | * @param integer $yb 56 | */ 57 | public function __construct($xa, $ya, $xb, $yb) 58 | { 59 | $this->$xa = $xa; 60 | $this->$ya = $ya; 61 | $this->$xb = $xb; 62 | $this->$yb = $yb; 63 | } 64 | 65 | /** 66 | * @return integer 67 | */ 68 | public function getWidth() 69 | { 70 | return abs($this->xb - $this->xa); 71 | } 72 | 73 | /** 74 | * @return integer 75 | */ 76 | public function getHeight() 77 | { 78 | return abs($this->yb - $this->ya); 79 | } 80 | } -------------------------------------------------------------------------------- /core/SLIRInstallerResponse.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * @copyright Copyright © 2014, Joe Lencioni 24 | * @license MIT 25 | */ 26 | 27 | namespace SLIR; 28 | /** 29 | * @package SLIR 30 | * @subpackage Installer 31 | * @since 2.0 32 | */ 33 | class SLIRInstallerResponse 34 | { 35 | /** 36 | * @var string 37 | * @since 2.0 38 | */ 39 | protected $type = 'Generic'; 40 | 41 | /** 42 | * @var string 43 | * @since 2.0 44 | */ 45 | protected $message = 'Unknown'; 46 | 47 | /** 48 | * @var string 49 | * @since 2.0 50 | */ 51 | protected $description = ''; 52 | 53 | /** 54 | * @param string $description 55 | * @return void 56 | * @since 2.0 57 | */ 58 | public function __construct($description = '') 59 | { 60 | $this->description = $description; 61 | } 62 | 63 | /** 64 | * @return string 65 | * @since 2.0 66 | */ 67 | public function getType() 68 | { 69 | return $this->type; 70 | } 71 | 72 | /** 73 | * @return string 74 | * @since 2.0 75 | */ 76 | public function getMessage() 77 | { 78 | return $this->message; 79 | } 80 | 81 | /** 82 | * @return string 83 | * @since 2.0 84 | */ 85 | public function getDescription() 86 | { 87 | return $this->description; 88 | } 89 | } -------------------------------------------------------------------------------- /core/Libs/GD/Croppers/SLIRCropperCentered.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | * @subpackage Croppers 32 | */ 33 | 34 | namespace SLIR\Libs\GD\Croppers; 35 | 36 | 37 | use \SLIR\Libs\SLIRImage; 38 | 39 | /** 40 | * Centered SLIR cropper 41 | * 42 | * Calculates the crop offset anchored in the center of the image 43 | * 44 | * @since 2.0 45 | * @author Joe Lencioni 46 | * @package SLIR 47 | * @subpackage Croppers 48 | */ 49 | class SLIRCropperCentered implements SLIRCropper 50 | { 51 | /** 52 | * Determines if the top and bottom need to be cropped 53 | * 54 | * @since 2.0 55 | * @param SLIRImage $image 56 | * @return boolean 57 | */ 58 | private function shouldCropTopAndBottom(SLIRImage $image) 59 | { 60 | if ($image->getCropRatio() > $image->getRatio()) { 61 | return true; 62 | } else { 63 | return false; 64 | } 65 | } 66 | 67 | /** 68 | * @since 2.0 69 | * @param SLIRImage $image 70 | * @return integer 71 | */ 72 | public function getCropY(SLIRImage $image) 73 | { 74 | return round(($image->getHeight() - $image->getCropHeight()) / 2); 75 | } 76 | 77 | /** 78 | * @since 2.0 79 | * @param SLIRImage $image 80 | * @return integer 81 | */ 82 | public function getCropX(SLIRImage $image) 83 | { 84 | return round(($image->getWidth() - $image->getCropWidth()) / 2); 85 | } 86 | 87 | /** 88 | * Calculates the crop offset anchored in the center of the image 89 | * 90 | * @since 2.0 91 | * @param SLIRImage $image 92 | * @return array Associative array with the keys of x and y that specify the top left corner of the box that should be cropped 93 | */ 94 | public function getCrop(SLIRImage $image) 95 | { 96 | // Determine crop offset 97 | $crop = array( 98 | 'x' => 0, 99 | 'y' => 0, 100 | ); 101 | 102 | if ($this->shouldCropTopAndBottom($image)) { 103 | // Image is too tall so we will crop the top and bottom 104 | $crop['y'] = $this->getCropY($image); 105 | } else { 106 | // Image is too wide so we will crop off the left and right sides 107 | $crop['x'] = $this->getCropX($image); 108 | } 109 | 110 | return $crop; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /core/SLIRGarbageCollector.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | namespace SLIR; 33 | /** 34 | * SLIR garbage collector class 35 | * 36 | * @since 2.0 37 | * @author Joe Lencioni 38 | * @package SLIR 39 | */ 40 | class SLIRGarbageCollector 41 | { 42 | 43 | /** 44 | * Setting for the garbage collector to sleep for a second after looking at this many files 45 | * 46 | * @since 2.0 47 | * @var integer 48 | */ 49 | const BREATHE_EVERY = 5000; 50 | 51 | /** 52 | * Garbage collector 53 | * 54 | * Clears out old files from the cache 55 | * 56 | * @since 2.0 57 | * @param array $directories 58 | * @return void 59 | */ 60 | public function __construct(array $directories) 61 | { 62 | // This code needs to be in a try/catch block to prevent the epically unhelpful 63 | // "PHP Fatal error: Exception thrown without a stack frame in Unknown on line 64 | // 0" from showing up in the error log. 65 | try { 66 | 67 | $configClass= SLIR::getConfigClass(); 68 | 69 | if ($this->isRunning()) { 70 | return; 71 | } 72 | 73 | $this->start(); 74 | foreach ($directories as $directory => $useAccessedTime) { 75 | $this->deleteStaleFilesFromDirectory($directory, $useAccessedTime); 76 | } 77 | $this->finish(); 78 | } catch (\Exception $e) { 79 | error_log(sprintf("\n[%s] %s thrown within the SLIR garbage collector. Message: %s in %s on line %d", @gmdate('D M d H:i:s Y'), get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()), 3, $configClass::$pathToErrorLog); 80 | error_log("\nException trace stack: " . print_r($e->getTrace(), true), 3, $configClass::$pathToErrorLog); 81 | $this->finish(false); 82 | } 83 | } 84 | 85 | /** 86 | * Deletes stale files from a directory. 87 | * 88 | * Used by the garbage collector to keep the cache directories from overflowing. 89 | * 90 | * @param string $path Directory to delete stale files from 91 | */ 92 | private function deleteStaleFilesFromDirectory($path, $useAccessedTime = true) 93 | { 94 | $configClass = SLIR::getConfigClass(); 95 | $now = time(); 96 | $dir = new \DirectoryIterator($path); 97 | 98 | if ($useAccessedTime === true) { 99 | $function = 'getATime'; 100 | } else { 101 | $function = 'getCTime'; 102 | } 103 | 104 | foreach ($dir as $file) { 105 | // Every x files, stop for a second to help let other things on the server happen 106 | if ($file->key() % self::BREATHE_EVERY == 0) { 107 | sleep(1); 108 | } 109 | 110 | // If the file is a link and not readable, the file it was pointing at has probably 111 | // been deleted, so we need to delete the link. 112 | // Otherwise, if the file is older than the max lifetime specified in the config, it is 113 | // stale and should be deleted. 114 | if (!$file->isDot() && (($file->isLink() && !$file->isReadable()) || ($now - $file->$function()) > $configClass::$garbageCollectFileCacheMaxLifetime)) { 115 | unlink($file->getPathName()); 116 | } 117 | } 118 | 119 | unset($dir); 120 | } 121 | 122 | /** 123 | * Checks to see if the garbage collector is currently running. 124 | * 125 | * @since 2.0 126 | * @return boolean 127 | */ 128 | private function isRunning() 129 | { 130 | $configClass = SLIR::getConfigClass(); 131 | if (file_exists($configClass::$pathToCacheDir . '/garbageCollector.tmp') && filemtime($configClass::$pathToCacheDir . '/garbageCollector.tmp') > time() - 86400) { 132 | // If the file is more than 1 day old, something probably went wrong and we should run again anyway 133 | return true; 134 | } else { 135 | return false; 136 | } 137 | } 138 | 139 | /** 140 | * Writes a file to the cache to use as a signal that the garbage collector is currently running. 141 | * 142 | * @since 2.0 143 | * @return void 144 | */ 145 | private function start() 146 | { 147 | $configClass = SLIR::getConfigClass(); 148 | error_log(sprintf("\n[%s] Garbage collection started", @gmdate('D M d H:i:s Y')), 3, $configClass::$pathToErrorLog); 149 | 150 | // Create the file that tells SLIR that the garbage collector is currently running and doesn't need to run again right now. 151 | touch($configClass::$pathToCacheDir . '/garbageCollector.tmp'); 152 | } 153 | 154 | /** 155 | * Removes the file that signifies that the garbage collector is currently running. 156 | * 157 | * @since 2.0 158 | * @param boolean $successful 159 | * @return void 160 | */ 161 | private function finish($successful = true) 162 | { 163 | $configClass = SLIR::getConfigClass(); 164 | // Delete the file that tells SLIR that the garbage collector is running 165 | unlink($configClass::$pathToCacheDir . '/garbageCollector.tmp'); 166 | 167 | if ($successful) { 168 | error_log(sprintf("\n[%s] Garbage collection completed", @gmdate('D M d H:i:s Y')), 3, $configClass::$pathToErrorLog); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /core/SLIRExceptionHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | namespace SLIR; 33 | /** 34 | * Exception and error handler 35 | * 36 | * @since 2.0 37 | * @author Joe Lencioni 38 | * @package SLIR 39 | */ 40 | class SLIRExceptionHandler 41 | { 42 | /** 43 | * Max number of characters to wrap error message at 44 | * 45 | * @since 2.0 46 | * @var integer 47 | */ 48 | const WRAP_AT = 65; 49 | 50 | /** 51 | * Text size to use in imagestring(). Possible values are 1, 2, 3, 4, or 5 52 | * 53 | * @since 2.0 54 | * @var integer 55 | */ 56 | const TEXT_SIZE = 4; 57 | 58 | /** 59 | * Height of one line of text, in pixels 60 | * 61 | * @since 2.0 62 | * @var integer 63 | */ 64 | const LINE_HEIGHT = 16; 65 | 66 | /** 67 | * Width of one character of text, in pixels 68 | * 69 | * @since 2.0 70 | * @var integer 71 | */ 72 | const CHAR_WIDTH = 8; 73 | 74 | /** 75 | * Logs the error to a file 76 | * 77 | * @since 2.0 78 | * @param Exception $e 79 | * @return boolean 80 | */ 81 | private static function log(Exception $e) 82 | { 83 | $configClass = SLIR::getConfigClass(); 84 | $userAgent = (isset($_SERVER['HTTP_USER_AGENT'])) ? $_SERVER['HTTP_USER_AGENT'] : ''; 85 | $referrer = (isset($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : ''; 86 | $request = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'] : ''; 87 | 88 | $message = vsprintf("\n[%s] [%s %s] %s\n\nREFERRER: %s\n\nREQUEST: %s\n\n%s", array( 89 | @gmdate('D M d H:i:s Y'), 90 | $_SERVER['REMOTE_ADDR'], 91 | $userAgent, 92 | $e->getMessage(), 93 | $referrer, 94 | $request, 95 | $e->getTraceAsString(), 96 | )); 97 | 98 | return @error_log($message, 3, $configClass::$pathToErrorLog); 99 | } 100 | 101 | /** 102 | * Create and output an image with an error message 103 | * 104 | * @since 2.0 105 | * @param Exception $e 106 | */ 107 | private static function errorImage(Exception $e) 108 | { 109 | $text = wordwrap($e->getMessage(), self::WRAP_AT); 110 | $text = explode("\n", $text); 111 | 112 | // determine width 113 | $characters = 0; 114 | foreach ($text as $line) { 115 | if (($temp = strlen($line)) > $characters) { 116 | $characters = $temp; 117 | } 118 | } // foreach 119 | 120 | // set up the image 121 | $image = imagecreatetruecolor( 122 | $characters * self::CHAR_WIDTH, 123 | count($text) * self::LINE_HEIGHT 124 | ); 125 | $white = imagecolorallocate($image, 255, 255, 255); 126 | imagefill($image, 0, 0, $white); 127 | 128 | // set text color 129 | $textColor = imagecolorallocate($image, 200, 0, 0); // red 130 | 131 | // write the text to the image 132 | $i = 0; 133 | foreach ($text as $line) { 134 | imagestring( 135 | $image, 136 | self::TEXT_SIZE, 137 | 0, 138 | $i * self::LINE_HEIGHT, 139 | $line, 140 | $textColor 141 | ); 142 | ++$i; 143 | } 144 | 145 | // output the image 146 | header('Content-type: image/png'); 147 | imagepng($image); 148 | 149 | // clean up for memory 150 | imagedestroy($image); 151 | } 152 | 153 | /** 154 | * Outputs the error as plain text 155 | * 156 | * @since 2.0 157 | * @param Exception $e 158 | * @return void 159 | */ 160 | private static function errorText(\Exception $e) 161 | { 162 | echo nl2br($e->getMessage() . ' in ' . $e->getFile() . ' on ' . $e->getLine()) . "\n"; 163 | } 164 | 165 | /** 166 | * Exception handler 167 | * 168 | * @since 2.0 169 | * @param Exception $e 170 | * @return void 171 | */ 172 | public static function handleException(\Exception $e) 173 | { 174 | $configClass = SLIR::getConfigClass(); 175 | if ($configClass::$enableErrorImages === true) { 176 | self::errorImage($e); 177 | } else { 178 | self::errorText($e); 179 | } 180 | 181 | self::log($e); 182 | } 183 | 184 | /** 185 | * Error handler 186 | * 187 | * Converts all errors into exceptions so they can be handled with the SLIR exception handler 188 | * 189 | * @since 2.0 190 | * @param integer $severity Level of the error raised 191 | * @param string $message Error message 192 | * @param string $filename Filename that the error was raised in 193 | * @param integer $lineno Line number the error was raised at, 194 | * @param array $context Points to the active symbol table at the point the error occurred 195 | */ 196 | public static function handleError($severity, $message, $filename = null, $lineno = null, $context = array()) 197 | { 198 | if (!(error_reporting() & $severity)) { 199 | // This error code is not included in error_reporting 200 | return; 201 | } 202 | 203 | throw new \ErrorException($message, 0, $severity, $filename, $lineno); 204 | } 205 | } 206 | 207 | set_error_handler(array('\SLIR\SLIRExceptionHandler', 'handleError')); 208 | set_exception_handler(array('\SLIR\SLIRExceptionHandler', 'handleException')); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SLIR (Smart Lencioni Image Resizer) 2 | 3 | ### Deprecation notice 4 | 5 | **SLIR is now deprecated in favor of [ambroisemaupate/intervention-request](https://github.com/ambroisemaupate/intervention-request) library which is based on the strong [image-intervention](http://image.intervention.io/) PHP framework. 6 | You should consider migrating to this new library which keeps the same Url guidelines.** 7 | 8 | SLIR (Smart Lencioni Image Resizer) resizes images, intelligently sharpens, crops based on width:height ratios, color fills transparent GIFs and PNGs, and caches variations for optimal performance. 9 | 10 | For questions or support, please [use the issue tracker](https://github.com/lencioni/SLIR/issues). 11 | 12 | ## Requirements 13 | 14 | * [Composer](https://getcomposer.org) 15 | * [PHP](http://php.net) 5.3.6+ 16 | * [GD Graphics Library](http://php.net/manual/en/book.image.php) -- must be a version that supports `imageconvolution()`, such as the bundled version 17 | 18 | ## Setting up, standalone 19 | 20 | * Download and unpack to a directory in your web root. I recommend putting SLIR in `/slir/` for ease of use. For example, if your website is `http://yourdomain.com`, then SLIR would be at `http://yourdomain.com/slir/`. 21 | * Run `composer install` to generate **autoloader** and dependencies. 22 | * Create your own `SLIRConfig.php` class using `slirconfig-sample.class.php` file. If you want to use a namespaced configuration class, you must define `SLIR_CONFIG_CLASSNAME` global constant with its Full-qualified classname. For example : `define('SLIR_CONFIG_CLASSNAME','\MyCompany\Utils\SLIRConfig');`, then SLIR will look for this class to use static configuration. 23 | * After you have SLIR configured, visit `http://yourdomain.com/slir/install/` in your favorite web browser. 24 | 25 | ## Setting up as a library in a symfony-like environment 26 | 27 | * Add *SLIR* to your project `composer.json` as a packagist repository. 28 | 29 | ```json 30 | { 31 | "require": { 32 | "rezozero/slir": "2.1.*" 33 | } 34 | } 35 | ``` 36 | 37 | * Run `composer update` to perform vendor changes. Composer will download SLIR to your `vendor/` folder and create a PSR-4 autoloader. 38 | * Create your `SLIRConfig` class 39 | 40 | ```php 41 | namespace MyBundle\Utils; 42 | 43 | /** 44 | * SLIR Config Class 45 | * 46 | * @since 2.0 47 | * @author Joe Lencioni 48 | * @package SLIR 49 | */ 50 | class SLIRConfig extends \SLIR\SLIRConfigDefaults 51 | { 52 | public static function init() 53 | { 54 | static::$garbageCollectDivisor = 400; 55 | static::$garbageCollectFileCacheMaxLifetime = 345600; 56 | static::$browserCacheTTL = 604800; // 7*24*60*60 57 | static::$pathToCacheDir = YOUR_PROJECT_ROOT.'/cache'; 58 | static::$pathToErrorLog = YOUR_PROJECT_ROOT.'/files/slir-error-log'; 59 | static::$documentRoot = YOUR_PROJECT_ROOT.'/files'; 60 | static::$urlToSLIR = '/assets'; // Tell SLIR to listen after "/assets" route 61 | static::$maxMemoryToAllocate = 64; 62 | // This must be the last line of this function 63 | parent::init(); 64 | } 65 | } 66 | 67 | SLIRConfig::init(); 68 | ``` 69 | 70 | * Create a route handling SLIR 71 | 72 | ```php 73 | # 74 | # routes.yml 75 | # 76 | SLIRProcess: 77 | path: /assets/{queryString}/{filename} 78 | defaults: { _controller: \MyBundle\Controllers\AssetsController::slirAction } 79 | requirements: { queryString : "[a-zA-Z0-9\-]+", filename : "[a-zA-Z0-9\-_\.\/]+" } 80 | ``` 81 | 82 | * Create your assets controller 83 | 84 | ```php 85 | // In AssetsController.php class 86 | /** 87 | * Handle images resize with SLIR vendor 88 | * 89 | * @param string $queryString 90 | * @param string $filename 91 | * @return void 92 | */ 93 | public function slirAction($queryString, $filename) 94 | { 95 | define('SLIR_CONFIG_CLASSNAME','\MyBundle\Utils\SLIRConfig'); 96 | 97 | $slir = new \SLIR\SLIR(); 98 | $slir->processRequestFromURL(); 99 | 100 | // SLIR handle response by itself 101 | // Do not return anything 102 | } 103 | ``` 104 | 105 | ## Using 106 | 107 | To use SLIR, place an `` tag with the `src` attribute pointing to the path of SLIR (typically "/slir/") followed by the parameters, followed by the path to the source image to resize (e.g. ``). All parameters follow the pattern of a one-letter code and then the parameter value: 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 |
ParameterMearningExample
wMaximum width/slir/w100/path/to/image.jpg
hMaximum height/slir/h100/path/to/image.jpg
cCrop ratio/slir/c1x1/path/to/image.jpg
qQuality/slir/q60/path/to/image.jpg
bBackground fill color/slir/bf00/path/to/image.png
pProgressive/slir/p1/path/to/image.jpg
gGrayscale/slir/g1/path/to/image.jpg
155 | 156 | Separate multiple parameters with a hyphen: /slir/w100-h100-c1x1/path/to/image.jpg 157 | 158 | ### Examples 159 | 160 | #### Resizing an image to a max width of 100 pixels and a max height of 100 pixels 161 | 162 | 163 | 164 | #### Resizing and cropping an image into a square 165 | 166 | 167 | 168 | #### Resizing and cropping an image to exact dimensions 169 | 170 | To do this, you simply need to make the crop ratio match up with the desired width and height. For example, if you want your image to be exactly 150 pixels wide by 100 pixels high, you could do this: 171 | 172 | 173 | 174 | Or, more concisely: 175 | 176 | 177 | 178 | However, SLIR will not enlarge images. So, if your source image is smaller than the desired size you will need to use CSS to make it the correct size. 179 | 180 | #### Resizing a JPEG without interlacing (for use in Flash) 181 | 182 | 183 | 184 | #### Matting a PNG with #990000 185 | 186 | 187 | 188 | #### Without mod_rewrite (not recommended) 189 | 190 | 191 | 192 | #### Changing SLIR request URL 193 | 194 | 195 | 196 | You can change SLIR request URL to handle it with your own rewrite engine. Just define `SLIRConfig::$urlToSLIR` to your relative folder: here `/assets`. 197 | 198 | #### Special characters (e.g. `+`) in image filenames 199 | 200 | Filenames that include special characters must be URL-encoded (e.g. plus sign, `+`, should be encoded as `%2B`) in order for SLIR to recognize them properly. This can be accomplished by passing your filenames through PHP's `rawurlencode()` function. 201 | 202 | 203 | 204 | ## Supporting SLIR 205 | 206 | If you would like to support SLIR or to show your appreciation for the time spent developing this project, please make a financial contribution. 207 | 208 | * [Dwolla](https://www.dwolla.com/hub/lencioni) 209 | * [Flattr](http://flattr.com/thing/178729/Smart-Lencioni-Image-Resizer-SLIR) 210 | 211 | *** 212 | 213 | For more documentation, open `core/SLIR.php` in your favorite text editor. 214 | -------------------------------------------------------------------------------- /core/Libs/SLIRImageLibrary.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | namespace SLIR\Libs; 33 | /** 34 | * SLIR Image Library interface 35 | * @package SLIR 36 | * @since 2.0 37 | */ 38 | interface SLIRImageLibrary 39 | { 40 | /** 41 | * Resamples the image into the destination image 42 | * @param SLIRImageLibrary $destination 43 | * @param integer $width 44 | * @param integer $height 45 | * @return SLIRImageLibrary 46 | * @since 2.0 47 | */ 48 | public function resample(SLIRImageLibrary $destination); 49 | 50 | /** 51 | * Copies the image into the destination image without reszing 52 | * @param SLIRImageLibrary $destination 53 | * @return SLIRImageLibrary 54 | * @since 2.0 55 | */ 56 | public function copy(SLIRImageLibrary $destination); 57 | 58 | /** 59 | * Gets a hash that represents the properties of the image. 60 | * 61 | * Used for caching. 62 | * 63 | * @param array $infosToInclude 64 | * @return string 65 | * @since 2.0 66 | */ 67 | public function getHash(array $infosToInclude = array()); 68 | 69 | /** 70 | * Sets the path of the file 71 | * @param string $path 72 | * @return SLIRImageLibrary 73 | * @since 2.0 74 | */ 75 | public function setPath($path); 76 | 77 | /** 78 | * Gets the path of the file 79 | * @return string 80 | * @since 2.0 81 | */ 82 | public function getPath(); 83 | 84 | /** 85 | * Sets the path of the original file 86 | * @param string $path 87 | * @return SLIRImageLibrary 88 | * @since 2.0 89 | */ 90 | public function setOriginalPath($path); 91 | 92 | /** 93 | * Gets the path of the original file 94 | * @return string 95 | * @since 2.0 96 | */ 97 | public function getOriginalPath(); 98 | 99 | /** 100 | * Gets the width of the image 101 | * @return integer 102 | * @since 2.0 103 | */ 104 | public function getWidth(); 105 | 106 | /** 107 | * Gets the height of the image 108 | * @return integer 109 | * @since 2.0 110 | */ 111 | public function getHeight(); 112 | 113 | /** 114 | * Sets the width of the image 115 | * @param integer $width 116 | * @return SLIRImageLibrary 117 | * @since 2.0 118 | */ 119 | public function setWidth($width); 120 | 121 | /** 122 | * Sets the height of the image 123 | * @param integer $height 124 | * @return SLIRImageLibrary 125 | * @since 2.0 126 | */ 127 | public function setHeight($height); 128 | 129 | /** 130 | * Gets the width of the cropped image 131 | * @return integer 132 | * @since 2.0 133 | */ 134 | public function getCropWidth(); 135 | 136 | /** 137 | * Gets the height of the cropped image 138 | * @return integer 139 | * @since 2.0 140 | */ 141 | public function getCropHeight(); 142 | 143 | /** 144 | * Sets the width of the cropped image 145 | * @param integer $width 146 | * @return SLIRImageLibrary 147 | * @since 2.0 148 | */ 149 | public function setCropWidth($width); 150 | 151 | /** 152 | * Sets the height of the cropped image 153 | * @param integer $height 154 | * @return SLIRImageLibrary 155 | * @since 2.0 156 | */ 157 | public function setCropHeight($height); 158 | 159 | /** 160 | * Gets cropper to be used 161 | * @return string 162 | * @since 2.0 163 | */ 164 | public function getCropper(); 165 | 166 | /** 167 | * Sets the cropper to be used 168 | * @param string $cropper 169 | * @return SLIRImageLibrary 170 | * @since 2.0 171 | */ 172 | public function setCropper($cropper); 173 | 174 | /** 175 | * @return integer 176 | * @since 2.0 177 | */ 178 | public function getArea(); 179 | 180 | /** 181 | * Gets info about the image 182 | * @param string $info 183 | * @return mixed 184 | * @since 2.0 185 | */ 186 | public function getInfo($info = null); 187 | 188 | /** 189 | * Gets the width / height 190 | * @return float 191 | * @since 2.0 192 | */ 193 | public function getRatio(); 194 | 195 | /** 196 | * Gets the cropWidth / cropHeight 197 | * @return float 198 | * @since 2.0 199 | */ 200 | public function getCropRatio(); 201 | 202 | /** 203 | * Gets the MIME type of the image 204 | * @return string 205 | * @since 2.0 206 | */ 207 | public function getMimeType(); 208 | 209 | /** 210 | * @return string raw image data 211 | * @since 2.0 212 | */ 213 | public function getData(); 214 | 215 | /** 216 | * @return integer size of image data 217 | */ 218 | public function getDatasize(); 219 | 220 | /** 221 | * Creates a new, blank image 222 | * @param integer $width 223 | * @param integer $height 224 | * @return SLIRImageLibrary 225 | */ 226 | public function create(); 227 | 228 | /** 229 | * @return integer 230 | * @since 2.0 231 | */ 232 | public function getQuality(); 233 | 234 | /** 235 | * @param integer $quality 236 | * @return SLIRImageLibrary 237 | */ 238 | public function setQuality($quality); 239 | 240 | /** 241 | * @return string 242 | * @since 2.0 243 | */ 244 | public function getBackground(); 245 | 246 | /** 247 | * @param string $color in hex 248 | * @return SLIRImageLibrary 249 | */ 250 | public function setBackground($color); 251 | 252 | /** 253 | * Turns on transparency for image if no background fill color is 254 | * specified, otherwise, fills background 255 | * 256 | * @since 2.0 257 | * @return SLIRImageLibrary 258 | */ 259 | public function background(); 260 | 261 | /** 262 | * Turns on the alpha channel to enable transparency in the image 263 | * @return SLIRImageLibrary 264 | * @since 2.0 265 | */ 266 | public function enableTransparency(); 267 | 268 | /** 269 | * Fills the image with the set background color 270 | * @return SLIRImageLibrary 271 | * @since 2.0 272 | */ 273 | public function fill(); 274 | 275 | /** 276 | * @return boolean 277 | * @since 2.0 278 | */ 279 | public function getProgressive(); 280 | 281 | /** 282 | * @param boolean $progressive 283 | * @return SLIRImageLibrary 284 | */ 285 | public function setProgressive($progressive); 286 | 287 | /** 288 | * @return boolean 289 | * @since 2.0 290 | */ 291 | public function getGrayscale(); 292 | 293 | /** 294 | * @param boolean $grayscale 295 | * @return SLIRImageLibrary 296 | */ 297 | public function setGrayscale($grayscale); 298 | 299 | /** 300 | * Turns interlacing on or off 301 | * @return SLIRImageLibrary 302 | * @since 2.0 303 | */ 304 | public function interlace(); 305 | 306 | /** 307 | * Turns grayscale on or off 308 | * @return SLIRImageLibrary 309 | * @since 2.0 310 | */ 311 | public function grayscale(); 312 | 313 | /** 314 | * Performs the actual cropping of the image 315 | * 316 | * @param integer $cropWidth 317 | * @param integer $cropHeight 318 | * @param string $fill color in hex 319 | * @return SLIRImageLibrary 320 | * @since 2.0 321 | */ 322 | public function crop(); 323 | 324 | /** 325 | * @return float 326 | * @since 2.0 327 | */ 328 | public function getSharpeningFactor(); 329 | 330 | /** 331 | * @param float $sharpeningFactor 332 | * @return SLIRImageLibrary 333 | */ 334 | public function setSharpeningFactor($sharpeningFactor); 335 | 336 | /** 337 | * Sharpens the image 338 | * @return SLIRImageLibrary 339 | * @since 2.0 340 | */ 341 | public function sharpen(); 342 | 343 | /** 344 | * Outputs the image to the client 345 | * @return SLIRImageLibrary 346 | * @since 2.0 347 | */ 348 | public function output(); 349 | 350 | /** 351 | * Saves the image to disk 352 | * @param string $path 353 | * @return SLIRImageLibrary 354 | * @since 2.0 355 | */ 356 | public function save(); 357 | 358 | /** 359 | * Destroys the image resource 360 | * @return SLIRImageLibrary 361 | * @since 2.0 362 | */ 363 | public function destroy(); 364 | 365 | /** 366 | * Resizes, crops, sharpens, fills background, etc. 367 | * @return SLIRImageLibrary 368 | * @since 2.0 369 | */ 370 | public function applyTransformations(); 371 | } 372 | -------------------------------------------------------------------------------- /core/SLIRConfigDefaults.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | namespace SLIR; 33 | /** 34 | * SLIR Config Class 35 | * 36 | * @since 2.0 37 | * @author Joe Lencioni 38 | * @package SLIR 39 | */ 40 | class SLIRConfigDefaults 41 | { 42 | /** 43 | * Path to default the source image to if the requested image cannot be found. 44 | * 45 | * This should match the style of path you would normally pass to SLIR in the URL (not the full path on the filesystem). 46 | * 47 | * For example, if your website was http://mysite.com and your document root was /var/www/, and your default image was at http://mysite.com/images/default.png, you would set $defaultImagePath = '/images/default.png'; 48 | * 49 | * If null, SLIR will throw an exception if the requested image cannot be found. 50 | * 51 | * @since 2.0 52 | * @var string 53 | */ 54 | public static $defaultImagePath = null; 55 | 56 | /** 57 | * Default quality setting to use if quality is not specified in the request. Ranges from 0 (worst quality, smaller file) to 100 (best quality, largest filesize). 58 | * 59 | * @since 2.0 60 | * @var integer 61 | */ 62 | public static $defaultQuality = 80; 63 | 64 | /** 65 | * Default setting for whether JPEGs should be progressive JPEGs (interlaced) or not. 66 | * 67 | * @since 2.0 68 | * @var boolean 69 | */ 70 | public static $defaultProgressiveJPEG = true; 71 | 72 | /** 73 | * How long (in seconds) the web browser should use its cached copy of the image 74 | * before checking with the server for a new version 75 | * 76 | * @since 2.0 77 | * @var integer 78 | */ 79 | public static $browserCacheTTL = 604800; // 7 days = 7 * 24 * 60 * 60 80 | 81 | /** 82 | * If true, enables the faster, symlink-based request cache as a first-line cache. If false, the request cache is disabled. 83 | * 84 | * The request cache seems to have issues on some Windows servers. 85 | * 86 | * @since 2.0 87 | * @var boolean 88 | */ 89 | public static $enableRequestCache = true; 90 | 91 | /** 92 | * How much memory (in megabytes) SLIR is allowed to allocate for memory-intensive processes such as rendering and cropping. 93 | * 94 | * @since 2.0 95 | * @var integer 96 | */ 97 | public static $maxMemoryToAllocate = 100; 98 | 99 | /** 100 | * Default crop mode setting to use if crop mode is not specified in the request. 101 | * 102 | * Possible values are: 103 | * SLIR::CROP_CLASS_CENTERED 104 | * SLIR::CROP_CLASS_TOP_CENTERED 105 | * SLIR::CROP_CLASS_SMART 106 | * 107 | * @since 2.0 108 | * @var string 109 | */ 110 | public static $defaultCropper = SLIR::CROP_CLASS_CENTERED; 111 | 112 | /** 113 | * If true, SLIR will generate and output images from error messages. If false, error messages will be plaintext. 114 | * 115 | * @since 2.0 116 | * @var boolean 117 | */ 118 | public static $enableErrorImages = true; 119 | 120 | /** 121 | * Absolute path to the web root (location of files when visiting http://example.com/) (no trailing slash). 122 | * 123 | * For example, if the files for your website are located in /var/www/ on your server, this should be '/var/www'. 124 | * 125 | * By default, this is dyanmically determined, so it is set in the init() function and hopefully will not need to be overwritten. However, if SLIR isn't working correctly, it might not be determining your document root correctly and you might need to set this manually in your configuration file. 126 | * 127 | * @since 2.0 128 | * @var string 129 | */ 130 | public static $documentRoot = null; 131 | 132 | /** 133 | * Absolute path to SLIR (no trailing slash) from the root directory on your server's filesystem. 134 | * 135 | * For example, if the files on your website are in /var/www/ and slir is accessible at http://example.com/slir/, then the value of this setting should be '/var/www/slir'. 136 | * 137 | * By default, this is dyanmically determined, so it is set in the init() function and hopefully will not need to be overwritten. However, if SLIR isn't working correctly, it might not be determining the path to SLIR correctly and you might need to set this manually in your configuration file. 138 | * 139 | * @since 2.0 140 | * @var string 141 | */ 142 | public static $pathToSLIR = null; 143 | 144 | 145 | /** 146 | * Absolute URI to SLIR (no trailing slash) from the root directory on your website filesystem. 147 | * 148 | * For example, if the files on your website are in /var/www/ and slir is accessible at URL : http://example.com/assets/, then the value of this setting should be '/assets'. 149 | * 150 | * @since 2.0 151 | * @var string 152 | */ 153 | public static $urlToSLIR = null; 154 | 155 | 156 | /** 157 | * Absolute path to cache directory (no trailing slash). This directory must be world-readable, writable by the web server. Ideally, this directory should be located outside of the web tree for security reasons. 158 | * 159 | * By default, this is dynamically determined in the init() function and it defaults to /path/to/slir/cache (or $pathToSlir . '/cache') which is the cache directory inside the directory SLIR is located. 160 | * 161 | * @var string 162 | */ 163 | public static $pathToCacheDir = null; 164 | 165 | /** 166 | * Path to the error log file. Needs to be writable by the web server. Ideally, this should be located outside of the web tree. 167 | * 168 | * If not specified, defaults to 'slir-error-log' in the directory SLIR is located. 169 | * 170 | * @since 2.0 171 | * @var string 172 | */ 173 | public static $pathToErrorLog = null; 174 | 175 | /** 176 | * If true, forces SLIR to always use the query string for parameters instead of mod_rewrite. 177 | * 178 | * @since 2.0 179 | * @var boolean 180 | */ 181 | public static $forceQueryString = false; 182 | 183 | /** 184 | * In conjunction with $garbageCollectDivisor is used to manage probability that the garbage collection routine is started. 185 | * 186 | * @since 2.0 187 | * @var integer 188 | */ 189 | public static $garbageCollectProbability = 1; 190 | 191 | /** 192 | * Coupled with $garbageCollectProbability defines the probability that the garbage collection process is started on every request. 193 | * 194 | * The probability is calculated by using $garbageCollectProbability/$garbageCollectDivisor, e.g. 1/100 means there is a 1% chance that the garbage collection process starts on each request. 195 | * 196 | * @since 2.0 197 | * @var integer 198 | */ 199 | public static $garbageCollectDivisor = 200; 200 | 201 | /** 202 | * Specifies the number of seconds after which data will be seen as 'garbage' and potentially cleaned up (deleted from the cache). 203 | * 204 | * @since 2.0 205 | * @var integer 206 | */ 207 | public static $garbageCollectFileCacheMaxLifetime = 604800; // 7 days = 7 * 24 * 60 * 60 208 | 209 | /** 210 | * If true, SLIR will copy EXIF information should from the source image to the rendered image. 211 | * 212 | * This can be particularly useful (necessary?) if you use an embedded color profile. 213 | * 214 | * @since 2.0 215 | * @var boolean 216 | */ 217 | public static $copyEXIF = false; 218 | 219 | /** 220 | * Initialize variables that require some dynamic processing. 221 | * 222 | * @since 2.0 223 | * @return void 224 | */ 225 | public static function init() 226 | { 227 | if (!defined('__DIR__')) { 228 | define('__DIR__', dirname(__FILE__)); 229 | } 230 | 231 | if (self::$documentRoot === null) { 232 | self::$documentRoot = rtrim(realpath(preg_replace('`' . preg_quote($_SERVER['PHP_SELF']) . '$`', '', $_SERVER['SCRIPT_FILENAME'])), '/'); 233 | } 234 | 235 | if (self::$pathToSLIR === null) { 236 | self::$pathToSLIR = realpath(__DIR__ . '/../'); 237 | } 238 | 239 | if (self::$pathToCacheDir === null) { 240 | self::$pathToCacheDir = self::$pathToSLIR . '/cache'; 241 | } 242 | 243 | if (self::$pathToErrorLog === null) { 244 | self::$pathToErrorLog = self::$pathToSLIR . '/slir-error-log'; 245 | } 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /install/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * HTML5 ✰ Boilerplate 3 | * 4 | * style.css contains a reset, font normalization and some base styles. 5 | * 6 | * Credit is left where credit is due. 7 | * Much inspiration was taken from these projects: 8 | * - yui.yahooapis.com/2.8.1/build/base/base.css 9 | * - camendesign.com/design/ 10 | * - praegnanz.de/weblog/htmlcssjs-kickstart 11 | */ 12 | 13 | 14 | /** 15 | * html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline) 16 | * v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark 17 | * html5doctor.com/html-5-reset-stylesheet/ 18 | */ 19 | 20 | html, body, div, span, object, iframe, 21 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 22 | abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, 23 | small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, 24 | fieldset, form, label, legend, 25 | table, caption, tbody, tfoot, thead, tr, th, td, 26 | article, aside, canvas, details, figcaption, figure, 27 | footer, header, hgroup, menu, nav, section, summary, 28 | time, mark, audio, video { 29 | margin: 0; 30 | padding: 0; 31 | border: 0; 32 | font-size: 100%; 33 | font: inherit; 34 | vertical-align: baseline; 35 | } 36 | 37 | article, aside, details, figcaption, figure, 38 | footer, header, hgroup, menu, nav, section { 39 | display: block; 40 | } 41 | 42 | blockquote, q { quotes: none; } 43 | 44 | blockquote:before, blockquote:after, 45 | q:before, q:after { content: ""; content: none; } 46 | 47 | ins { background-color: #ff9; color: #000; text-decoration: none; } 48 | 49 | mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } 50 | 51 | del { text-decoration: line-through; } 52 | 53 | abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } 54 | 55 | table { border-collapse: collapse; border-spacing: 0; } 56 | 57 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } 58 | 59 | input, select { vertical-align: middle; } 60 | 61 | 62 | /** 63 | * Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/ 64 | */ 65 | 66 | body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */ 67 | select, input, textarea, button { font:99% sans-serif; } 68 | 69 | /* Normalize monospace sizing: 70 | en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */ 71 | pre, code, kbd, samp { font-family: monospace, sans-serif; } 72 | 73 | 74 | /** 75 | * Minimal base styles. 76 | */ 77 | 78 | /* Always force a scrollbar in non-IE */ 79 | html { overflow-y: scroll; } 80 | 81 | /* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */ 82 | a:hover, a:active { outline: none; } 83 | 84 | ul, ol { margin-left: 2em; } 85 | ol { list-style-type: decimal; } 86 | 87 | /* Remove margins for navigation lists */ 88 | nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } 89 | 90 | small { font-size: 85%; } 91 | strong, th { font-weight: bold; } 92 | 93 | td { vertical-align: top; } 94 | 95 | /* Set sub, sup without affecting line-height: gist.github.com/413930 */ 96 | sub, sup { font-size: 75%; line-height: 0; position: relative; } 97 | sup { top: -0.5em; } 98 | sub { bottom: -0.25em; } 99 | 100 | pre { 101 | /* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */ 102 | white-space: pre; white-space: pre-wrap; word-wrap: break-word; 103 | padding: 15px; 104 | } 105 | 106 | textarea { overflow: auto; } /* www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */ 107 | 108 | .ie6 legend, .ie7 legend { margin-left: -7px; } 109 | 110 | /* Align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */ 111 | input[type="radio"] { vertical-align: text-bottom; } 112 | input[type="checkbox"] { vertical-align: bottom; } 113 | .ie7 input[type="checkbox"] { vertical-align: baseline; } 114 | .ie6 input { vertical-align: text-bottom; } 115 | 116 | /* Hand cursor on clickable input elements */ 117 | label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } 118 | 119 | /* Webkit browsers add a 2px margin outside the chrome of form elements */ 120 | button, input, select, textarea { margin: 0; } 121 | 122 | /* Colors for form validity */ 123 | input:valid, textarea:valid { } 124 | input:invalid, textarea:invalid { 125 | border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; 126 | } 127 | .no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } 128 | 129 | 130 | /* These selection declarations have to be separate 131 | No text-shadow: twitter.com/miketaylr/status/12228805301 132 | Also: hot pink! */ 133 | ::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; } 134 | ::selection { background:#FF5E99; color:#fff; text-shadow: none; } 135 | 136 | /* j.mp/webkit-tap-highlight-color */ 137 | a:link { -webkit-tap-highlight-color: #FF5E99; } 138 | 139 | /* Make buttons play nice in IE: 140 | www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */ 141 | button { width: auto; overflow: visible; } 142 | 143 | /* Bicubic resizing for non-native sized IMG: 144 | code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */ 145 | .ie7 img { -ms-interpolation-mode: bicubic; } 146 | 147 | /** 148 | * You might tweak these.. 149 | */ 150 | 151 | body, select, input, textarea { 152 | /* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */ 153 | color: #444; 154 | /* Set your base font here, to apply evenly */ 155 | /* font-family: Georgia, serif; */ 156 | } 157 | 158 | /* Headers (h1, h2, etc) have no default font-size or margin; define those yourself */ 159 | h1, h2, h3, h4, h5, h6 { font-weight: bold; } 160 | 161 | a, a:active, a:visited { color: #607890; } 162 | a:hover { color: #036; } 163 | 164 | 165 | /** 166 | * Primary styles 167 | * 168 | * Author: Joe Lencioni 169 | */ 170 | 171 | body { 172 | background-color: #def; 173 | } 174 | p { 175 | margin: 1em 0; 176 | } 177 | p:first-child { 178 | margin-top: 0; 179 | } 180 | p:last-child { 181 | margin-bottom: 0; 182 | } 183 | #container, footer { 184 | max-width: 65em; 185 | } 186 | header, #main { 187 | margin: 1em; 188 | } 189 | header { 190 | font-family: Georgia, serif; 191 | font-style: italic; 192 | background-color: rgba(0, 0, 0, .04); 193 | padding: 1em; 194 | -webkit-box-shadow: inset 0 0 6em rgba(0, 0, 0, .08); 195 | -moz-box-shadow: inset 0 0 6em rgba(0, 0, 0, .08); 196 | -ms-box-shadow: inset 0 0 6em rgba(0, 0, 0, .08); 197 | -o-box-shadow: inset 0 0 6em rgba(0, 0, 0, .08); 198 | box-shadow: inset 0 0 6em rgba(0, 0, 0, .08); 199 | } 200 | header h1 { 201 | font-size: 3.6em; 202 | font-weight: normal; 203 | } 204 | #main { 205 | padding: 1em; 206 | background-color: rgba(255, 255, 255, .4); 207 | -webkit-box-shadow: 0 0 1em rgba(0, 0, 0, .07), 208 | inset 0 0 1.5em .5em rgba(255, 255, 255, .4); 209 | -moz-box-shadow: 0 0 1em rgba(0, 0, 0, .07), 210 | inset 0 0 1.5em .5em rgba(255, 255, 255, .4); 211 | -ms-box-shadow: 0 0 1em rgba(0, 0, 0, .07), 212 | inset 0 0 1.5em .5em rgba(255, 255, 255, .4); 213 | -o-box-shadow: 0 0 1em rgba(0, 0, 0, .07), 214 | inset 0 0 1.5em .5em rgba(255, 255, 255, .4); 215 | box-shadow: 0 0 1em rgba(0, 0, 0, .07), 216 | inset 0 0 1.5em .5em rgba(255, 255, 255, .4); 217 | } 218 | #content { 219 | font-size: 1.2em; 220 | } 221 | footer { 222 | position: fixed; 223 | bottom: 1em; 224 | left: 1em; 225 | background-color: rgba(0, 0, 0, .07); 226 | padding: 1em; 227 | opacity: .3; 228 | -webkit-transition: opacity 0.3s ease-in-out; /* Saf3.2+, Chrome */ 229 | -moz-transition: opacity 0.3s ease-in-out; /* FF4+ */ 230 | -ms-transition: opacity 0.3s ease-in-out; /* IE10? */ 231 | -o-transition: opacity 0.3s ease-in-out; /* Opera 10.5+ */ 232 | transition: opacity 0.3s ease-in-out; 233 | -webkit-box-shadow: .3em .3em .5em rgba(0, 0, 0, .15); 234 | -moz-box-shadow: .3em .3em .5em rgba(0, 0, 0, .15); 235 | -ms-box-shadow: .3em .3em .5em rgba(0, 0, 0, .15); 236 | -o-box-shadow: .3em .3em .5em rgba(0, 0, 0, .15); 237 | box-shadow: .3em .3em .5em rgba(0, 0, 0, .15); 238 | } 239 | footer:hover { 240 | opacity: 1; 241 | } 242 | 243 | .responses p { 244 | text-indent: -3em; 245 | margin-left: 3em; 246 | } 247 | .Positive { 248 | color: green; 249 | } 250 | .Negative, .Fatal { 251 | color: red; 252 | } 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | /** 263 | * Non-semantic helper classes: please define your styles before this section. 264 | */ 265 | 266 | /* For image replacement */ 267 | .ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; } 268 | 269 | /* Hide for both screenreaders and browsers: 270 | css-discuss.incutio.com/wiki/Screenreader_Visibility */ 271 | .hidden { display: none; visibility: hidden; } 272 | 273 | /* Hide only visually, but have it available for screenreaders: by Jon Neal. 274 | www.webaim.org/techniques/css/invisiblecontent/ & j.mp/visuallyhidden */ 275 | .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } 276 | /* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: drupal.org/node/897638 */ 277 | .visuallyhidden.focusable:active, 278 | .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } 279 | 280 | /* Hide visually and from screenreaders, but maintain layout */ 281 | .invisible { visibility: hidden; } 282 | 283 | /* The Magnificent Clearfix: Updated to prevent margin-collapsing on child elements. 284 | j.mp/bestclearfix */ 285 | .clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; } 286 | .clearfix:after { clear: both; } 287 | /* Fix clearfix: blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page */ 288 | .clearfix { zoom: 1; } 289 | 290 | 291 | 292 | /** 293 | * Media queries for responsive design. 294 | * 295 | * These follow after primary styles so they will successfully override. 296 | */ 297 | 298 | @media all and (orientation:portrait) { 299 | /* Style adjustments for portrait mode goes here */ 300 | 301 | } 302 | 303 | @media all and (orientation:landscape) { 304 | /* Style adjustments for landscape mode goes here */ 305 | 306 | } 307 | 308 | /* Grade-A Mobile Browsers (Opera Mobile, Mobile Safari, Android Chrome) 309 | consider this: www.cloudfour.com/css-media-query-for-mobile-is-fools-gold/ */ 310 | @media screen and (max-device-width: 480px) { 311 | 312 | 313 | /* Uncomment if you don't want iOS and WinMobile to mobile-optimize the text for you: j.mp/textsizeadjust */ 314 | /* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */ 315 | } 316 | 317 | 318 | /** 319 | * Print styles. 320 | * 321 | * Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ 322 | */ 323 | @media print { 324 | * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important; 325 | -ms-filter: none !important; } /* Black prints faster: sanbeiji.com/archives/953 */ 326 | a, a:visited { color: #444 !important; text-decoration: underline; } 327 | a[href]:after { content: " (" attr(href) ")"; } 328 | abbr[title]:after { content: " (" attr(title) ")"; } 329 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ 330 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } 331 | thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */ 332 | tr, img { page-break-inside: avoid; } 333 | @page { margin: 0.5cm; } 334 | p, h2, h3 { orphans: 3; widows: 3; } 335 | h2, h3{ page-break-after: avoid; } 336 | } 337 | 338 | -------------------------------------------------------------------------------- /core/SLIRInstaller.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | namespace SLIR; 33 | 34 | /** 35 | * SLIR installer class 36 | * 37 | * @since 2.0 38 | * @author Joe Lencioni 39 | * @package SLIR 40 | * @subpackage Installer 41 | */ 42 | class SLIRInstaller 43 | { 44 | /** 45 | * @var string 46 | * @since 2.0 47 | */ 48 | const DEFAULT_PAGE_TITLE = 'Install SLIR (Smart Lencioni Image Resizer)'; 49 | 50 | /** 51 | * @var string 52 | * @since 2.0 53 | */ 54 | const DEFAULT_CONTENT_TITLE = '

Install SLIR

'; 55 | 56 | /** 57 | * @var string 58 | * @since 2.0 59 | */ 60 | const SAMPLE_CONFIG_FILEPATH = '../slirconfig-sample.class.php'; 61 | 62 | /** 63 | * @var string 64 | * @since 2.0 65 | */ 66 | const DEFAULT_CONFIG_FILEPATH = 'slirconfigdefaults.class.php'; 67 | 68 | /** 69 | * @since 2.0 70 | * @var SLIR 71 | */ 72 | private $slir; 73 | 74 | /** 75 | * @since 2.0 76 | * @var array 77 | */ 78 | private $templateCache; 79 | 80 | /** 81 | * @since 2.0 82 | * @return void 83 | */ 84 | public function __construct() 85 | { 86 | $this->slir = new SLIR(); 87 | 88 | $this->slir->escapeOutputBuffering(); 89 | 90 | echo $this->renderTemplate('header.html', array( 91 | self::DEFAULT_PAGE_TITLE, 92 | self::DEFAULT_CONTENT_TITLE, 93 | )); 94 | 95 | if (!defined('__DIR__')) { 96 | define('__DIR__', dirname(__FILE__)); 97 | } 98 | 99 | echo '

Installing SLIR

'; 100 | 101 | $tasks = array( 102 | 'PHP Version' => 'checkPHPVersion', 103 | 'Config File' => 'initializeConfigFile', 104 | 'Config Entropy' => 'checkConfigEntropy', 105 | 'Error Log' => 'initializeErrorLog', 106 | ); 107 | 108 | echo '
'; 109 | foreach ($tasks as $label => $function) { 110 | echo "

$label: "; 111 | flush(); 112 | 113 | $response = $this->$function(); 114 | echo $this->renderResponse($response); 115 | echo '

'; 116 | flush(); 117 | 118 | if ($this->responseIsFatal($response)) { 119 | echo $this->renderFatalResponseReceivedMessage(); 120 | break; 121 | } 122 | } 123 | echo '
'; 124 | 125 | echo $this->renderTemplate('footer.html', array()); 126 | } 127 | 128 | /** 129 | * @since 2.0 130 | * @return string 131 | */ 132 | private function renderFatalResponseReceivedMessage() 133 | { 134 | return '

Installation has not successfully completed. Please address the issues above and re-run installation.

'; 135 | } 136 | 137 | /** 138 | * Determines if the response is positive 139 | * 140 | * @since 2.0 141 | * @param SLIRInstallerResponse $response 142 | * @return boolean 143 | */ 144 | private function responseIsPositive(SLIRInstallerResponse $response) 145 | { 146 | return is_a($response, 'PositiveSLIRInstallerResponse'); 147 | } 148 | 149 | /** 150 | * Determines if the response is negative 151 | * 152 | * @since 2.0 153 | * @param SLIRInstallerResponse $response 154 | * @return boolean 155 | */ 156 | private function responseIsNegative(SLIRInstallerResponse $response) 157 | { 158 | return is_a($response, 'NegativeSLIRInstallerResponse'); 159 | } 160 | 161 | /** 162 | * Determines if the response is fatal 163 | * 164 | * @since 2.0 165 | * @param SLIRInstallerResponse $response 166 | * @return boolean 167 | */ 168 | private function responseIsFatal(SLIRInstallerResponse $response) 169 | { 170 | return is_a($response, 'FatalSLIRInstallerResponse'); 171 | } 172 | 173 | /** 174 | * Gets the contents of the template file and stores it in a variable to help prevent excessive disk reads. 175 | * 176 | * @param string $filename 177 | * @return string 178 | * @since 2.0 179 | */ 180 | private function getTemplate($filename) 181 | { 182 | if (!isset($this->templateCache[$filename])) { 183 | $this->templateCache[$filename] = file_get_contents("templates/$filename"); 184 | } 185 | return $this->templateCache[$filename]; 186 | } 187 | 188 | /** 189 | * @param string $filename 190 | * @param array $variables 191 | * @return string 192 | * @since 2.0 193 | */ 194 | private function renderTemplate($filename, array $variables) 195 | { 196 | return vsprintf($this->getTemplate($filename), $variables); 197 | } 198 | 199 | /** 200 | * @param SLIRInstallerResponse $response 201 | * @return string 202 | * @since 2.0 203 | */ 204 | public function renderResponse(SLIRInstallerResponse $response) 205 | { 206 | return $this->renderTemplate('response.html', array( 207 | $response->getType(), 208 | $response->getMessage(), 209 | $response->getDescription(), 210 | )); 211 | } 212 | 213 | /** 214 | * @param array $responses 215 | * @return string 216 | * @since 2.0 217 | */ 218 | public function renderResponses(array $responses) 219 | { 220 | $r = ''; 221 | foreach ($responses as $response) { 222 | $r .= $this->renderResponse($response); 223 | } 224 | return $r; 225 | } 226 | 227 | /** 228 | * @return string 229 | * @since 2.0 230 | */ 231 | private function getConfigPath() 232 | { 233 | return $this->slir->getConfigPath(); 234 | } 235 | 236 | /** 237 | * @return string 238 | * @since 2.0 239 | */ 240 | private function getSampleConfigPath() 241 | { 242 | return $this->slir->resolveRelativePath(self::SAMPLE_CONFIG_FILEPATH); 243 | } 244 | 245 | /** 246 | * @return string 247 | * @since 2.0 248 | */ 249 | private function getDefaultConfigPath() 250 | { 251 | return $this->slir->resolveRelativePath(self::DEFAULT_CONFIG_FILEPATH); 252 | } 253 | 254 | /** 255 | * @since 2.0 256 | * @param integer $number 257 | * @param string $singularPattern 258 | * @param string $pluralPattern 259 | * @return string 260 | */ 261 | private function renderQuantity($number, $singularPattern = '%d thing', $pluralPattern = '%d things') 262 | { 263 | if ($number === 1) { 264 | return sprintf($singularPattern, $number); 265 | } else { 266 | return sprintf($pluralPattern, $number); 267 | } 268 | } 269 | 270 | /** 271 | * Checks the version of PHP to make sure it is new enough for SLIR to work properly. 272 | */ 273 | private function checkPHPVersion() 274 | { 275 | $minimumVersion = '5.1.2'; 276 | 277 | if (version_compare(PHP_VERSION, $minimumVersion) >= 0) { 278 | return new PositiveSLIRInstallerResponse('Your PHP version is new enough for SLIR to work properly.'); 279 | } else { 280 | return new NegativeSLIRInstallerResponse(sprintf('You are running a version of PHP that is too old for SLIR to work properly. Please upgrade to version %s or newer.', $minimumVersion)); 281 | } 282 | } 283 | 284 | /** 285 | * Initializes SLIR's configuration file if it needs to be done. 286 | * 287 | * @return SLIRInstallerResponse 288 | * @since 2.0 289 | */ 290 | private function initializeConfigFile() 291 | { 292 | $config = $this->getConfigPath(); 293 | 294 | if (file_exists($config)) { 295 | $this->slir->getConfig(); 296 | return new PositiveSLIRInstallerResponse(vsprintf('Config file exists. Edit %s if you want to override any of the default settings found in %s.', array( 297 | $config, 298 | $this->getDefaultConfigPath(), 299 | ))); 300 | } 301 | 302 | if (file_exists($this->getSampleConfigPath())) { 303 | if (copy($this->getSampleConfigPath(), $config)) { 304 | $this->slir->getConfig(); 305 | return new PositiveSLIRInstallerResponse(vsprintf('Sample config file was successfully copied to %s. Edit this file if you want to override any of the default settings found in %s.', array( 306 | $config, 307 | $this->getDefaultConfigPath(), 308 | ))); 309 | } else { 310 | return new FatalSLIRInstallerResponse(vsprintf('Could not initialize configuration file. Please copy %s to %s and then edit it if you want to override any of the default settings.', array( 311 | $this->getSampleConfigPath(), 312 | $config, 313 | ))); 314 | } 315 | } 316 | 317 | return new FatalSLIRInstallerResponse(vsprintf('Could not find %s or %s. Please try downloading the latest version of SLIR and running the installer again.', array( 318 | $config, 319 | $this->getSampleConfigPath(), 320 | ))); 321 | } 322 | 323 | /** 324 | * Checks the config file being used against the default configuration and determines if anything needs to be updated. 325 | * 326 | * @since 2.0 327 | * @return SLIRInstallerResponse 328 | */ 329 | private function checkConfigEntropy() 330 | { 331 | $this->slir->getConfig(); 332 | 333 | $reflectDefaults = new \ReflectionClass('\SLIR\SLIRConfigDefaults'); 334 | $reflectConfig = new \ReflectionClass(SLIR::getConfigClass()); 335 | 336 | $defaultProperties = $reflectDefaults->getStaticProperties(); 337 | $configProperties = $reflectConfig->getStaticProperties(); 338 | 339 | $additions = array_diff(array_keys($configProperties), array_keys($defaultProperties)); 340 | 341 | if (count($additions) === 0) { 342 | return new PositiveSLIRInstallerResponse('There are no settings in your config file that are not also found in the default config file.'); 343 | } else { 344 | return new NegativeSLIRInstallerResponse(vsprintf('There %s in your config file that was not found in the default config file. %s most likely leftover from a previous version and should be addressed. Check the following %s in %s against what is found in %s: $%s', array( 345 | $this->renderQuantity(count($additions), 'is %d setting', 'are %d settings'), 346 | $this->renderQuantity(count($additions), 'This setting was', 'These settings were'), 347 | $this->renderQuantity(count($additions), 'setting', 'settings'), 348 | $this->getConfigPath(), 349 | $this->getDefaultConfigPath(), 350 | implode(', $', $additions), 351 | ))); 352 | } 353 | } 354 | 355 | /** 356 | * Checks to see if the SLIR error log exists and is writable. 357 | * 358 | * @since 2.0 359 | * @return SLIRInstallerResponse 360 | */ 361 | private function initializeErrorLog() 362 | { 363 | $configClass = SLIR::getConfigClass(); 364 | 365 | if (!file_exists($configClass::$pathToErrorLog)) { 366 | // Error log does not exist, try creating it 367 | if (file_put_contents($configClass::$pathToErrorLog, '') === false) { 368 | // Error log was unable to be created 369 | return new NegativeSLIRInstallerResponse(sprintf('Error log does not exist and could not be created at %s. Please create this file and make sure the web server has permission to write to it. If you would like to change the path of this file, set $pathToErrorLog in slirconfig.class.php and run the installer again.', $configClass::$pathToErrorLog)); 370 | } else { 371 | // Everything worked well 372 | return new PositiveSLIRInstallerResponse(sprintf('Error log successfully created at %s. If you would like to change the path of this file, set $pathToErrorLog in slirconfig.class.php and run the installer again.', $configClass::$pathToErrorLog)); 373 | } 374 | } else if (!is_writable($configClass::$pathToErrorLog)) { 375 | // Error log exists, but is not writable 376 | return new NegativeSLIRInstallerResponse(sprintf('Error log exists at %s but is not writable. Please make sure the web server has permission to write to this file. If you would like to change the path of this file, set $pathToErrorLog in slirconfig.class.php and run the installer again.', $configClass::$pathToErrorLog)); 377 | } else { 378 | // Everything is good 379 | return new PositiveSLIRInstallerResponse(sprintf('Error log exists at %s and is writable by the web server. If you would like to change the path of this file, set $pathToErrorLog in slirconfig.class.php and run the installer again.', $configClass::$pathToErrorLog)); 380 | } 381 | } 382 | } -------------------------------------------------------------------------------- /core/Libs/SLIRImage.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in 13 | * all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | * THE SOFTWARE. 22 | * 23 | * @copyright Copyright © 2014, Joe Lencioni 24 | * @license MIT 25 | */ 26 | 27 | namespace SLIR\Libs; 28 | 29 | abstract class SLIRImage 30 | { 31 | /** 32 | * @var string Path to file 33 | */ 34 | protected $path; 35 | 36 | /** 37 | * @var string Path to original file 38 | */ 39 | protected $originalPath; 40 | 41 | /** 42 | * @var integer quality to render image at 43 | */ 44 | protected $quality; 45 | 46 | /** 47 | * @var string background color in hex 48 | */ 49 | protected $background; 50 | 51 | /** 52 | * @var float amount to sharpen 53 | */ 54 | protected $sharpeningFactor; 55 | 56 | /** 57 | * @var boolean 58 | */ 59 | protected $progressive; 60 | 61 | /** 62 | * @var boolean 63 | */ 64 | protected $grayscale; 65 | 66 | /** 67 | * @var string specified cropper to use 68 | */ 69 | protected $cropper; 70 | 71 | /** 72 | * @var array information about the image 73 | */ 74 | protected $info; 75 | 76 | /** 77 | * Mime types 78 | * @var array 79 | * @since 2.0 80 | */ 81 | private $mimeTypes = array( 82 | 'JPEG' => array( 83 | 'image/jpeg' => 1, 84 | ), 85 | 'GIF' => array( 86 | 'image/gif' => 1, 87 | ), 88 | 'PNG' => array( 89 | 'image/png' => 1, 90 | 'image/x-png' => 1, 91 | ), 92 | 'BMP' => array( 93 | 'image/bmp' => 1, 94 | 'image/x-ms-bmp' => 1, 95 | ), 96 | ); 97 | 98 | /** 99 | * @param string $path 100 | * @return void 101 | */ 102 | public function __construct($path = null) 103 | { 104 | if ($path !== null) { 105 | $this->setPath($path); 106 | $this->setOriginalPath($path); 107 | } 108 | } 109 | 110 | /** 111 | * @return void 112 | */ 113 | public function __destruct() 114 | { 115 | unset( 116 | $this->path, 117 | $this->originalPath, 118 | $this->info 119 | ); 120 | } 121 | 122 | /** 123 | * Gets a hash that represents the properties of the image. 124 | * 125 | * Used for caching. 126 | * 127 | * @param $infosToInclude 128 | * @return string 129 | * @since 2.0 130 | */ 131 | public function getHash(array $infosToInclude = array()) 132 | { 133 | $infos = array( 134 | $this->getOriginalPath(), 135 | $this->getBackground(), 136 | $this->getSharpeningFactor(), 137 | $this->getProgressive(), 138 | $this->getInfo(), 139 | $this->getCropper(), 140 | $this->getQuality(), 141 | $this->getGrayscale() 142 | ); 143 | 144 | $infos = array_merge($infos, $infosToInclude); 145 | 146 | return hash('md4', serialize($infos)); 147 | } 148 | 149 | /** 150 | * Sets the path of the file 151 | * @param string $path 152 | * @return SLIRImageLibrary 153 | * @since 2.0 154 | */ 155 | final public function setPath($path) 156 | { 157 | $this->path = $path; 158 | return $this; 159 | } 160 | 161 | /** 162 | * Gets the path of the file 163 | * @return string 164 | * @since 2.0 165 | */ 166 | final public function getPath() 167 | { 168 | return $this->path; 169 | } 170 | 171 | /** 172 | * @return string 173 | * @since 2.0 174 | */ 175 | final public function getFullPath() 176 | { 177 | $configClass = \SLIR\SLIR::getConfigClass(); 178 | return $configClass::$documentRoot . $this->getPath(); 179 | } 180 | 181 | /** 182 | * Sets the path of the original file 183 | * @param string $path 184 | * @return SLIRImageLibrary 185 | * @since 2.0 186 | */ 187 | final public function setOriginalPath($path) 188 | { 189 | $this->originalPath = $path; 190 | return $this; 191 | } 192 | 193 | /** 194 | * Gets the path of the original file 195 | * @return string 196 | * @since 2.0 197 | */ 198 | final public function getOriginalPath() 199 | { 200 | return $this->originalPath; 201 | } 202 | 203 | /** 204 | * @return integer 205 | * @since 2.0 206 | */ 207 | public function getQuality() 208 | { 209 | return $this->quality; 210 | } 211 | 212 | /** 213 | * @param integer $quality 214 | * @return SLIRImageLibrary 215 | */ 216 | public function setQuality($quality) 217 | { 218 | $this->quality = $quality; 219 | return $this; 220 | } 221 | 222 | /** 223 | * @return string 224 | * @since 2.0 225 | */ 226 | public function getBackground() 227 | { 228 | return $this->background; 229 | } 230 | 231 | /** 232 | * @param string $color in hex 233 | * @return SLIRImageLibrary 234 | */ 235 | public function setBackground($color) 236 | { 237 | $this->background = $color; 238 | return $this; 239 | } 240 | 241 | /** 242 | * @return boolean 243 | * @since 2.0 244 | */ 245 | public function getProgressive() 246 | { 247 | return $this->progressive; 248 | } 249 | 250 | /** 251 | * @param boolean $progressive 252 | * @return SLIRImageLibrary 253 | */ 254 | public function setProgressive($progressive) 255 | { 256 | $this->progressive = $progressive; 257 | return $this; 258 | } 259 | 260 | /** 261 | * @return boolean 262 | * @since 2.0 263 | */ 264 | public function getGrayscale() 265 | { 266 | return $this->grayscale; 267 | } 268 | 269 | /** 270 | * @param boolean $grayscale 271 | * @return SLIRImageLibrary 272 | */ 273 | public function setGrayscale($grayscale) 274 | { 275 | $this->grayscale = $grayscale; 276 | return $this; 277 | } 278 | 279 | /** 280 | * Sets the sharpening factor of the image 281 | * @param float $sharpeningFactor 282 | * @return SLIRImageLibrary 283 | * @since 2.0 284 | */ 285 | final public function setSharpeningFactor($sharpeningFactor) 286 | { 287 | $this->sharpeningFactor = $sharpeningFactor; 288 | return $this; 289 | } 290 | 291 | /** 292 | * Gets the sharpening factor of the image 293 | * @return float 294 | * @since 2.0 295 | */ 296 | final public function getSharpeningFactor() 297 | { 298 | return $this->sharpeningFactor; 299 | } 300 | 301 | /** 302 | * Checks the mime type to see if it is an image 303 | * 304 | * @since 2.0 305 | * @return boolean 306 | */ 307 | final public function isImage() 308 | { 309 | if (substr($this->getMimeType(), 0, 6) == 'image/') { 310 | return true; 311 | } else { 312 | return false; 313 | } 314 | } 315 | 316 | /** 317 | * @since 2.0 318 | * @param string $type Can be 'JPEG', 'GIF', 'PNG', or 'BMP' 319 | * @return boolean 320 | */ 321 | final public function isOfType($type = 'JPEG') 322 | { 323 | if (isset($this->mimeTypes[$type][$this->getMimeType()])) { 324 | return true; 325 | } else { 326 | return false; 327 | } 328 | } 329 | 330 | /** 331 | * @since 2.0 332 | * @return boolean 333 | */ 334 | final public function isJPEG() 335 | { 336 | return $this->isOfType('JPEG'); 337 | } 338 | 339 | /** 340 | * @since 2.0 341 | * @return boolean 342 | */ 343 | final public function isGIF() 344 | { 345 | return $this->isOfType('GIF'); 346 | } 347 | 348 | /** 349 | * @since 2.0 350 | * @return boolean 351 | */ 352 | final public function isBMP() 353 | { 354 | return $this->isOfType('BMP'); 355 | } 356 | 357 | /** 358 | * @since 2.0 359 | * @return boolean 360 | */ 361 | final public function isPNG() 362 | { 363 | return $this->isOfType('PNG'); 364 | } 365 | 366 | /** 367 | * @since 2.0 368 | * @return boolean 369 | */ 370 | final public function isAbleToHaveTransparency() 371 | { 372 | if ($this->isPNG() || $this->isGIF()) { 373 | return true; 374 | } else { 375 | return false; 376 | } 377 | } 378 | 379 | /** 380 | * @since 2.0 381 | * @return boolean 382 | */ 383 | final protected function isSharpeningDesired() 384 | { 385 | if ($this->isJPEG()) { 386 | return true; 387 | } else { 388 | return false; 389 | } 390 | } 391 | 392 | /** 393 | * @since 2.0 394 | * @return integer 395 | */ 396 | final public function getArea() 397 | { 398 | return $this->getWidth() * $this->getHeight(); 399 | } 400 | 401 | /** 402 | * @since 2.0 403 | * @return float 404 | */ 405 | final public function getRatio() 406 | { 407 | if ($this->getHeight() === 0 || $this->getHeight() === null) { 408 | return null; 409 | } else { 410 | return $this->getWidth() / $this->getHeight(); 411 | } 412 | } 413 | 414 | /** 415 | * @since 2.0 416 | * @return float 417 | */ 418 | final public function getCropRatio() 419 | { 420 | if ($this->getCropHeight() === 0 || $this->getCropHeight() === null) { 421 | return null; 422 | } else { 423 | return $this->getCropWidth() / $this->getCropHeight(); 424 | } 425 | } 426 | 427 | /** 428 | * @since 2.0 429 | * @return integer 430 | */ 431 | final public function getCropWidth() 432 | { 433 | return (integer) $this->getInfo('cropWidth'); 434 | } 435 | 436 | /** 437 | * @since 2.0 438 | * @return integer 439 | */ 440 | final public function getCropHeight() 441 | { 442 | return (integer) $this->getInfo('cropHeight'); 443 | } 444 | 445 | /** 446 | * @since 2.0 447 | * @param integer $width 448 | * @return SLIRImage 449 | */ 450 | final public function setCropWidth($width) 451 | { 452 | $this->info['cropWidth'] = $width; 453 | return $this; 454 | } 455 | 456 | /** 457 | * @since 2.0 458 | * @param integer $height 459 | * @return SLIRImage 460 | */ 461 | final public function setCropHeight($height) 462 | { 463 | $this->info['cropHeight'] = $height; 464 | return $this; 465 | } 466 | 467 | /** 468 | * Gets the width of the image 469 | * @return integer 470 | * @since 2.0 471 | */ 472 | public function getWidth() 473 | { 474 | return (integer) $this->getInfo('width'); 475 | } 476 | 477 | /** 478 | * Gets the height of the image 479 | * @return integer 480 | * @since 2.0 481 | */ 482 | public function getHeight() 483 | { 484 | return (integer) $this->getInfo('height'); 485 | } 486 | 487 | /** 488 | * @since 2.0 489 | * @param integer $width 490 | * @return SLIRImage 491 | */ 492 | final public function setWidth($width) 493 | { 494 | $this->info['width'] = $width; 495 | return $this; 496 | } 497 | 498 | /** 499 | * @since 2.0 500 | * @param integer $height 501 | * @return SLIRImage 502 | */ 503 | final public function setHeight($height) 504 | { 505 | $this->info['height'] = $height; 506 | return $this; 507 | } 508 | 509 | /** 510 | * Gets the MIME type of the image 511 | * @return string 512 | * @since 2.0 513 | */ 514 | public function getMimeType() 515 | { 516 | return (string) $this->getInfo('mime'); 517 | } 518 | 519 | /** 520 | * Sets the MIME type of the image 521 | * @param string $mime 522 | * @return SLIRImageLibrary 523 | * @since 2.0 524 | */ 525 | public function setMimeType($mime) 526 | { 527 | $this->info['mime'] = $mime; 528 | return $this; 529 | } 530 | 531 | /** 532 | * @return string 533 | * @since 2.0 534 | */ 535 | public function getCropper() 536 | { 537 | $configClass = \SLIR\SLIR::getConfigClass(); 538 | if ($this->cropper !== null) { 539 | return $this->cropper; 540 | } else { 541 | return $configClass::$defaultCropper; 542 | } 543 | } 544 | 545 | /** 546 | * @param string $cropper 547 | * @return SLIRImage 548 | * @since 2.0 549 | */ 550 | public function setCropper($cropper) 551 | { 552 | $this->cropper = $cropper; 553 | return $this; 554 | } 555 | 556 | /** 557 | * @return integer size of image data 558 | */ 559 | public function getDatasize() 560 | { 561 | return strlen($this->getData()); 562 | } 563 | 564 | /** 565 | * Turns on transparency for image if no background fill color is 566 | * specified, otherwise, fills background with specified color 567 | * 568 | * @since 2.0 569 | * @return SLIRImageLibrary 570 | */ 571 | final public function background() 572 | { 573 | if ($this->isAbleToHaveTransparency()) { 574 | if ($this->getBackground() === false || $this->getBackground() === null) { 575 | // If this is a GIF or a PNG, we need to set up transparency 576 | $this->enableTransparency(); 577 | } else { 578 | // Fill the background with the specified color for matting purposes 579 | $this->fill($this->getBackground()); 580 | } 581 | } 582 | 583 | return $this; 584 | } 585 | 586 | /** 587 | * 588 | * @since 2.0 589 | * @return SLIRImageLibrary 590 | */ 591 | final public function grayscale() { 592 | 593 | if ($this->getGrayscale() == true) { 594 | $this->fadeToGray(); 595 | } 596 | 597 | return $this; 598 | } 599 | 600 | /** 601 | * @since 2.0 602 | * @return boolean 603 | */ 604 | protected function croppingIsNeeded() 605 | { 606 | if ($this->getCropWidth() === 0 || $this->getCropHeight() === 0) { 607 | return false; 608 | } else if ($this->getCropWidth() < $this->getWidth() || $this->getCropHeight() < $this->getHeight()) { 609 | return true; 610 | } else { 611 | return false; 612 | } 613 | } 614 | 615 | /** 616 | * @since 2.0 617 | */ 618 | public function applyTransformations() 619 | { 620 | $this->crop() 621 | ->sharpen() 622 | ->interlace() 623 | ->optimize() 624 | ->grayscale(); 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /core/SLIRRequest.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | namespace SLIR; 33 | /** 34 | * SLIR request class 35 | * 36 | * @since 2.0 37 | * @author Joe Lencioni 38 | * @package SLIR 39 | */ 40 | class SLIRRequest 41 | { 42 | 43 | const CROP_RATIO_DELIMITERS = ':.x'; 44 | 45 | /** 46 | * Path to image 47 | * 48 | * @since 2.0 49 | * @var string 50 | */ 51 | private $path; 52 | 53 | /** 54 | * Maximum width for resized image, in pixels 55 | * 56 | * @since 2.0 57 | * @var integer 58 | */ 59 | private $width; 60 | 61 | /** 62 | * Maximum height for resized image, in pixels 63 | * 64 | * @since 2.0 65 | * @var integer 66 | */ 67 | private $height; 68 | 69 | /** 70 | * Ratio of width:height to crop image to. 71 | * 72 | * For example, if a square shape is desired, the crop ratio should be "1:1" 73 | * or if a long rectangle is desired, the crop ratio could be "4:1". Stored 74 | * as an associative array with keys being 'width' and 'height'. 75 | * 76 | * @since 2.0 77 | * @var array 78 | */ 79 | private $cropRatio; 80 | 81 | /** 82 | * Name of the cropper to use, e.g. 'centered' or 'smart' 83 | * 84 | * @since 2.0 85 | * @var string 86 | */ 87 | private $cropper; 88 | 89 | /** 90 | * Quality of rendered image 91 | * 92 | * @since 2.0 93 | * @var integer 94 | */ 95 | private $quality; 96 | 97 | /** 98 | * Whether or not progressive JPEG output is turned on 99 | * @var boolean 100 | * @since 2.0 101 | */ 102 | private $progressive; 103 | 104 | /** 105 | * Whether or not grayscale is turned on 106 | * @var boolean 107 | * @since 2.0 108 | */ 109 | private $grayscale; 110 | 111 | /** 112 | * Color to fill background of transparent PNGs and GIFs 113 | * @var string 114 | * @since 2.0 115 | */ 116 | private $background; 117 | 118 | /** 119 | * @since 2.0 120 | * @var boolean 121 | */ 122 | private $isUsingDefaultImagePath = false; 123 | 124 | /** 125 | * @since 2.0 126 | */ 127 | final public function __construct() 128 | { 129 | } 130 | 131 | /** 132 | * @since 2.0 133 | */ 134 | final public function initialize() 135 | { 136 | $configClass = SLIR::getConfigClass(); 137 | $params = $this->getParameters(); 138 | 139 | // Set image path first 140 | if (isset($params['i']) && $params['i'] != '' && $params['i'] != '/') { 141 | $this->__set('i', $params['i']); 142 | unset($params['i']); 143 | } else if ($configClass::$defaultImagePath !== null) { 144 | $this->__set('i', $configClass::$defaultImagePath); 145 | } else { 146 | throw new \RuntimeException('Source image was not specified.'); 147 | } // if 148 | 149 | // Set the rest of the parameters 150 | foreach ($params as $name => $value) { 151 | $this->__set($name, $value); 152 | } // foreach 153 | } 154 | 155 | /** 156 | * Destructor method. Try to clean up memory. 157 | * 158 | * @return void 159 | * @since 2.0 160 | */ 161 | final public function __destruct() 162 | { 163 | unset($this->path); 164 | unset($this->width); 165 | unset($this->height); 166 | unset($this->cropRatio); 167 | unset($this->cropper); 168 | unset($this->quality); 169 | unset($this->progressive); 170 | unset($this->grayscale); 171 | unset($this->background); 172 | unset($this->isUsingDefaultImagePath); 173 | } 174 | 175 | /** 176 | * @since 2.0 177 | * @return void 178 | */ 179 | final public function __set($name, $value) 180 | { 181 | switch ($name) { 182 | case 'i': 183 | case 'image': 184 | case 'imagePath': 185 | case 'path': 186 | $this->setPath($value); 187 | break; 188 | 189 | case 'w': 190 | case 'width': 191 | $this->setWidth($value); 192 | break; 193 | 194 | case 'h': 195 | case 'height': 196 | $this->setHeight($value); 197 | break; 198 | 199 | case 'q': 200 | case 'quality': 201 | $this->setQuality($value); 202 | break; 203 | 204 | case 'p': 205 | case 'progressive': 206 | $this->setProgressive($value); 207 | break; 208 | 209 | case 'g': 210 | case 'grayscale': 211 | case 'greyscale': 212 | $this->setGrayscale($value); 213 | break; 214 | 215 | case 'b'; 216 | case 'background': 217 | case 'backgroundFillColor': 218 | $this->setBackgroundFillColor($value); 219 | break; 220 | 221 | case 'c': 222 | case 'cropRatio': 223 | $this->setCropRatio($value); 224 | break; 225 | } // switch 226 | } 227 | 228 | /** 229 | * @since 2.0 230 | * @return mixed 231 | */ 232 | final public function __get($name) 233 | { 234 | return $this->$name; 235 | } 236 | 237 | /** 238 | * @since 2.0 239 | * @return void 240 | */ 241 | private function setWidth($value) 242 | { 243 | $this->width = (int) $value; 244 | if ($this->width < 1) { 245 | throw new \RuntimeException('Width must be greater than 0: ' . $this->width); 246 | } 247 | } 248 | 249 | /** 250 | * @since 2.0 251 | * @return void 252 | */ 253 | private function setHeight($value) 254 | { 255 | $this->height = (int) $value; 256 | if ($this->height < 1) { 257 | throw new \RuntimeException('Height must be greater than 0: ' . $this->height); 258 | } 259 | } 260 | 261 | /** 262 | * @since 2.0 263 | * @return void 264 | */ 265 | private function setQuality($value) 266 | { 267 | $this->quality = (int) $value; 268 | if ($this->quality < 0 || $this->quality > 100) { 269 | throw new \RuntimeException('Quality must be between 0 and 100: ' . $this->quality); 270 | } 271 | } 272 | 273 | /** 274 | * @param string $value 275 | * @return void 276 | */ 277 | private function setProgressive($value) 278 | { 279 | $this->progressive = (bool) $value; 280 | } 281 | 282 | /** 283 | * @param string $value 284 | * @return void 285 | */ 286 | private function setGrayscale($value) 287 | { 288 | $this->grayscale = (bool) $value; 289 | } 290 | 291 | /** 292 | * @param string $value 293 | * @return void 294 | */ 295 | private function setBackgroundFillColor($value) 296 | { 297 | $this->background = preg_replace('/[^0-9a-fA-F]/', '', $value); 298 | 299 | if (strlen($this->background) == 3) { 300 | $this->background = $this->background[0] 301 | .$this->background[0] 302 | .$this->background[1] 303 | .$this->background[1] 304 | .$this->background[2] 305 | .$this->background[2]; 306 | } else if (strlen($this->background) != 6) { 307 | throw new \RuntimeException('Background fill color must be in hexadecimal format, longhand or shorthand: ' . $this->background); 308 | } // if 309 | } 310 | 311 | /** 312 | * @param string $value 313 | * @return void 314 | */ 315 | private function setCropRatio($value) 316 | { 317 | $delimiters = preg_quote(self::CROP_RATIO_DELIMITERS); 318 | $ratio = preg_split("/[$delimiters]/", (string) urldecode($value)); 319 | if (count($ratio) >= 2) { 320 | if ((float) $ratio[0] == 0 || (float) $ratio[1] == 0) { 321 | throw new \RuntimeException('Crop ratio must not contain a zero: ' . (string) $value); 322 | } 323 | 324 | $this->cropRatio = array( 325 | 'width' => (float) $ratio[0], 326 | 'height' => (float) $ratio[1], 327 | 'ratio' => (float) $ratio[0] / (float) $ratio[1] 328 | ); 329 | 330 | // If there was a third part, that is the cropper being specified 331 | if (count($ratio) >= 3) { 332 | $this->cropper = (string) $ratio[2]; 333 | } 334 | } else { 335 | throw new \RuntimeException('Crop ratio must be in [width]x[height] format (e.g. 2x1): ' . (string) $value); 336 | } // if 337 | } 338 | 339 | /** 340 | * Determines the parameters to use for resizing 341 | * 342 | * @since 2.0 343 | * @return array 344 | */ 345 | private function getParameters() 346 | { 347 | if (!$this->isUsingQueryString()) { 348 | // Using the mod_rewrite version 349 | return $this->getParametersFromURL(); 350 | } else { 351 | // Using the query string version 352 | return $_GET; 353 | } 354 | } 355 | 356 | /** 357 | * Gets parameters from the URL 358 | * 359 | * This is used for requests that are using the mod_rewrite syntax 360 | * 361 | * @since 2.0 362 | * @return array 363 | */ 364 | private function getParametersFromURL() 365 | { 366 | $configClass = SLIR::getConfigClass(); 367 | $params = array(); 368 | 369 | // The parameters should be the first set of characters after the SLIR path 370 | // 371 | if ($configClass::$urlToSLIR !== null) { 372 | $request = preg_replace('`.*?/'.preg_quote(basename($configClass::$urlToSLIR)) . '/`', '', (string) $_SERVER['REQUEST_URI'], 1); 373 | } 374 | else { 375 | $request = preg_replace('`.*?/' . preg_quote(basename($configClass::$pathToSLIR)) . '/`', '', (string) $_SERVER['REQUEST_URI'], 1); 376 | } 377 | $paramString = strtok($request, '/'); 378 | 379 | if ($paramString === false || $paramString === $request) { 380 | throw new \RuntimeException('Not enough parameters were given. 381 | 382 | Available parameters: 383 | w = Maximum width 384 | h = Maximum height 385 | c = Crop ratio (width.height(.cropper?)) 386 | q = Quality (0-100) 387 | b = Background fill color (RRGGBB or RGB) 388 | p = Progressive (0 or 1) 389 | g = Grayscale (0 or 1) 390 | 391 | Example usage: 392 | /slir/w300-h300-c1.1/path/to/image.jpg'); 393 | 394 | } 395 | 396 | // The image path should start right after the parameters 397 | $params['i'] = substr($request, strlen($paramString) + 1); // +1 for the slash 398 | 399 | // The parameters are separated by hyphens 400 | $rawParam = strtok($paramString, '-'); 401 | while ($rawParam !== false) { 402 | if (strlen($rawParam) > 1) { 403 | // The name of each parameter should be the first character of the parameter string and the value of each parameter should be the remaining characters of the parameter string 404 | $params[$rawParam[0]] = substr($rawParam, 1); 405 | } 406 | 407 | $rawParam = strtok('-'); 408 | } 409 | 410 | return $params; 411 | } 412 | 413 | /** 414 | * Determines if the request is using the mod_rewrite version or the query 415 | * string version 416 | * 417 | * @since 2.0 418 | * @return boolean 419 | */ 420 | private function isUsingQueryString() 421 | { 422 | $configClass = SLIR::getConfigClass(); 423 | if ($configClass::$forceQueryString === true) { 424 | return true; 425 | } else if (!empty($_SERVER['QUERY_STRING']) && count(array_intersect(array('i', 'w', 'h', 'q', 'c', 'b', 'p', 'g'), array_keys($_GET)))) { 426 | return true; 427 | } else { 428 | return false; 429 | } 430 | } 431 | 432 | /** 433 | * Checks if the default image path set in the config is being used for this request 434 | * 435 | * @since 2.0 436 | * @return boolean 437 | */ 438 | public function isUsingDefaultImagePath() 439 | { 440 | return $this->isUsingDefaultImagePath; 441 | } 442 | 443 | /** 444 | * @since 2.0 445 | * @param string $path 446 | */ 447 | private function setPath($path) 448 | { 449 | $configClass = SLIR::getConfigClass(); 450 | $this->path = $this->localizePath((string) urldecode($path)); 451 | 452 | if (!$this->isPathSecure()) { 453 | // Make sure the image path is secure 454 | throw new \RuntimeException('Image path may not contain ":", "..", "<", or ">"'); 455 | } else if (!$this->pathExists()) { 456 | // Make sure the image file exists 457 | if ($configClass::$defaultImagePath !== null && !$this->isUsingDefaultImagePath()) { 458 | $this->isUsingDefaultImagePath = true; 459 | return $this->setPath($configClass::$defaultImagePath); 460 | } else { 461 | throw new \RuntimeException('Image does not exist: ' . $this->fullPath()); 462 | } 463 | } 464 | } 465 | 466 | /** 467 | * Strips the domain and query string from the path if either is there 468 | * @since 2.0 469 | * @return string 470 | */ 471 | private function localizePath($path) 472 | { 473 | return '/' . trim($this->stripQueryString($this->stripProtocolAndDomain($path)), '/'); 474 | } 475 | 476 | /** 477 | * Strips the protocol and domain from the path if it is there 478 | * @since 2.0 479 | * @return string 480 | */ 481 | private function stripProtocolAndDomain($path) 482 | { 483 | return preg_replace('/^[^:]+:\/\/[^\/]+/i', '', $path); 484 | } 485 | 486 | /** 487 | * Strips the query string from the path if it is there 488 | * @since 2.0 489 | * @return string 490 | */ 491 | private function stripQueryString($path) 492 | { 493 | return preg_replace('/\?.*+/', '', $path); 494 | } 495 | 496 | /** 497 | * Checks to see if the path is secure 498 | * 499 | * For security, directories may not contain ':' and images may not contain 500 | * '..', '<', or '>'. 501 | * 502 | * @since 2.0 503 | * @return boolean 504 | */ 505 | private function isPathSecure() 506 | { 507 | if (strpos(dirname($this->path), ':') || preg_match('/(?:\.\.|<|>)/', $this->path)) { 508 | return false; 509 | } else { 510 | return true; 511 | } 512 | } 513 | 514 | /** 515 | * Determines if the path exists 516 | * 517 | * @since 2.0 518 | * @return boolean 519 | */ 520 | private function pathExists() 521 | { 522 | return is_file($this->fullPath()); 523 | } 524 | 525 | /** 526 | * @return string 527 | * @since 2.0 528 | */ 529 | final public function fullPath() 530 | { 531 | $configClass = SLIR::getConfigClass(); 532 | return $configClass::$documentRoot . $this->path; 533 | } 534 | 535 | /** 536 | * @since 2.0 537 | * @return boolean 538 | */ 539 | final public function isBackground() 540 | { 541 | if ($this->background !== null) { 542 | return true; 543 | } else { 544 | return false; 545 | } 546 | } 547 | 548 | /** 549 | * @since 2.0 550 | * @return boolean 551 | */ 552 | final public function isQuality() 553 | { 554 | if ($this->quality !== null) { 555 | return true; 556 | } else { 557 | return false; 558 | } 559 | } 560 | 561 | /** 562 | * @since 2.0 563 | * @return boolean 564 | */ 565 | final public function isCropping() 566 | { 567 | if ($this->cropRatio['width'] !== null && $this->cropRatio['height'] !== null) { 568 | return true; 569 | } else { 570 | return false; 571 | } 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /Test/core/slir.class.Test.php: -------------------------------------------------------------------------------- 1 | assertContains($header, $this->slir->getHeaders()); 18 | } 19 | 20 | /** 21 | * @param string $header 22 | * @return void 23 | */ 24 | private function assertHeaderNotSent($header) 25 | { 26 | $this->assertNotContains($header, $this->slir->getHeaders()); 27 | } 28 | 29 | /** 30 | * @test 31 | */ 32 | public function escapeOutputBuffering() 33 | { 34 | ob_start(); 35 | ob_start(); 36 | ob_start(); 37 | $inceptionLevel = ob_get_level(); 38 | 39 | $this->slir->escapeOutputBuffering(); 40 | 41 | $this->assertLessThan($inceptionLevel, ob_get_level()); 42 | } 43 | 44 | /** 45 | * @test 46 | * @expectedException RuntimeException 47 | * @expectedExceptionMessage Not enough parameters 48 | */ 49 | public function processRequestFromURLNoParameters() 50 | { 51 | $_SERVER['REQUEST_URI'] = ''; 52 | $this->slir->processRequestFromURL(); 53 | } 54 | 55 | /** 56 | * @test 57 | * 58 | * @return string image output 59 | */ 60 | public function processUncachedRequestFromURLWithOnlyWidthSpecified() 61 | { 62 | $_SERVER['REQUEST_URI'] = '/w50/Test/images/camera-logo.png'; 63 | 64 | $this->slir->uncache(); 65 | 66 | $this->assertFalse($this->slir->isRequestCached()); 67 | $this->assertFalse($this->slir->isRenderedCached()); 68 | 69 | ob_start(); 70 | $this->slir->processRequestFromURL(); 71 | $output = ob_get_clean(); 72 | 73 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 74 | $this->assertTrue($this->slir->isRequestCached()); 75 | $this->assertTrue($this->slir->isRenderedCached()); 76 | 77 | $image = imagecreatefromstring($output); 78 | $this->assertInternalType('resource', $image); 79 | $this->assertSame(50, imagesx($image)); 80 | 81 | imagedestroy($image); 82 | unset($image); 83 | 84 | return $output; 85 | } 86 | 87 | /** 88 | * @test 89 | * 90 | * @return string image output 91 | */ 92 | public function processUncachedRequestFromURLWithOnlyHeightSpecified() 93 | { 94 | $_SERVER['REQUEST_URI'] = '/h50/Test/images/camera-logo.png'; 95 | 96 | $this->slir->uncache(); 97 | 98 | $this->assertFalse($this->slir->isRequestCached()); 99 | $this->assertFalse($this->slir->isRenderedCached()); 100 | 101 | ob_start(); 102 | $this->slir->processRequestFromURL(); 103 | $output = ob_get_clean(); 104 | 105 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 106 | $this->assertTrue($this->slir->isRequestCached()); 107 | $this->assertTrue($this->slir->isRenderedCached()); 108 | 109 | $image = imagecreatefromstring($output); 110 | $this->assertInternalType('resource', $image); 111 | $this->assertSame(50, imagesy($image)); 112 | 113 | imagedestroy($image); 114 | unset($image); 115 | 116 | return $output; 117 | } 118 | 119 | /** 120 | * @test 121 | * 122 | * @return string image output 123 | */ 124 | public function processUncachedRequestFromURLWithOnlyQualitySpecified() 125 | { 126 | $_SERVER['REQUEST_URI'] = '/q10/Test/images/camera-logo.png'; 127 | 128 | $this->slir->uncache(); 129 | 130 | $this->assertFalse($this->slir->isRequestCached()); 131 | $this->assertFalse($this->slir->isRenderedCached()); 132 | 133 | ob_start(); 134 | $this->slir->processRequestFromURL(); 135 | $output = ob_get_clean(); 136 | 137 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 138 | $this->assertTrue($this->slir->isRequestCached()); 139 | $this->assertTrue($this->slir->isRenderedCached()); 140 | 141 | $image = imagecreatefromstring($output); 142 | $this->assertInternalType('resource', $image); 143 | 144 | imagedestroy($image); 145 | unset($image); 146 | 147 | return $output; 148 | } 149 | 150 | /** 151 | * @test 152 | * @depends processUncachedRequestFromURLWithOnlyWidthSpecified 153 | * 154 | * @param string $uncachedImageOutput 155 | */ 156 | public function processRequestThatShouldBeServedFromTheRequestCache($uncachedImageOutput) 157 | { 158 | $_SERVER['REQUEST_URI'] = '/w50/Test/images/camera-logo.png'; 159 | 160 | $this->assertTrue($this->slir->isRequestCached()); 161 | $this->assertTrue($this->slir->isRenderedCached()); 162 | 163 | ob_start(); 164 | $this->slir->processRequestFromURL(); 165 | $output = ob_get_clean(); 166 | 167 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 168 | 169 | $this->assertSame($uncachedImageOutput, $output); 170 | } 171 | 172 | /** 173 | * @test 174 | * @depends processUncachedRequestFromURLWithOnlyWidthSpecified 175 | * 176 | * @param string $uncachedImageOutput 177 | */ 178 | public function processRequestThatShouldBeServedFromTheRenderedCache($uncachedImageOutput) 179 | { 180 | 181 | $_SERVER['REQUEST_URI'] = '/w50-h10000/Test/images/camera-logo.png'; 182 | 183 | $this->slir->uncacheRequest(); 184 | 185 | $this->assertFalse($this->slir->isRequestCached()); 186 | $this->assertTrue($this->slir->isRenderedCached()); 187 | 188 | ob_start(); 189 | $this->slir->processRequestFromURL(); 190 | $output = ob_get_clean(); 191 | 192 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 193 | $this->assertTrue($this->slir->isRequestCached()); 194 | 195 | $this->assertSame($uncachedImageOutput, $output); 196 | } 197 | 198 | /** 199 | * @test 200 | */ 201 | public function processRequestThatShouldServeSourceImage() 202 | { 203 | $_SERVER['REQUEST_URI'] = '/w99999-q100/Test/images/camera-logo.png'; 204 | 205 | $this->assertFalse($this->slir->isRequestCached()); 206 | $this->assertFalse($this->slir->isRenderedCached()); 207 | 208 | ob_start(); 209 | $this->slir->processRequestFromURL(); 210 | $output = ob_get_clean(); 211 | 212 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 213 | $this->assertSame(file_get_contents(realpath(__DIR__ . '/../images/camera-logo.png')), $output); 214 | 215 | $this->assertFalse($this->slir->isRequestCached()); 216 | $this->assertFalse($this->slir->isRenderedCached()); 217 | } 218 | 219 | /** 220 | * @test 221 | * @depends processUncachedRequestFromURLWithOnlyWidthSpecified 222 | */ 223 | public function processRequestThatShouldBeCachedInTheBrowser() 224 | { 225 | $_SERVER['HTTP_IF_MODIFIED_SINCE'] = gmdate('D, d M Y H:i:s', time() + 100) . ' GMT'; 226 | 227 | $_SERVER['REQUEST_URI'] = '/w50/Test/images/camera-logo.png'; 228 | 229 | ob_start(); 230 | $this->slir->processRequestFromURL(); 231 | $output = ob_get_clean(); 232 | 233 | $this->assertHeaderSent('HTTP/1.1 304 Not Modified'); 234 | $this->assertSame('', $output); 235 | 236 | unset($_SERVER['HTTP_IF_MODIFIED_SINCE']); 237 | } 238 | 239 | /** 240 | * @test 241 | * 242 | * @return string image output 243 | */ 244 | public function processUncachedRequestFromURLWithHeightAndWidthSpecified() 245 | { 246 | $_SERVER['REQUEST_URI'] = '/w50-h50/Test/images/camera-logo.png'; 247 | 248 | $this->slir->uncache(); 249 | 250 | $this->assertFalse($this->slir->isRequestCached()); 251 | $this->assertFalse($this->slir->isRenderedCached()); 252 | 253 | ob_start(); 254 | $this->slir->processRequestFromURL(); 255 | $output = ob_get_clean(); 256 | 257 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 258 | $this->assertTrue($this->slir->isRequestCached()); 259 | $this->assertTrue($this->slir->isRenderedCached()); 260 | 261 | $image = imagecreatefromstring($output); 262 | $this->assertInternalType('resource', $image); 263 | $this->assertLessThan(50, imagesy($image)); 264 | $this->assertSame(50, imagesx($image)); 265 | 266 | imagedestroy($image); 267 | unset($image); 268 | 269 | return $output; 270 | } 271 | 272 | /** 273 | * @test 274 | * 275 | * @return string image output 276 | */ 277 | public function processUncachedRequestFromURLWithSquareCropSpecified() 278 | { 279 | $_SERVER['REQUEST_URI'] = '/w50-c1.1/Test/images/camera-logo.png'; 280 | 281 | $this->slir->uncache(); 282 | 283 | $this->assertFalse($this->slir->isRequestCached()); 284 | $this->assertFalse($this->slir->isRenderedCached()); 285 | 286 | ob_start(); 287 | $this->slir->processRequestFromURL(); 288 | $output = ob_get_clean(); 289 | 290 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 291 | $this->assertTrue($this->slir->isRequestCached()); 292 | $this->assertTrue($this->slir->isRenderedCached()); 293 | 294 | $image = imagecreatefromstring($output); 295 | $this->assertInternalType('resource', $image); 296 | $this->assertSame(imagesx($image), imagesy($image)); 297 | 298 | imagedestroy($image); 299 | unset($image); 300 | 301 | return $output; 302 | } 303 | 304 | /** 305 | * @test 306 | * 307 | * @return string image output 308 | */ 309 | public function processUncachedRequestFromURLWithWideCropSpecified() 310 | { 311 | $_SERVER['REQUEST_URI'] = '/w50-c2.1/Test/images/camera-logo.png'; 312 | 313 | $this->slir->uncache(); 314 | 315 | $this->assertFalse($this->slir->isRequestCached()); 316 | $this->assertFalse($this->slir->isRenderedCached()); 317 | 318 | ob_start(); 319 | $this->slir->processRequestFromURL(); 320 | $output = ob_get_clean(); 321 | 322 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 323 | $this->assertTrue($this->slir->isRequestCached()); 324 | $this->assertTrue($this->slir->isRenderedCached()); 325 | 326 | $image = imagecreatefromstring($output); 327 | $this->assertInternalType('resource', $image); 328 | $this->assertSame(50, imagesx($image)); 329 | $this->assertSame(25, imagesy($image)); 330 | 331 | imagedestroy($image); 332 | unset($image); 333 | 334 | return $output; 335 | } 336 | 337 | /** 338 | * @test 339 | * 340 | * @return string image output 341 | */ 342 | public function processUncachedRequestFromURLWithTallCropSpecified() 343 | { 344 | $_SERVER['REQUEST_URI'] = '/w50-c1.2/Test/images/camera-logo.png'; 345 | 346 | $this->slir->uncache(); 347 | 348 | $this->assertFalse($this->slir->isRequestCached()); 349 | $this->assertFalse($this->slir->isRenderedCached()); 350 | 351 | ob_start(); 352 | $this->slir->processRequestFromURL(); 353 | $output = ob_get_clean(); 354 | 355 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 356 | $this->assertTrue($this->slir->isRequestCached()); 357 | $this->assertTrue($this->slir->isRenderedCached()); 358 | 359 | $image = imagecreatefromstring($output); 360 | $this->assertInternalType('resource', $image); 361 | $this->assertSame(50, imagesx($image)); 362 | $this->assertSame(100, imagesy($image)); 363 | 364 | imagedestroy($image); 365 | unset($image); 366 | 367 | return $output; 368 | } 369 | 370 | /** 371 | * @test 372 | * 373 | * @return string image output 374 | */ 375 | public function processUncachedRequestFromURLWithSquareCropCenteredSpecified() 376 | { 377 | $_SERVER['REQUEST_URI'] = '/w50-c1.1.centered/Test/images/camera-logo.png'; 378 | 379 | $this->slir->uncache(); 380 | 381 | $this->assertFalse($this->slir->isRequestCached()); 382 | $this->assertFalse($this->slir->isRenderedCached()); 383 | 384 | ob_start(); 385 | $this->slir->processRequestFromURL(); 386 | $output = ob_get_clean(); 387 | 388 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 389 | $this->assertTrue($this->slir->isRequestCached()); 390 | $this->assertTrue($this->slir->isRenderedCached()); 391 | 392 | $image = imagecreatefromstring($output); 393 | $this->assertInternalType('resource', $image); 394 | $this->assertSame(imagesx($image), imagesy($image)); 395 | 396 | imagedestroy($image); 397 | unset($image); 398 | 399 | return $output; 400 | } 401 | 402 | /** 403 | * @test 404 | * @depends processUncachedRequestFromURLWithSquareCropCenteredSpecified 405 | * 406 | * @param string $centerCroppedImage 407 | * @return string image output 408 | */ 409 | public function processUncachedRequestFromURLWithSquareCropSmartSpecified($centerCroppedImage) 410 | { 411 | $_SERVER['REQUEST_URI'] = '/w50-c1.1.smart/Test/images/camera-logo.png'; 412 | 413 | $this->slir->uncache(); 414 | 415 | $this->assertFalse($this->slir->isRequestCached()); 416 | $this->assertFalse($this->slir->isRenderedCached()); 417 | 418 | ob_start(); 419 | $this->slir->processRequestFromURL(); 420 | $output = ob_get_clean(); 421 | 422 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 423 | $this->assertTrue($this->slir->isRequestCached()); 424 | $this->assertTrue($this->slir->isRenderedCached()); 425 | 426 | $this->assertNotSame($centerCroppedImage, $output); 427 | 428 | $image = imagecreatefromstring($output); 429 | $this->assertInternalType('resource', $image); 430 | $this->assertSame(50, imagesx($image)); 431 | $this->assertSame(50, imagesy($image)); 432 | 433 | imagedestroy($image); 434 | unset($image); 435 | 436 | return $output; 437 | } 438 | 439 | /** 440 | * @test 441 | * 442 | * @return string image output 443 | */ 444 | public function processUncachedRequestFromURLWithOnlyBlueBackgroundFill() 445 | { 446 | $_SERVER['REQUEST_URI'] = '/b00f/Test/images/camera-logo.png'; 447 | 448 | $this->slir->uncache(); 449 | 450 | $this->assertFalse($this->slir->isRequestCached()); 451 | $this->assertFalse($this->slir->isRenderedCached()); 452 | 453 | ob_start(); 454 | $this->slir->processRequestFromURL(); 455 | $output = ob_get_clean(); 456 | 457 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 458 | $this->assertTrue($this->slir->isRequestCached()); 459 | $this->assertTrue($this->slir->isRenderedCached()); 460 | 461 | $image = imagecreatefromstring($output); 462 | $this->assertInternalType('resource', $image); 463 | 464 | $color = imagecolorat($image, 0, 0); 465 | $rgb = imagecolorsforindex($image, $color); 466 | $this->assertSame(0, $rgb['red']); 467 | $this->assertSame(0, $rgb['green']); 468 | $this->assertSame(255, $rgb['blue']); 469 | $this->assertSame(0, $rgb['alpha']); 470 | 471 | imagedestroy($image); 472 | unset($image); 473 | 474 | return $output; 475 | } 476 | 477 | /** 478 | * @test 479 | * 480 | * @return string image output 481 | */ 482 | public function processUncachedRequestFromURLWithOnlyWidthSpecifiedForJPEG() 483 | { 484 | $_SERVER['REQUEST_URI'] = '/w50/Test/images/joe-lencioni.jpg'; 485 | 486 | $this->slir->uncache(); 487 | 488 | $this->assertFalse($this->slir->isRequestCached()); 489 | $this->assertFalse($this->slir->isRenderedCached()); 490 | 491 | ob_start(); 492 | $this->slir->processRequestFromURL(); 493 | $output = ob_get_clean(); 494 | 495 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 496 | $this->assertTrue($this->slir->isRequestCached()); 497 | $this->assertTrue($this->slir->isRenderedCached()); 498 | 499 | $image = imagecreatefromstring($output); 500 | $this->assertInternalType('resource', $image); 501 | $this->assertSame(50, imagesx($image)); 502 | 503 | imagedestroy($image); 504 | unset($image); 505 | 506 | return $output; 507 | } 508 | 509 | /** 510 | * @test 511 | * @depends processUncachedRequestFromURLWithOnlyWidthSpecifiedForJPEG 512 | * 513 | * @param string $defaultQualityImage 514 | * @return string image output 515 | */ 516 | public function processUncachedRequestFromURLWithWidthAndQualitySpecifiedForJPEG($defaultQualityImage) 517 | { 518 | $_SERVER['REQUEST_URI'] = '/w50-q10/Test/images/joe-lencioni.jpg'; 519 | 520 | $this->slir->uncache(); 521 | 522 | $this->assertFalse($this->slir->isRequestCached()); 523 | $this->assertFalse($this->slir->isRenderedCached()); 524 | 525 | ob_start(); 526 | $this->slir->processRequestFromURL(); 527 | $output = ob_get_clean(); 528 | 529 | $this->assertHeaderNotSent('HTTP/1.1 304 Not Modified'); 530 | $this->assertTrue($this->slir->isRequestCached()); 531 | $this->assertTrue($this->slir->isRenderedCached()); 532 | 533 | $this->assertNotSame($defaultQualityImage, $output); 534 | $this->assertLessThan(strlen($defaultQualityImage), strlen($output)); 535 | 536 | $image = imagecreatefromstring($output); 537 | $this->assertInternalType('resource', $image); 538 | $this->assertSame(50, imagesx($image)); 539 | 540 | imagedestroy($image); 541 | unset($image); 542 | 543 | return $output; 544 | } 545 | 546 | } 547 | -------------------------------------------------------------------------------- /icc/JpegIcc.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | * @license MIT 26 | * @author Richard Toth aka risko (risko@risko.org) 27 | * @version 0.1 28 | */ 29 | namespace Risko; 30 | 31 | class JpegIcc 32 | { 33 | /** 34 | * ICC header size in APP2 segment 35 | * 36 | * 'ICC_PROFILE' 0x00 chunk_no chunk_cnt 37 | */ 38 | const ICC_HEADER_LEN = 14; 39 | 40 | /** 41 | * maximum data len of a JPEG marker 42 | */ 43 | const MAX_BYTES_IN_MARKER = 65533; 44 | 45 | /** 46 | * ICC header marker 47 | */ 48 | const ICC_MARKER = "ICC_PROFILE\x00"; 49 | 50 | /** 51 | * Rendering intent field (Bytes 64 to 67 in ICC profile data) 52 | */ 53 | const ICC_RI_PERCEPTUAL = 0x00000000; 54 | const ICC_RI_RELATIVE_COLORIMETRIC = 0x00000001; 55 | const ICC_RI_SATURATION = 0x00000002; 56 | const ICC_RI_ABSOLUTE_COLORIMETRIC = 0x00000003; 57 | 58 | /** 59 | * ICC profile data 60 | * @var string 61 | */ 62 | private $icc_profile = ''; 63 | 64 | /** 65 | * ICC profile data size 66 | * @var int 67 | */ 68 | private $icc_size = 0; 69 | /** 70 | * ICC profile data chunks count 71 | * @var int 72 | */ 73 | private $icc_chunks = 0; 74 | 75 | 76 | /** 77 | * Class contructor 78 | */ 79 | public function __construct() 80 | { 81 | } 82 | 83 | /** 84 | * Load ICC profile from JPEG file. 85 | * 86 | * Returns true if profile successfully loaded, false otherwise. 87 | * 88 | * @param string file name 89 | * @return bool 90 | */ 91 | public function LoadFromJPEG($fname) 92 | { 93 | $f = file_get_contents($fname); 94 | $len = strlen($f); 95 | $pos = 0; 96 | $counter = 0; 97 | $profile_chunks = array(); // tu su ulozene jednotlive casti profilu 98 | 99 | while ($pos < $len && $counter < 1000) 100 | { 101 | $pos = strpos($f, "\xff", $pos); 102 | if ($pos === false) break; // dalsie 0xFF sa uz nenaslo - koniec vyhladavania 103 | 104 | $type = $this->getJPEGSegmentType($f, $pos); 105 | switch ($type) 106 | { 107 | case 0xe2: // APP2 108 | //echo "APP2 "; 109 | $size = $this->getJPEGSegmentSize($f, $pos); 110 | //echo "Size: $size\n"; 111 | 112 | if ($this->getJPEGSegmentContainsICC($f, $pos, $size)) 113 | { 114 | //echo "+ ICC Profile: YES\n"; 115 | list($chunk_no, $chunk_cnt) = $this->getJPEGSegmentICCChunkInfo($f, $pos); 116 | //echo "+ ICC Profile chunk number: $chunk_no\n"; 117 | //echo "+ ICC Profile chunks count: $chunk_cnt\n"; 118 | 119 | if ($chunk_no <= $chunk_cnt) 120 | { 121 | $profile_chunks[$chunk_no] = $this->getJPEGSegmentICCChunk($f, $pos); 122 | 123 | if ($chunk_no == $chunk_cnt) // posledny kusok 124 | { 125 | ksort($profile_chunks); 126 | $this->SetProfile(implode('', $profile_chunks)); 127 | return true; 128 | } 129 | } 130 | } 131 | $pos += $size + 2; // size of segment data + 2B size of segment marker 132 | break; 133 | 134 | case 0xe0: // APP0 135 | case 0xe1: // APP1 136 | case 0xe3: // APP3 137 | case 0xe4: // APP4 138 | case 0xe5: // APP5 139 | case 0xe6: // APP6 140 | case 0xe7: // APP7 141 | case 0xe8: // APP8 142 | case 0xe9: // APP9 143 | case 0xea: // APP10 144 | case 0xeb: // APP11 145 | case 0xec: // APP12 146 | case 0xed: // APP13 147 | case 0xee: // APP14 148 | case 0xef: // APP15 149 | case 0xc0: // SOF0 150 | case 0xc2: // SOF2 151 | case 0xc4: // DHT 152 | case 0xdb: // DQT 153 | case 0xda: // SOS 154 | case 0xfe: // COM 155 | $size = $this->getJPEGSegmentSize($f, $pos); 156 | $pos += $size + 2; // size of segment data + 2B size of segment marker 157 | break; 158 | 159 | case 0xd8: // SOI 160 | case 0xdd: // DRI 161 | case 0xd9: // EOI 162 | case 0xd0: // RST0 163 | case 0xd1: // RST1 164 | case 0xd2: // RST2 165 | case 0xd3: // RST3 166 | case 0xd4: // RST4 167 | case 0xd5: // RST5 168 | case 0xd6: // RST6 169 | case 0xd7: // RST7 170 | default: 171 | $pos += 2; 172 | break; 173 | } 174 | $counter++; 175 | } 176 | 177 | return false; 178 | } 179 | 180 | public function SaveToJPEG($fname) 181 | { 182 | if ($this->icc_profile == '') throw new \Exception("No profile loaded.\n"); 183 | 184 | if (!file_exists($fname)) throw new \Exception("File $fname doesn't exist.\n"); 185 | if (!is_readable($fname)) throw new \Exception("File $fname isn't readable.\n"); 186 | $dir = realpath($fname); 187 | if (!is_writable($dir)) throw new \Exception("Directory $fname isn't writeable.\n"); 188 | 189 | $f = file_get_contents($fname); 190 | if ($this->insertProfile($f)) 191 | { 192 | $fsize = strlen($f); 193 | $ret = file_put_contents($fname, $f); 194 | if ($ret === false || $ret < $fsize) throw new \Exception ("Write failed.\n"); 195 | } 196 | } 197 | 198 | /** 199 | * Load profile from ICC file. 200 | * 201 | * @param string file name 202 | */ 203 | public function LoadFromICC($fname) 204 | { 205 | if (!file_exists($fname)) throw new \Exception("File $fname doesn't exist.\n"); 206 | if (!is_readable($fname)) throw new \Exception("File $fname isn't readable.\n"); 207 | 208 | $this->SetProfile(file_get_contents($fname)); 209 | } 210 | 211 | /** 212 | * Save profile to ICC file. 213 | * 214 | * @param string file name 215 | * @param bool [force overwrite] 216 | */ 217 | public function SaveToICC($fname, $force_overwrite = false) 218 | { 219 | if ($this->icc_profile == '') throw new \Exception("No profile loaded.\n"); 220 | $dir = realpath($fname); 221 | if (!is_writable($dir)) throw new \Exception("Directory $fname isn't writeable.\n"); 222 | if (!$force_overwrite && file_exists($fname)) throw new \Exception("File $fname exists.\n"); 223 | 224 | $ret = file_put_contents($fname, $this->icc_profile); 225 | if ($ret === false || $ret < $this->icc_size) throw new \Exception ("Write failed.\n"); 226 | } 227 | 228 | /** 229 | * Remove profile from JPEG file and save it as a new file. 230 | * Overwriting destination file can be forced 231 | * 232 | * @param string source file 233 | * @param string destination file 234 | * @param bool [force overwrite] 235 | * @return bool 236 | */ 237 | public function RemoveFromJPEG($input, $output, $force_overwrite = false) 238 | { 239 | if (!file_exists($input)) throw new \Exception("File $input doesn't exist.\n"); 240 | if (!is_readable($input)) throw new \Exception("File $input isn't readable.\n"); 241 | $dir = realpath($output); 242 | if (!is_writable($dir)) throw new \Exception("Directory $output isn't writeable.\n"); 243 | if (!$force_overwrite && file_exists($output)) throw new \Exception("File $output exists.\n"); 244 | 245 | $f = file_get_contents($input); 246 | $this->removeProfile($f); 247 | $fsize = strlen($f); 248 | $ret = file_put_contents($output, $f); 249 | if ($ret === false || $ret < $fsize) throw new \Exception ("Write failed.\n"); 250 | 251 | return true; // any other error throws exception 252 | } 253 | 254 | /** 255 | * Set profile directly 256 | * 257 | * @param string profile data 258 | */ 259 | public function SetProfile($data) 260 | { 261 | $this->icc_profile = $data; 262 | $this->icc_size = strlen($data); 263 | $this->countChunks(); 264 | } 265 | 266 | /** 267 | * Get profile directly 268 | * 269 | * @return string 270 | */ 271 | public function GetProfile() 272 | { 273 | return $this->icc_profile; 274 | } 275 | 276 | /** 277 | * Count in how many chunks we need to divide the profile to store it in JPEG APP2 segments 278 | */ 279 | private function countChunks() 280 | { 281 | $this->icc_chunks = ceil($this->icc_size / ((float) (self::MAX_BYTES_IN_MARKER - self::ICC_HEADER_LEN))); 282 | } 283 | 284 | /** 285 | * Set Rendering Intent of the profile. 286 | * 287 | * Possilbe values are ICC_RI_PERCEPTUAL, ICC_RI_RELATIVE_COLORIMETRIC, ICC_RI_SATURATION or ICC_RI_ABSOLUTE_COLORIMETRIC. 288 | * 289 | * @param int rendering intent 290 | */ 291 | private function setRenderingIntent($newRI) 292 | { 293 | if ($this->icc_size >= 68) 294 | { 295 | substr_replace($this->icc_profile, pack('N', $newRI), 64, 4); 296 | } 297 | } 298 | 299 | /** 300 | * Get value of Rendering Intent field in ICC profile 301 | * 302 | * @return int 303 | */ 304 | private function getRenderingIntent() 305 | { 306 | if ($this->icc_size >= 68) 307 | { 308 | $arr = unpack('Nint', substr($this->icc_profile, 64, 4)); 309 | return $arr['int']; 310 | } 311 | 312 | return null; 313 | } 314 | 315 | /** 316 | * Size of JPEG segment 317 | * 318 | * @param string file data 319 | * @param int start of segment 320 | * @return int 321 | */ 322 | private function getJPEGSegmentSize(&$f, $pos) 323 | { 324 | $arr = unpack('nint', substr($f, $pos + 2, 2)); // segment size has offset 2 and length 2B 325 | return $arr['int']; 326 | } 327 | 328 | /** 329 | * Type of JPEG segment 330 | * 331 | * @param string file data 332 | * @param int start of segment 333 | * @return int 334 | */ 335 | private function getJPEGSegmentType(&$f, $pos) 336 | { 337 | $arr = unpack('Cchar', substr($f, $pos + 1, 1)); // segment type has offset 1 and length 1B 338 | return $arr['char']; 339 | } 340 | 341 | /** 342 | * Check if segment contains ICC profile marker 343 | * 344 | * @param string file data 345 | * @param int position of segment data 346 | * @param int size of segment data (without 2 bytes of size field) 347 | * @return bool 348 | */ 349 | private function getJPEGSegmentContainsICC(&$f, $pos, $size) 350 | { 351 | if ($size < self::ICC_HEADER_LEN) return false; // ICC_PROFILE 0x00 Marker_no Marker_cnt 352 | 353 | return (bool) (substr($f, $pos + 4, self::ICC_HEADER_LEN - 2) == self::ICC_MARKER); // 4B offset in segment data = 2B segment marker + 2B segment size data 354 | } 355 | 356 | /** 357 | * Get ICC segment chunk info 358 | * 359 | * @param string file data 360 | * @param int position of segment data 361 | * @return array {chunk_no, chunk_cnt} 362 | */ 363 | private function getJPEGSegmentICCChunkInfo(&$f, $pos) 364 | { 365 | $a = unpack('Cchunk_no/Cchunk_count', substr($f, $pos + 16, 2)); // 16B offset to data = 2B segment marker + 2B segment size + 'ICC_PROFILE' + 0x00, 1. byte chunk number, 2. byte chunks count 366 | return array_values($a); 367 | } 368 | 369 | /** 370 | * Returns chunk of ICC profile data from segment. 371 | * 372 | * @param string &data 373 | * @param int current position 374 | * @return string 375 | */ 376 | private function getJPEGSegmentICCChunk(&$f, $pos) 377 | { 378 | $data_offset = $pos + 4 + self::ICC_HEADER_LEN; // 4B JPEG APP offset + 14B ICC header offset 379 | $size = $this->getJPEGSegmentSize($f, $pos); 380 | $data_size = $size - self::ICC_HEADER_LEN - 2; // 14B ICC header - 2B of size data 381 | return substr($f, $data_offset, $data_size); 382 | } 383 | 384 | /** 385 | * Get data of given chunk 386 | * 387 | * @param int chunk number 388 | * @return string 389 | */ 390 | private function getChunk($chunk_no) 391 | { 392 | if ($chunk_no > $this->icc_chunks) return ''; 393 | 394 | $max_chunk_size = self::MAX_BYTES_IN_MARKER - self::ICC_HEADER_LEN; 395 | $from = ($chunk_no - 1) * $max_chunk_size; 396 | $bytes = ($chunk_no < $this->icc_chunks) ? $max_chunk_size : $this->icc_size % $max_chunk_size; 397 | 398 | return substr($this->icc_profile, $from, $bytes); 399 | } 400 | 401 | private function prepareJPEGProfileData() 402 | { 403 | $data = ''; 404 | 405 | for ($i = 1; $i <= $this->icc_chunks; $i++) 406 | { 407 | $chunk = $this->getChunk($i); 408 | $chunk_size = strlen($chunk); 409 | $data .= "\xff\xe2" . pack('n', $chunk_size + 2 + self::ICC_HEADER_LEN); // APP2 segment marker + size field 410 | $data .= self::ICC_MARKER . pack('CC', $i, $this->icc_chunks); // profile marker inside segment 411 | $data .= $chunk; 412 | } 413 | 414 | return $data; 415 | } 416 | 417 | /** 418 | * Removes profile from JPEG data 419 | * 420 | * @param string &data 421 | * @return bool 422 | */ 423 | private function removeProfile(&$jpeg_data) 424 | { 425 | $len = strlen($jpeg_data); 426 | $pos = 0; 427 | $counter = 0; // ehm... 428 | $chunks_to_go = -1; 429 | 430 | while ($pos < $len && $counter < 100) 431 | { 432 | $pos = strpos($jpeg_data, "\xff", $pos); 433 | if ($pos === false) break; // no more 0xFF - we can end up with search 434 | 435 | // analyze next segment 436 | $type = $this->getJPEGSegmentType($jpeg_data, $pos); 437 | 438 | switch ($type) 439 | { 440 | case 0xe2: // APP2 441 | $size = $this->getJPEGSegmentSize($jpeg_data, $pos); 442 | 443 | if ($this->getJPEGSegmentContainsICC($jpeg_data, $pos, $size)) 444 | { 445 | list($chunk_no, $chunk_cnt) = $this->getJPEGSegmentICCChunkInfo($jpeg_data, $pos); 446 | if ($chunks_to_go == -1) $chunks_to_go = $chunk_cnt; // first time save chunks count 447 | 448 | $jpeg_data = substr_replace($jpeg_data, '', $pos, $size + 2); // remove this APP segment from dataset (segment size + 2B app marker) 449 | $len -= $size + 2; // shorten the size 450 | 451 | if (--$chunks_to_go == 0) return true; // no more icc profile chunks, store file 452 | 453 | break; // go out without changing the position 454 | } 455 | $pos += $size + 2; // size of segment data + 2B size of segment marker 456 | break; 457 | 458 | case 0xe0: // APP0 459 | case 0xe1: // APP1 460 | case 0xe3: // APP3 461 | case 0xe4: // APP4 462 | case 0xe5: // APP5 463 | case 0xe6: // APP6 464 | case 0xe7: // APP7 465 | case 0xe8: // APP8 466 | case 0xe9: // APP9 467 | case 0xea: // APP10 468 | case 0xeb: // APP11 469 | case 0xec: // APP12 470 | case 0xed: // APP13 471 | case 0xee: // APP14 472 | case 0xef: // APP15 473 | case 0xc0: // SOF0 474 | case 0xc2: // SOF2 475 | case 0xc4: // DHT 476 | case 0xdb: // DQT 477 | case 0xda: // SOS 478 | case 0xfe: // COM 479 | $size = $this->getJPEGSegmentSize($jpeg_data, $pos); 480 | $pos += $size + 2; // size of segment data + 2B size of segment marker 481 | break; 482 | 483 | case 0xd8: // SOI 484 | case 0xdd: // DRI 485 | case 0xd9: // EOI 486 | case 0xd0: // RST0 487 | case 0xd1: // RST1 488 | case 0xd2: // RST2 489 | case 0xd3: // RST3 490 | case 0xd4: // RST4 491 | case 0xd5: // RST5 492 | case 0xd6: // RST6 493 | case 0xd7: // RST7 494 | default: 495 | $pos += 2; 496 | break; 497 | } 498 | $counter++; 499 | } 500 | 501 | return false; 502 | } 503 | 504 | /** 505 | * Inserts profile to JPEG data. 506 | * 507 | * Inserts profile immediately after SOI section 508 | * 509 | * @param string &data 510 | * @return bool 511 | */ 512 | private function insertProfile(&$jpeg_data) 513 | { 514 | $len = strlen($jpeg_data); 515 | $pos = 0; 516 | $counter = 0; // ehm... 517 | $chunks_to_go = -1; 518 | 519 | while ($pos < $len && $counter < 100) 520 | { 521 | $pos = strpos($jpeg_data, "\xff", $pos); 522 | if ($pos === false) break; // no more 0xFF - we can end up with search 523 | 524 | // analyze next segment 525 | $type = $this->getJPEGSegmentType($jpeg_data, $pos); 526 | 527 | switch ($type) 528 | { 529 | case 0xd8: // SOI 530 | $pos += 2; 531 | 532 | $p_data = $this->prepareJPEGProfileData(); 533 | if ($p_data != '') 534 | { 535 | $before = substr($jpeg_data, 0, $pos); 536 | $after = substr($jpeg_data, $pos); 537 | $jpeg_data = $before . $p_data . $after; 538 | return true; 539 | } 540 | return false; 541 | //break; 542 | 543 | case 0xe0: // APP0 544 | case 0xe1: // APP1 545 | case 0xe2: // APP2 546 | case 0xe3: // APP3 547 | case 0xe4: // APP4 548 | case 0xe5: // APP5 549 | case 0xe6: // APP6 550 | case 0xe7: // APP7 551 | case 0xe8: // APP8 552 | case 0xe9: // APP9 553 | case 0xea: // APP10 554 | case 0xeb: // APP11 555 | case 0xec: // APP12 556 | case 0xed: // APP13 557 | case 0xee: // APP14 558 | case 0xef: // APP15 559 | case 0xc0: // SOF0 560 | case 0xc2: // SOF2 561 | case 0xc4: // DHT 562 | case 0xdb: // DQT 563 | case 0xda: // SOS 564 | case 0xfe: // COM 565 | $size = $this->getJPEGSegmentSize($jpeg_data, $pos); 566 | $pos += $size + 2; // size of segment data + 2B size of segment marker 567 | break; 568 | 569 | case 0xdd: // DRI 570 | case 0xd9: // EOI 571 | case 0xd0: // RST0 572 | case 0xd1: // RST1 573 | case 0xd2: // RST2 574 | case 0xd3: // RST3 575 | case 0xd4: // RST4 576 | case 0xd5: // RST5 577 | case 0xd6: // RST6 578 | case 0xd7: // RST7 579 | default: 580 | $pos += 2; 581 | break; 582 | } 583 | $counter++; 584 | } 585 | 586 | return false; 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /core/Libs/GD/SLIRGDImage.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | */ 32 | 33 | namespace SLIR\Libs\GD; 34 | 35 | use \SLIR\Libs\SLIRImage; 36 | use \SLIR\Libs\SLIRImageLibrary; 37 | 38 | /** 39 | * Class for working with the GD image library 40 | * @package SLIR 41 | * @since 2.0 42 | */ 43 | class SLIRGDImage extends SLIRImage implements SLIRImageLibrary 44 | { 45 | 46 | /** 47 | * @var resource GD image resource 48 | */ 49 | private $image; 50 | 51 | /** 52 | * @var string $data 53 | */ 54 | private $data; 55 | 56 | private $transparencyEnabled = false; 57 | 58 | /** 59 | * @param string $path 60 | * @return void 61 | * @since 2.0 62 | */ 63 | public function __construct($path = null) 64 | { 65 | // Allows some funky JPEGs to work instead of breaking everything 66 | ini_set('gd.jpeg_ignore_warning', '1'); 67 | 68 | return parent::__construct($path); 69 | } 70 | 71 | /** 72 | * @return void 73 | * @since 2.0 74 | */ 75 | public function __destruct() 76 | { 77 | unset( 78 | $this->image 79 | ); 80 | return parent::__destruct(); 81 | } 82 | 83 | /** 84 | * Gets a hash that represents the properties of the image. 85 | * 86 | * Used for caching. 87 | * 88 | * @param $infosToInclude 89 | * @return string 90 | * @since 2.0 91 | */ 92 | public function getHash(array $infosToInclude = array()) 93 | { 94 | $infos = array( 95 | ); 96 | 97 | $infos = array_merge($infos, $infosToInclude); 98 | 99 | return parent::getHash($infos); 100 | } 101 | 102 | /** 103 | * @return resource 104 | * @since 2.0 105 | */ 106 | public function getImage() 107 | { 108 | if ($this->image === null) { 109 | if ($this->getPath() === null) { 110 | $this->create(); 111 | } else { 112 | try { 113 | if ($this->isJPEG()) { 114 | $this->image = imagecreatefromjpeg($this->getFullPath()); 115 | } else if ($this->isGIF()) { 116 | $this->image = imagecreatefromgif($this->getFullPath()); 117 | } else if ($this->isPNG()) { 118 | $this->image = imagecreatefrompng($this->getFullPath()); 119 | } else if ($this->isBMP()) { 120 | $this->image = $this->imagecreatefrombmp($this->getFullPath()); 121 | } 122 | } catch (\Exception $e) { 123 | // Try an alternate catch-all method 124 | $this->image = imagecreatefromstring(file_get_contents($this->getFullPath())); 125 | } 126 | 127 | $this->info = null; 128 | } 129 | } 130 | 131 | return $this->image; 132 | } 133 | 134 | /** 135 | * @since 2.0 136 | * @param string $path path to BMP file 137 | * @return resource 138 | * @link http://us.php.net/manual/en/function.imagecreatefromwbmp.php#86214 139 | */ 140 | public function imagecreatefrombmp($path) 141 | { 142 | // Load the image into a string 143 | $read = file_get_contents($path); 144 | 145 | $temp = unpack('H*', $read); 146 | $hex = $temp[1]; 147 | $header = substr($hex, 0, 108); 148 | 149 | // Process the header 150 | // Structure: http://www.fastgraph.com/help/bmp_header_format.html 151 | if (substr($header, 0, 4) == '424d') { 152 | // Get the width 4 bytes 153 | $width = hexdec($header[38] . $header[39] . $header[36] . $header[37]); 154 | 155 | // Get the height 4 bytes 156 | $height = hexdec($header[46] . $header[47] . $header[44] . $header[45]); 157 | } 158 | 159 | // Define starting X and Y 160 | $x = 0; 161 | $y = 1; 162 | 163 | // Create newimage 164 | $image = imagecreatetruecolor($width, $height); 165 | 166 | // Grab the body from the image 167 | $body = substr($hex, 108); 168 | 169 | // Calculate if padding at the end-line is needed 170 | // Divided by two to keep overview. 171 | // 1 byte = 2 HEX-chars 172 | $bodySize = (strlen($body) / 2); 173 | $headerSize = ($width * $height); 174 | 175 | // Use end-line padding? Only when needed 176 | $usePadding = ($bodySize > ($headerSize * 3) + 4); 177 | 178 | // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption 179 | // Calculate the next DWORD-position in the body 180 | for ($i = 0; $i < $bodySize; $i += 3) { 181 | // Calculate line-ending and padding 182 | if ($x >= $width) { 183 | // If padding needed, ignore image-padding 184 | // Shift i to the ending of the current 32-bit-block 185 | if ($usePadding) { 186 | $i += $width % 4; 187 | } 188 | 189 | // Reset horizontal position 190 | $x = 0; 191 | 192 | // Raise the height-position (bottom-up) 193 | ++$y; 194 | 195 | // Reached the image-height? Break the for-loop 196 | if ($y > $height) { 197 | break; 198 | } 199 | } 200 | 201 | // Calculation of the RGB-pixel (defined as BGR in image-data) 202 | // Define $iPos as absolute position in the body 203 | $iPos = $i * 2; 204 | $r = hexdec($body[$iPos + 4] . $body[$iPos + 5]); 205 | $g = hexdec($body[$iPos + 2] . $body[$iPos + 3]); 206 | $b = hexdec($body[$iPos] . $body[$iPos + 1]); 207 | 208 | // Calculate and draw the pixel 209 | $color = imagecolorallocate($image, $r, $g, $b); 210 | imagesetpixel($image, $x, $height - $y, $color); 211 | 212 | // Raise the horizontal position 213 | ++$x; 214 | } 215 | 216 | // Unset the body / free the memory 217 | unset($body); 218 | 219 | // Return image-object 220 | return $image; 221 | } 222 | 223 | /** 224 | * Resamples the image into the destination image 225 | * @param SLIRGDImage $destination 226 | * @return SLIRImageLibrary 227 | * @since 2.0 228 | */ 229 | public function resample(SLIRImageLibrary $destination) 230 | { 231 | imagecopyresampled( 232 | $destination->getImage(), 233 | $this->getImage(), 234 | 0, 235 | 0, 236 | 0, 237 | 0, 238 | $destination->getWidth(), 239 | $destination->getHeight(), 240 | $this->getWidth(), 241 | $this->getHeight() 242 | ); 243 | 244 | return $this; 245 | } 246 | 247 | /** 248 | * Copies the image into the destination image without reszing 249 | * @param SLIRGDImage $destination 250 | * @return SLIRImageLibrary 251 | * @since 2.0 252 | */ 253 | public function copy(SLIRImageLibrary $destination) 254 | { 255 | imagecopy( 256 | $destination->getImage(), 257 | $this->getImage(), 258 | 0, 259 | 0, 260 | 0, 261 | 0, 262 | $this->getWidth(), 263 | $this->getHeight() 264 | ); 265 | 266 | return $this; 267 | } 268 | 269 | /** 270 | * Gets width, height, and iptc information from the image 271 | * @param string $info 272 | * @return mixed 273 | * @since 2.0 274 | */ 275 | public function getInfo($info = null) 276 | { 277 | if ($this->info === null) { 278 | if ($this->getPath() === null) { 279 | // If there is no path, get the info from the image resource 280 | if ($this->getImage() === null) { 281 | // There is nothing to get 282 | } else { 283 | $this->info['width'] = imagesx($this->getImage()); 284 | $this->info['height'] = imagesy($this->getImage()); 285 | $this->info['mime'] = image_type_to_mime_type(exif_imagetype($this->getImage())); 286 | } 287 | } else { 288 | // There is a path, so get the info from the file 289 | $this->info = getimagesize($this->getFullPath(), $extraInfo); 290 | 291 | if ($this->info === false) { 292 | header('HTTP/1.1 400 Bad Request'); 293 | throw new \RuntimeException('getimagesize failed (source file may not be an image): ' . $this->getFullPath()); 294 | } 295 | 296 | $this->info['width'] =& $this->info[0]; 297 | $this->info['height'] =& $this->info[1]; 298 | $this->info['mime'] = image_type_to_mime_type(exif_imagetype($this->getFullPath())); 299 | 300 | // IPTC 301 | if (is_array($extraInfo) && isset($extraInfo['APP13'])) { 302 | $this->info['iptc'] = iptcparse($extraInfo['APP13']); 303 | } 304 | } 305 | } 306 | 307 | if ($info === null) { 308 | return $this->info; 309 | } else { 310 | if (isset($this->info[$info])) { 311 | return $this->info[$info]; 312 | } else { 313 | return null; 314 | } 315 | } 316 | } 317 | 318 | /** 319 | * Creates a new, blank image 320 | * @return SLIRImageLibrary 321 | */ 322 | public function create() 323 | { 324 | $this->image = imagecreatetruecolor($this->getWidth(), $this->getHeight()); 325 | 326 | return $this; 327 | } 328 | 329 | /** 330 | * Turns on the alpha channel to enable transparency in the image 331 | * @return SLIRImageLibrary 332 | * @since 2.0 333 | */ 334 | public function enableTransparency() 335 | { 336 | imagealphablending($this->getImage(), false); 337 | imagesavealpha($this->getImage(), true); 338 | 339 | $this->transparencyEnabled = true; 340 | 341 | return $this; 342 | } 343 | 344 | /** 345 | * Fade image to grayscale 346 | * @return SLIRImageLibrary 347 | * @since 2.0 348 | */ 349 | public function fadeToGray() 350 | { 351 | imagefilter ( $this->getImage() , IMG_FILTER_GRAYSCALE ); 352 | return $this; 353 | } 354 | 355 | /** 356 | * Fills the image with the set background color 357 | * @return SLIRImageLibrary 358 | * @since 2.0 359 | */ 360 | public function fill() 361 | { 362 | $color = $this->getBackground(); 363 | 364 | if ($color === null) { 365 | $color = "ffffff"; 366 | } 367 | 368 | $background = null; 369 | 370 | if ($this->transparencyEnabled === true) { 371 | $background = imagecolorallocatealpha( 372 | $this->getImage(), 373 | hexdec($color[0].$color[1]), 374 | hexdec($color[2].$color[3]), 375 | hexdec($color[4].$color[5]), 376 | 127 377 | ); 378 | } 379 | else { 380 | 381 | $background = imagecolorallocate( 382 | $this->getImage(), 383 | hexdec($color[0].$color[1]), 384 | hexdec($color[2].$color[3]), 385 | hexdec($color[4].$color[5]) 386 | ); 387 | 388 | } 389 | 390 | imagefilledrectangle($this->getImage(), 0, 0, $this->getWidth(), $this->getHeight(), $background); 391 | 392 | return $this; 393 | } 394 | 395 | /** 396 | * Turns interlacing on or off 397 | * @return SLIRImageLibrary 398 | * @since 2.0 399 | */ 400 | public function interlace() 401 | { 402 | imageinterlace($this->getImage(), $this->getProgressive()); 403 | return $this; 404 | } 405 | 406 | /** 407 | * Gets the class that will be used to determine the crop offset for the image 408 | * 409 | * @since 2.0 410 | * @return SLIRCropper 411 | */ 412 | final public function getCropperClass() 413 | { 414 | $configClass = \SLIR\SLIR::getConfigClass(); 415 | 416 | $cropClass = 'SLIRCropper' . ucfirst($this->getCropper()); 417 | $fileName = $configClass::$pathToSLIR . "/core/Libs/GD/Croppers/$cropClass.php"; 418 | $class = '\SLIR\Libs\GD\Croppers\SLIRCropper' . ucfirst($this->getCropper()); 419 | 420 | if (!file_exists($fileName)) { 421 | throw new \RuntimeException('The requested cropper could not be found: ' . $fileName); 422 | } 423 | 424 | return new $class(); 425 | } 426 | 427 | /** 428 | * Performs the actual cropping of the image 429 | * 430 | * @return SLIRImageLibrary 431 | * @since 2.0 432 | */ 433 | public function crop() 434 | { 435 | if ($this->croppingIsNeeded()) { 436 | $cropper = $this->getCropperClass(); 437 | $offset = $cropper->getCrop($this); 438 | $this->cropImage($offset['x'], $offset['y']); 439 | } 440 | 441 | return $this; 442 | } 443 | 444 | /** 445 | * Performs the actual cropping of the image 446 | * 447 | * @since 2.0 448 | * @param integer $leftOffset Number of pixels from the left side of the image to crop in 449 | * @param integer $topOffset Number of pixels from the top side of the image to crop in 450 | * @param string $fill color in hex 451 | * @return boolean 452 | */ 453 | private function cropImage($leftOffset, $topOffset) 454 | { 455 | $class = __CLASS__; 456 | $cropped = new $class(); 457 | 458 | $cropped->setMimeType($this->getMimeType()) // To enable again transparency on PNGs ! 459 | ->setWidth($this->getCropWidth()) 460 | ->setHeight($this->getCropHeight()) 461 | ->setBackground($this->getBackground()) 462 | ->setGrayscale($this->getGrayscale()); 463 | 464 | 465 | $cropped->background(); 466 | //$cropped->grayscale(); 467 | 468 | // Copy rendered image to cropped image 469 | imagecopy( 470 | $cropped->getImage(), 471 | $this->getImage(), 472 | 0, 473 | 0, 474 | $leftOffset, 475 | $topOffset, 476 | $cropped->getWidth(), 477 | $cropped->getHeight() 478 | ); 479 | 480 | // Replace pre-cropped image with cropped image 481 | $this->destroy(); 482 | $this->image = $cropped->getImage(); 483 | 484 | // Update width and height 485 | $this->info['width'] = $cropped->getWidth(); 486 | $this->info['height'] = $cropped->getHeight(); 487 | 488 | // Clean up memory 489 | unset($cropped); 490 | 491 | return $this; 492 | } 493 | 494 | /** 495 | * Sharpens the image 496 | * @return SLIRImageLibrary 497 | * @since 2.0 498 | */ 499 | public function sharpen() 500 | { 501 | if ($this->isSharpeningDesired()) { 502 | imageconvolution( 503 | $this->getImage(), 504 | $this->sharpenMatrix($this->getSharpeningFactor()), 505 | $this->getSharpeningFactor(), 506 | 0 507 | ); 508 | } 509 | 510 | return $this; 511 | } 512 | 513 | /** 514 | * @param integer $sharpness 515 | * @return array 516 | * @since 2.0 517 | */ 518 | private function sharpenMatrix($sharpness) 519 | { 520 | return array( 521 | array(-1, -2, -1), 522 | array(-2, $sharpness + 12, -2), 523 | array(-1, -2, -1) 524 | ); 525 | } 526 | 527 | /** 528 | * Determines if the image can be converted to a palette image 529 | * 530 | * @since 2.0 531 | * @return array colors in image, otherwise false if image is not palette 532 | */ 533 | private function isPalette() 534 | { 535 | $colors = array(); 536 | $image = $this->getImage(); 537 | // Loop over all of the pixels in the image, counting the colors and checking their alpha channels 538 | for ($x = 0, $width = $this->getWidth(); $x < $width; ++$x) { 539 | for ($y = 0, $height = $this->getHeight(); $y < $height; ++$y) { 540 | $color = imagecolorat($image, $x, $y); 541 | 542 | if (isset($colors[$color])) { 543 | // This color has already been checked, move on to the next pixel 544 | continue; 545 | } 546 | 547 | $colors[$color] = true; 548 | 549 | if (count($colors) > 256) { 550 | // Too many colors to convert to a palette image without losing quality 551 | return false; 552 | } 553 | 554 | // Get the alpha channel of the color 555 | $alpha = ($color & 0x7F000000) >> 24; 556 | 557 | // What is the threshold for visibility in an alpha channel? (out of 127) 558 | if ($alpha > 1 && $alpha < 126) { 559 | return false; 560 | } 561 | } 562 | } 563 | 564 | return $colors; 565 | } 566 | 567 | /** 568 | * @since 2.0 569 | * @return void 570 | * @link http://us.php.net/manual/ro/function.imagetruecolortopalette.php#44803 571 | */ 572 | private function trueColorToPalette($dither, $ncolors) 573 | { 574 | $palette = imagecreate($this->getWidth(), $this->getHeight()); 575 | 576 | imagecopy( 577 | $palette, 578 | $this->getImage(), 579 | 0, 580 | 0, 581 | 0, 582 | 0, 583 | $this->getWidth(), 584 | $this->getHeight() 585 | ); 586 | 587 | $this->destroy(); 588 | $this->image = $palette; 589 | } 590 | 591 | /** 592 | * @since 2.0 593 | * @return SLIRImage 594 | */ 595 | public function optimize() 596 | { 597 | $colors = $this->isPalette(); 598 | if ($colors !== false) { 599 | $this->trueColorToPalette(false, count($colors)); 600 | } 601 | return $this; 602 | } 603 | 604 | /** 605 | * @return string 606 | */ 607 | public function getData() 608 | { 609 | if ($this->data === null) { 610 | ob_start(); 611 | $this->output(); 612 | $this->data = ob_get_clean(); 613 | } 614 | 615 | return $this->data; 616 | } 617 | 618 | /** 619 | * Outputs the image 620 | * @return SLIRImageLibrary 621 | * @since 2.0 622 | */ 623 | public function output() 624 | { 625 | $this->render(null); 626 | return $this; 627 | } 628 | 629 | /** 630 | * Saves the image to disk 631 | * @param string $filename 632 | * @return SLIRImageLibrary 633 | * @since 2.0 634 | */ 635 | public function save() 636 | { 637 | $this->render($this->getFullPath()); 638 | return $this; 639 | } 640 | 641 | /** 642 | * @param string $path 643 | * @return boolean 644 | * @since 2.0 645 | */ 646 | private function render($path) 647 | { 648 | if ($this->isJPEG()) { 649 | return imagejpeg($this->image, $path, $this->getQuality()); 650 | } else if ($this->isPNG()) { 651 | return imagepng($this->image, $path, (integer) round(10 - ($this->getQuality() / 10))); 652 | } else if ($this->isGIF()) { 653 | return imagegif($this->image, $path); 654 | } else { 655 | return false; 656 | } 657 | } 658 | 659 | /** 660 | * Destroys the image 661 | * @return SLIRImageLibrary 662 | * @since 2.0 663 | */ 664 | public function destroy() 665 | { 666 | if ($this->image !== null) { 667 | imagedestroy($this->image); 668 | // We need to set the image to null because imagedestroy() doesn't 669 | $this->image = null; 670 | } 671 | return $this; 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /Test/core/slirrequest.class.Test.php: -------------------------------------------------------------------------------- 1 | path = 'path/to/nonexistant/image.jpg'; 21 | } 22 | 23 | /** 24 | * @test 25 | * @expectedException RuntimeException 26 | * @expectedExceptionMessage Image does not exist 27 | */ 28 | public function setPathToNonexistentImageWithNonexistentDefaultImage() 29 | { 30 | $request = new SLIRRequest(); 31 | SLIRConfig::$defaultImagePath = 'default.jpg'; 32 | $request->path = 'path/to/nonexistant/image.jpg'; 33 | SLIRConfig::$defaultImagePath = null; 34 | } 35 | 36 | /** 37 | * @test 38 | */ 39 | public function setPathToNonexistentImageWithExistentDefaultImage() 40 | { 41 | $request = new SLIRRequest(); 42 | SLIRConfig::$defaultImagePath = 'Test/images/camera-logo.png'; 43 | $request->path = 'path/to/nonexistent/image.jpg'; 44 | $this->assertSame($request->path, '/Test/images/camera-logo.png'); 45 | SLIRConfig::$defaultImagePath = null; 46 | } 47 | 48 | /** 49 | * @test 50 | * @expectedException RuntimeException 51 | */ 52 | public function setPathToImageWithDoubleDots() 53 | { 54 | $request = new SLIRRequest(); 55 | $request->path = 'path/to/../insecure/image.jpg'; 56 | } 57 | 58 | /** 59 | * @test 60 | * @expectedException RuntimeException 61 | * @expectedExceptionMessage Image path may not contain 62 | */ 63 | public function setPathToImageWithColon() 64 | { 65 | $request = new SLIRRequest(); 66 | $request->path = 'path/to/in:secure/image.jpg'; 67 | } 68 | 69 | /** 70 | * @test 71 | * @expectedException RuntimeException 72 | * @expectedExceptionMessage Image path may not contain 73 | */ 74 | public function setPathToImageWithGreaterThan() 75 | { 76 | $request = new SLIRRequest(); 77 | $request->path = 'path/to/insecure/im>age.jpg'; 78 | } 79 | 80 | /** 81 | * @test 82 | * @expectedException RuntimeException 83 | * @expectedExceptionMessage Image path may not contain 84 | */ 85 | public function setPathToImageWithLessThan() 86 | { 87 | $request = new SLIRRequest(); 88 | $request->path = 'path/to/insecure/impath = 'Test/images/camera-logo.png'; 98 | $this->assertSame($request->path, '/Test/images/camera-logo.png'); 99 | } 100 | 101 | /** 102 | * @test 103 | */ 104 | public function setHeightWithString() 105 | { 106 | $request = new SLIRRequest(); 107 | $request->height = '100'; 108 | $this->assertSame($request->height, 100); 109 | } 110 | 111 | /** 112 | * @test 113 | */ 114 | public function setHeightWithInteger() 115 | { 116 | $request = new SLIRRequest(); 117 | $request->height = 100; 118 | $this->assertSame($request->height, 100); 119 | } 120 | 121 | /** 122 | * @test 123 | */ 124 | public function setHeightWithFloatLowDecimal() 125 | { 126 | $request = new SLIRRequest(); 127 | $request->height = 100.1; 128 | $this->assertSame($request->height, 100); 129 | } 130 | 131 | /** 132 | * @test 133 | */ 134 | public function setHeightWithFloatHighDecimal() 135 | { 136 | $request = new SLIRRequest(); 137 | $request->height = 100.9; 138 | $this->assertSame($request->height, 100); 139 | } 140 | 141 | /** 142 | * @test 143 | * @expectedException RuntimeException 144 | * @expectedExceptionMessage Height must be greater than 0 145 | */ 146 | public function setHeightWithNegativeInteger() 147 | { 148 | $request = new SLIRRequest(); 149 | $request->height = -100; 150 | } 151 | 152 | /** 153 | * @test 154 | */ 155 | public function setWidthWithString() 156 | { 157 | $request = new SLIRRequest(); 158 | $request->width = '100'; 159 | $this->assertSame($request->width, 100); 160 | } 161 | 162 | /** 163 | * @test 164 | */ 165 | public function setWidthWithInteger() 166 | { 167 | $request = new SLIRRequest(); 168 | $request->width = 100; 169 | $this->assertSame($request->width, 100); 170 | } 171 | 172 | /** 173 | * @test 174 | */ 175 | public function setWidthWithFloatLowDecimal() 176 | { 177 | $request = new SLIRRequest(); 178 | $request->width = 100.1; 179 | $this->assertSame($request->width, 100); 180 | } 181 | 182 | /** 183 | * @test 184 | */ 185 | public function setWidthWithFloatHighDecimal() 186 | { 187 | $request = new SLIRRequest(); 188 | $request->width = 100.9; 189 | $this->assertSame($request->width, 100); 190 | } 191 | 192 | /** 193 | * @test 194 | * @expectedException RuntimeException 195 | * @expectedExceptionMessage Width must be greater than 0 196 | */ 197 | public function setWidthWithNegativeInteger() 198 | { 199 | $request = new SLIRRequest(); 200 | $request->width = -100; 201 | } 202 | 203 | /** 204 | * @test 205 | */ 206 | public function setQualityWithString() 207 | { 208 | $request = new SLIRRequest(); 209 | $this->assertFalse($request->isQuality()); 210 | $request->quality = '50'; 211 | $this->assertSame($request->quality, 50); 212 | $this->assertTrue($request->isQuality()); 213 | } 214 | 215 | /** 216 | * @test 217 | */ 218 | public function setQualityWithInteger() 219 | { 220 | $request = new SLIRRequest(); 221 | $this->assertFalse($request->isQuality()); 222 | $request->quality = 50; 223 | $this->assertSame($request->quality, 50); 224 | $this->assertTrue($request->isQuality()); 225 | } 226 | 227 | /** 228 | * @test 229 | */ 230 | public function setQualityWithFloatLowDecimal() 231 | { 232 | $request = new SLIRRequest(); 233 | $this->assertFalse($request->isQuality()); 234 | $request->quality = 50.1; 235 | $this->assertSame($request->quality, 50); 236 | $this->assertTrue($request->isQuality()); 237 | } 238 | 239 | /** 240 | * @test 241 | */ 242 | public function setQualityWithFloatHighDecimal() 243 | { 244 | $request = new SLIRRequest(); 245 | $this->assertFalse($request->isQuality()); 246 | $request->quality = 50.9; 247 | $this->assertSame($request->quality, 50); 248 | $this->assertTrue($request->isQuality()); 249 | } 250 | 251 | /** 252 | * @test 253 | * @expectedException RuntimeException 254 | * @expectedExceptionMessage Quality must be between 0 and 100 255 | */ 256 | public function setQualityWithNegativeInteger() 257 | { 258 | $request = new SLIRRequest(); 259 | $request->quality = -1; 260 | $this->assertFalse($request->isQuality()); 261 | } 262 | 263 | /** 264 | * @test 265 | * @expectedException RuntimeException 266 | * @expectedExceptionMessage Quality must be between 0 and 100 267 | */ 268 | public function setQualityWithIntegerAbove100() 269 | { 270 | $request = new SLIRRequest(); 271 | $request->quality = 101; 272 | $this->assertFalse($request->isQuality()); 273 | } 274 | 275 | /** 276 | * @test 277 | */ 278 | public function setProgressiveWithNumericStringOne() 279 | { 280 | $request = new SLIRRequest(); 281 | $request->progressive = '1'; 282 | $this->assertSame($request->progressive, true); 283 | } 284 | 285 | /** 286 | * @test 287 | */ 288 | public function setProgressiveWithNumericStringZero() 289 | { 290 | $request = new SLIRRequest(); 291 | $request->progressive = '0'; 292 | $this->assertSame($request->progressive, false); 293 | } 294 | 295 | /** 296 | * @test 297 | */ 298 | public function setProgressiveWithNumericStringGreaterThanOne() 299 | { 300 | $request = new SLIRRequest(); 301 | $request->progressive = '100'; 302 | $this->assertSame($request->progressive, true); 303 | } 304 | 305 | /** 306 | * @test 307 | */ 308 | public function setProgressiveWithNumericStringLessThanZero() 309 | { 310 | $request = new SLIRRequest(); 311 | $request->progressive = '-100'; 312 | $this->assertSame($request->progressive, true); 313 | } 314 | 315 | /** 316 | * @test 317 | */ 318 | public function setProgressiveWithNonNumericString() 319 | { 320 | $request = new SLIRRequest(); 321 | $request->progressive = 'test'; 322 | $this->assertSame($request->progressive, true); 323 | } 324 | 325 | /** 326 | * @test 327 | */ 328 | public function setProgressiveWithNonNumericStringFalse() 329 | { 330 | $request = new SLIRRequest(); 331 | $request->progressive = 'false'; 332 | $this->assertSame($request->progressive, true); 333 | } 334 | 335 | /** 336 | * @test 337 | */ 338 | public function setProgressiveWithEmptyString() 339 | { 340 | $request = new SLIRRequest(); 341 | $request->progressive = ''; 342 | $this->assertSame($request->progressive, false); 343 | } 344 | 345 | /** 346 | * @test 347 | */ 348 | public function setProgressiveWithIntegerOne() 349 | { 350 | $request = new SLIRRequest(); 351 | $request->progressive = 1; 352 | $this->assertSame($request->progressive, true); 353 | } 354 | 355 | /** 356 | * @test 357 | */ 358 | public function setBackgroundWithLongHexUppercase() 359 | { 360 | $request = new SLIRRequest(); 361 | $this->assertFalse($request->isBackground()); 362 | $request->background = 'BADA55'; 363 | $this->assertSame($request->background, 'BADA55'); 364 | $this->assertTrue($request->isBackground()); 365 | } 366 | 367 | /** 368 | * @test 369 | */ 370 | public function setBackgroundWithLongHexLowercase() 371 | { 372 | $request = new SLIRRequest(); 373 | $this->assertFalse($request->isBackground()); 374 | $request->background = 'bada55'; 375 | $this->assertSame($request->background, 'bada55'); 376 | $this->assertTrue($request->isBackground()); 377 | } 378 | 379 | /** 380 | * @test 381 | */ 382 | public function setBackgroundWithLongHexMixedcase() 383 | { 384 | $request = new SLIRRequest(); 385 | $this->assertFalse($request->isBackground()); 386 | $request->background = 'BadA55'; 387 | $this->assertSame($request->background, 'BadA55'); 388 | $this->assertTrue($request->isBackground()); 389 | } 390 | 391 | /** 392 | * @test 393 | */ 394 | public function setBackgroundWithShortHexUppercase() 395 | { 396 | $request = new SLIRRequest(); 397 | $this->assertFalse($request->isBackground()); 398 | $request->background = 'FA8'; 399 | $this->assertSame($request->background, 'FFAA88'); 400 | $this->assertTrue($request->isBackground()); 401 | } 402 | 403 | /** 404 | * @test 405 | */ 406 | public function setBackgroundWithShortHexLowercase() 407 | { 408 | $request = new SLIRRequest(); 409 | $this->assertFalse($request->isBackground()); 410 | $request->background = 'fa8'; 411 | $this->assertSame($request->background, 'ffaa88'); 412 | $this->assertTrue($request->isBackground()); 413 | } 414 | 415 | /** 416 | * @test 417 | */ 418 | public function setBackgroundWithShortHexMixedcase() 419 | { 420 | $request = new SLIRRequest(); 421 | $this->assertFalse($request->isBackground()); 422 | $request->background = 'Fa8'; 423 | $this->assertSame($request->background, 'FFaa88'); 424 | $this->assertTrue($request->isBackground()); 425 | } 426 | 427 | /** 428 | * @test 429 | * @expectedException RuntimeException 430 | * @expectedExceptionMessage Background fill color must be in hexadecimal format 431 | */ 432 | public function setBackgroundOneCharacter() 433 | { 434 | $request = new SLIRRequest(); 435 | $request->background = 'a'; 436 | $this->assertFalse($request->isBackground()); 437 | } 438 | 439 | /** 440 | * @test 441 | * @expectedException RuntimeException 442 | * @expectedExceptionMessage Background fill color must be in hexadecimal format 443 | */ 444 | public function setBackgroundTwoCharacters() 445 | { 446 | $request = new SLIRRequest(); 447 | $request->background = 'ef'; 448 | $this->assertFalse($request->isBackground()); 449 | } 450 | 451 | /** 452 | * @test 453 | * @expectedException RuntimeException 454 | * @expectedExceptionMessage Background fill color must be in hexadecimal format 455 | */ 456 | public function setBackgroundFourCharacters() 457 | { 458 | $request = new SLIRRequest(); 459 | $request->background = 'FACE'; 460 | $this->assertFalse($request->isBackground()); 461 | } 462 | 463 | /** 464 | * @test 465 | * @expectedException RuntimeException 466 | * @expectedExceptionMessage Background fill color must be in hexadecimal format 467 | */ 468 | public function setBackgroundFiveCharacters() 469 | { 470 | $request = new SLIRRequest(); 471 | $request->background = 'Ca5e5'; 472 | $this->assertFalse($request->isBackground()); 473 | } 474 | 475 | /** 476 | * @test 477 | * @expectedException RuntimeException 478 | * @expectedExceptionMessage Background fill color must be in hexadecimal format 479 | */ 480 | public function setBackgroundSevenCharacters() 481 | { 482 | $request = new SLIRRequest(); 483 | $request->background = '1234567'; 484 | $this->assertFalse($request->isBackground()); 485 | } 486 | 487 | /** 488 | * @test 489 | */ 490 | public function setBackgroundWithNonHexCharacters() 491 | { 492 | $request = new SLIRRequest(); 493 | $this->assertFalse($request->isBackground()); 494 | $request->background = '#BADA55'; 495 | $this->assertSame($request->background, 'BADA55'); 496 | $this->assertTrue($request->isBackground()); 497 | } 498 | 499 | /** 500 | * @test 501 | */ 502 | public function setCropRatioWithXDelimiter() 503 | { 504 | $request = new SLIRRequest(); 505 | $this->assertFalse($request->isCropping()); 506 | $request->cropRatio = '2x1'; 507 | $this->assertSame($request->cropRatio, array('width' => 2.0, 'height' => 1.0, 'ratio' => 2.0)); 508 | $this->assertTrue($request->isCropping()); 509 | } 510 | 511 | /** 512 | * @test 513 | */ 514 | public function setCropRatioWithPeriodDelimiter() 515 | { 516 | $request = new SLIRRequest(); 517 | $this->assertFalse($request->isCropping()); 518 | $request->cropRatio = '2.1'; 519 | $this->assertSame($request->cropRatio, array('width' => 2.0, 'height' => 1.0, 'ratio' => 2.0)); 520 | $this->assertTrue($request->isCropping()); 521 | } 522 | 523 | /** 524 | * @test 525 | */ 526 | public function setCropRatioWithColonDelimiter() 527 | { 528 | $request = new SLIRRequest(); 529 | $this->assertFalse($request->isCropping()); 530 | $request->cropRatio = '2:1'; 531 | $this->assertSame($request->cropRatio, array('width' => 2.0, 'height' => 1.0, 'ratio' => 2.0)); 532 | $this->assertTrue($request->isCropping()); 533 | } 534 | 535 | /** 536 | * @test 537 | */ 538 | public function setCropRatioWithCropperSameDelimiters() 539 | { 540 | $request = new SLIRRequest(); 541 | $this->assertFalse($request->isCropping()); 542 | $request->cropRatio = '2x1xsmart'; 543 | $this->assertSame($request->cropRatio, array('width' => 2.0, 'height' => 1.0, 'ratio' => 2.0)); 544 | $this->assertSame($request->cropper, 'smart'); 545 | $this->assertTrue($request->isCropping()); 546 | } 547 | 548 | /** 549 | * @test 550 | */ 551 | public function setCropRatioWithCropperMixedDelimiters() 552 | { 553 | $request = new SLIRRequest(); 554 | $this->assertFalse($request->isCropping()); 555 | $request->cropRatio = '2x1.smart'; 556 | $this->assertSame($request->cropRatio, array('width' => 2.0, 'height' => 1.0, 'ratio' => 2.0)); 557 | $this->assertSame($request->cropper, 'smart'); 558 | $this->assertTrue($request->isCropping()); 559 | } 560 | 561 | /** 562 | * @test 563 | */ 564 | public function setCropRatioWithExtraInformation() 565 | { 566 | $request = new SLIRRequest(); 567 | $this->assertFalse($request->isCropping()); 568 | $request->cropRatio = '2x1xsmartxbonusxinformation'; 569 | $this->assertSame($request->cropRatio, array('width' => 2.0, 'height' => 1.0, 'ratio' => 2.0)); 570 | $this->assertSame($request->cropper, 'smart'); 571 | $this->assertTrue($request->isCropping()); 572 | } 573 | 574 | /** 575 | * @test 576 | */ 577 | public function setCropRatioWithLargeWidth() 578 | { 579 | $request = new SLIRRequest(); 580 | $this->assertFalse($request->isCropping()); 581 | $request->cropRatio = '2000000x1'; 582 | $this->assertSame($request->cropRatio, array('width' => 2000000.0, 'height' => 1.0, 'ratio' => 2000000.0)); 583 | $this->assertTrue($request->isCropping()); 584 | } 585 | 586 | /** 587 | * @test 588 | */ 589 | public function setCropRatioWithLargeHeight() 590 | { 591 | $request = new SLIRRequest(); 592 | $this->assertFalse($request->isCropping()); 593 | $request->cropRatio = '1x2000000'; 594 | $this->assertSame($request->cropRatio, array('width' => 1.0, 'height' => 2000000.0, 'ratio' => 0.0000005)); 595 | $this->assertTrue($request->isCropping()); 596 | } 597 | 598 | /** 599 | * @test 600 | * @expectedException RuntimeException 601 | * @expectedExceptionMessage Crop ratio must not contain a zero 602 | */ 603 | public function setCropRatioWithZeroWidth() 604 | { 605 | $request = new SLIRRequest(); 606 | $request->cropRatio = '100x0'; 607 | $this->assertFalse($request->isCropping()); 608 | } 609 | 610 | /** 611 | * @test 612 | * @expectedException RuntimeException 613 | * @expectedExceptionMessage Crop ratio must not contain a zero 614 | */ 615 | public function setCropRatioWithZeroHeight() 616 | { 617 | $request = new SLIRRequest(); 618 | $request->cropRatio = '0x100'; 619 | $this->assertFalse($request->isCropping()); 620 | } 621 | 622 | /** 623 | * @test 624 | * @expectedException RuntimeException 625 | * @expectedExceptionMessage Crop ratio must be in [width]x[height] format 626 | */ 627 | public function setCropRatioWithNoHeight() 628 | { 629 | $request = new SLIRRequest(); 630 | $request->cropRatio = '100'; 631 | $this->assertFalse($request->isCropping()); 632 | } 633 | 634 | /** 635 | * @test 636 | * @expectedException RuntimeException 637 | * @expectedExceptionMessage Source image was not specified 638 | */ 639 | public function initializeNoImage() 640 | { 641 | $request = new SLIRRequest(); 642 | 643 | $_SERVER['REQUEST_URI'] = '/slir/w100/'; 644 | $request->initialize(); 645 | } 646 | 647 | /** 648 | * @test 649 | * @expectedException RuntimeException 650 | * @expectedExceptionMessage Not enough parameters were given 651 | */ 652 | public function initializeNoParameters() 653 | { 654 | $request = new SLIRRequest(); 655 | 656 | $_SERVER['REQUEST_URI'] = '/'; 657 | $request->initialize(); 658 | } 659 | 660 | /** 661 | * @test 662 | * @expectedException RuntimeException 663 | * @expectedExceptionMessage Image does not exist 664 | */ 665 | public function initializeNonexistentImage() 666 | { 667 | $request = new SLIRRequest(); 668 | 669 | $_SERVER['REQUEST_URI'] = '/slir/w100/path/to/nonexistent/image.jpg'; 670 | $request->initialize(); 671 | } 672 | 673 | /** 674 | * @test 675 | */ 676 | public function initializeExistentImageOnlyWidth() 677 | { 678 | $request = new SLIRRequest(); 679 | 680 | $_SERVER['REQUEST_URI'] = '/slir/w100/Test/images/camera-logo.png'; 681 | $request->initialize(); 682 | 683 | $this->assertSame($request->width, 100); 684 | $this->assertSame($request->path, '/Test/images/camera-logo.png'); 685 | } 686 | 687 | /** 688 | * @test 689 | */ 690 | public function initializeLotsOfParameters() 691 | { 692 | $request = new SLIRRequest(); 693 | 694 | $_SERVER['REQUEST_URI'] = '/slir/w100-h101-c2.1-q15-p1/Test/images/camera-logo.png'; 695 | $request->initialize(); 696 | 697 | $this->assertSame($request->width, 100); 698 | $this->assertSame($request->height, 101); 699 | $this->assertSame($request->cropRatio, array('width' => 2.0, 'height' => 1.0, 'ratio' => 2.0)); 700 | $this->assertSame($request->quality, 15); 701 | $this->assertSame($request->progressive, true); 702 | $this->assertSame($request->path, '/Test/images/camera-logo.png'); 703 | } 704 | 705 | /** 706 | * @test 707 | */ 708 | public function initializeDefaultImageOnlyWidth() 709 | { 710 | $request = new SLIRRequest(); 711 | 712 | SLIRConfig::$defaultImagePath = '/Test/images/camera-logo.png'; 713 | 714 | $_SERVER['REQUEST_URI'] = '/slir/w100/path/to/nonexistent/image.png'; 715 | $request->initialize(); 716 | 717 | $this->assertSame($request->width, 100); 718 | $this->assertSame($request->path, '/Test/images/camera-logo.png'); 719 | 720 | SLIRConfig::$defaultImagePath = null; 721 | } 722 | 723 | /** 724 | * @test 725 | */ 726 | public function initializeUnspecifiedImageWithDefaultImageOnlyWidth() 727 | { 728 | $request = new SLIRRequest(); 729 | 730 | SLIRConfig::$defaultImagePath = '/Test/images/camera-logo.png'; 731 | 732 | $_SERVER['REQUEST_URI'] = '/slir/w100/'; 733 | $request->initialize(); 734 | 735 | $this->assertSame($request->width, 100); 736 | $this->assertSame($request->path, '/Test/images/camera-logo.png'); 737 | 738 | SLIRConfig::$defaultImagePath = null; 739 | } 740 | 741 | /** 742 | * @test 743 | * @expectedException RuntimeException 744 | * @expectedExceptionMessage Source image was not specified 745 | */ 746 | public function initializeForceQueryStringNoParameters() 747 | { 748 | SLIRConfig::$forceQueryString = true; 749 | $request = new SLIRRequest(); 750 | 751 | // This should have no effect 752 | $_SERVER['REQUEST_URI'] = '/slir/w100/Test/images/camera-logo.png'; 753 | $request->initialize(); 754 | 755 | SLIRConfig::$forceQueryString = false; 756 | } 757 | 758 | /** 759 | * @test 760 | */ 761 | public function initializeForceQueryStringWidthAndImage() 762 | { 763 | SLIRConfig::$forceQueryString = true; 764 | $request = new SLIRRequest(); 765 | 766 | // This should have no effect 767 | $_SERVER['REQUEST_URI'] = '/slir/w100/nonexistent/image.png'; 768 | 769 | $_GET = array(); 770 | $_GET['i'] = '/Test/images/camera-logo.png'; 771 | $_GET['w'] = '100'; 772 | 773 | $request->initialize(); 774 | 775 | $this->assertSame($request->path, $_GET['i']); 776 | $this->assertSame($request->width, 100); 777 | 778 | SLIRConfig::$forceQueryString = false; 779 | } 780 | 781 | /** 782 | * @test 783 | */ 784 | public function initializeUnforcedQueryStringWidthAndImage() 785 | { 786 | SLIRConfig::$forceQueryString = false; 787 | $request = new SLIRRequest(); 788 | 789 | $_SERVER['QUERY_STRING'] = 'i=/Test/images/camera-logo.png&w=100'; 790 | $_GET = array(); 791 | $_GET['i'] = '/Test/images/camera-logo.png'; 792 | $_GET['w'] = '100'; 793 | 794 | $request->initialize(); 795 | 796 | $this->assertSame($request->path, $_GET['i']); 797 | $this->assertSame($request->width, 100); 798 | } 799 | 800 | /** 801 | * @test 802 | */ 803 | public function destruct() 804 | { 805 | $request = new SLIRRequest(); 806 | $request->__destruct(); 807 | 808 | $this->assertFalse(isset($request->path)); 809 | $this->assertFalse(isset($request->width)); 810 | $this->assertFalse(isset($request->height)); 811 | $this->assertFalse(isset($request->cropRatio)); 812 | $this->assertFalse(isset($request->cropper)); 813 | $this->assertFalse(isset($request->quality)); 814 | $this->assertFalse(isset($request->progressive)); 815 | $this->assertFalse(isset($request->background)); 816 | $this->assertFalse(isset($request->isUsingDefaultImagePath)); 817 | $this->assertTrue(isset($request)); 818 | } 819 | 820 | } 821 | -------------------------------------------------------------------------------- /core/Libs/GD/Croppers/SLIRCropperSmart.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | * 27 | * @copyright Copyright © 2014, Joe Lencioni 28 | * @license MIT 29 | * @since 2.0 30 | * @package SLIR 31 | * @subpackage Croppers 32 | */ 33 | 34 | namespace SLIR\Libs\GD\Croppers; 35 | 36 | 37 | 38 | use \SLIR\Libs\SLIRImage; 39 | /** 40 | * Smart SLIR cropper 41 | * 42 | * @since 2.0 43 | * @author Joe Lencioni 44 | * @package SLIR 45 | * @subpackage Croppers 46 | */ 47 | class SLIRCropperSmart implements SLIRCropper 48 | { 49 | const OFFSET_NEAR = 0; 50 | const OFFSET_FAR = 1; 51 | 52 | const PIXEL_LAB = 0; 53 | const PIXEL_DELTA_E = 1; 54 | const PIXEL_INTERESTINGNESS = 2; 55 | 56 | const RGB_RED = 0; 57 | const RGB_GREEN = 1; 58 | const RGB_BLUE = 2; 59 | 60 | const XYZ_X = 0; 61 | const XYZ_Y = 1; 62 | const XYZ_Z = 2; 63 | 64 | const LAB_L = 0; 65 | const LAB_A = 1; 66 | const LAB_B = 2; 67 | 68 | /** 69 | * @var array 70 | */ 71 | private $colors; 72 | 73 | /** 74 | * Destruct method. Try to clean up memory a little. 75 | * 76 | * @return void 77 | * @since 2.0 78 | */ 79 | public function __destruct() 80 | { 81 | unset($this->colors); 82 | } 83 | 84 | /** 85 | * Determines if the top and bottom need to be cropped 86 | * 87 | * @since 2.0 88 | * @param SLIRImage $image 89 | * @return boolean 90 | */ 91 | private function shouldCropTopAndBottom(SLIRImage $image) 92 | { 93 | if ($image->getCropRatio() > $image->getRatio()) { 94 | return true; 95 | } else { 96 | return false; 97 | } 98 | } 99 | 100 | /** 101 | * Determines the optimal number of rows in from the top or left to crop 102 | * the source image 103 | * 104 | * @since 2.0 105 | * @param SLIRImage $image 106 | * @return integer|boolean 107 | */ 108 | private function cropSmartOffsetRows(SLIRImage $image) 109 | { 110 | // @todo Change this method to resize image, determine offset, and then extrapolate the actual offset based on the image size difference. Then we can cache the offset in APC (all just like we are doing for face detection) 111 | 112 | if ($this->shouldCropTopAndBottom($image)) { 113 | $length = $image->getCropHeight(); 114 | $lengthB = $image->getCropWidth(); 115 | $originalLength = $image->getHeight(); 116 | } else { 117 | $length = $image->getCropWidth(); 118 | $lengthB = $image->getCropHeight(); 119 | $originalLength = $image->getWidth(); 120 | } 121 | 122 | // To smart crop an image, we need to calculate the difference between 123 | // each pixel in each row and its adjacent pixels. Add these up to 124 | // determine how interesting each row is. Based on how interesting each 125 | // row is, we can determine whether or not to discard it. We start with 126 | // the closest row and the farthest row and then move on from there. 127 | 128 | // All colors in the image will be stored in the colors array. 129 | // This array will also include information about each pixel's 130 | // interestingness. 131 | // 132 | // For example (rough representation): 133 | // 134 | // $this->colors = array( 135 | // x1 => array( 136 | // x1y1 => array( 137 | // self::PIXEL_LAB => array(l, a, b), 138 | // self::PIXEL_DELTA_E => array(TL, TC, TR, LC, LR, BL, BC, BR), 139 | // self::PIXEL_INTERESTINGNESS => computedInterestingness 140 | // ), 141 | // x1y2 => array( ... ), 142 | // ... 143 | // ), 144 | // x2 => array( ... ), 145 | // ... 146 | // ); 147 | $this->colors = array(); 148 | 149 | // Offset will remember how far in from each side we are in the 150 | // cropping game 151 | $offset = array( 152 | self::OFFSET_NEAR => 0, 153 | self::OFFSET_FAR => 0, 154 | ); 155 | 156 | $rowsToCrop = $originalLength - $length; 157 | 158 | // $pixelStep will sacrifice accuracy for memory and speed. Essentially 159 | // it acts as a spot-checker and scales with the size of the cropped area 160 | $pixelStep = round(sqrt($rowsToCrop * $lengthB) / 10); 161 | 162 | // We won't save much speed if the pixelStep is between 4 and 1 because 163 | // we still need to sample adjacent pixels 164 | if ($pixelStep < 4) { 165 | $pixelStep = 1; 166 | } 167 | 168 | $tolerance = 0.5; 169 | $upperTol = 1 + $tolerance; 170 | $lowerTol = 1 / $upperTol; 171 | 172 | // Fight the near and far rows. The stronger will remain standing. 173 | $returningChampion = null; 174 | $ratio = 1; 175 | 176 | for ($rowsCropped = 0; $rowsCropped < $rowsToCrop; ++$rowsCropped) { 177 | $a = $this->rowInterestingness($image, $offset[self::OFFSET_NEAR], $pixelStep, $originalLength); 178 | $b = $this->rowInterestingness($image, $originalLength - $offset[self::OFFSET_FAR] - 1, $pixelStep, $originalLength); 179 | 180 | if ($a == 0 && $b == 0) { 181 | $ratio = 1; 182 | } else if ($b == 0) { 183 | $ratio = 1 + $a; 184 | } else { 185 | $ratio = $a / $b; 186 | } 187 | 188 | if ($ratio > $upperTol) { 189 | ++$offset[self::OFFSET_FAR]; 190 | 191 | // Fightback. Winning side gets to go backwards through fallen rows 192 | // to see if they are stronger 193 | if ($returningChampion == self::OFFSET_NEAR) { 194 | $offset[self::OFFSET_NEAR] -= ($offset[self::OFFSET_NEAR] > 0) ? 1 : 0; 195 | } else { 196 | $returningChampion = self::OFFSET_NEAR; 197 | } 198 | } else if ($ratio < $lowerTol) { 199 | ++$offset[self::OFFSET_NEAR]; 200 | 201 | if ($returningChampion == self::OFFSET_FAR) { 202 | $offset[self::OFFSET_FAR] -= ($offset[self::OFFSET_FAR] > 0) ? 1 : 0; 203 | } else { 204 | $returningChampion = self::OFFSET_FAR; 205 | } 206 | } else { 207 | // There is no strong winner, so discard rows from the side that 208 | // has lost the fewest so far. Essentially this is a draw. 209 | if ($offset[self::OFFSET_NEAR] > $offset[self::OFFSET_FAR]) { 210 | ++$offset[self::OFFSET_FAR]; 211 | } else { 212 | // Discard near 213 | ++$offset[self::OFFSET_NEAR]; 214 | } 215 | 216 | // No fightback for draws 217 | $returningChampion = null; 218 | } // if 219 | 220 | } // for 221 | 222 | // Bounceback for potentially important details on the edge. 223 | // This may possibly be better if the winning side fights a hard final 224 | // push multiple-rows-at-stake battle where it stands the chance to gain 225 | // ground. 226 | if ($ratio > (1 + ($tolerance * 1.25))) { 227 | $offset[self::OFFSET_NEAR] -= round($length * .03); 228 | } else if ($ratio < (1 / (1 + ($tolerance * 1.25)))) { 229 | $offset[self::OFFSET_NEAR] += round($length * .03); 230 | } 231 | 232 | return min($rowsToCrop, max(0, $offset[self::OFFSET_NEAR])); 233 | } 234 | 235 | /** 236 | * Calculate the interestingness value of a row of pixels 237 | * 238 | * @since 2.0 239 | * @param SLIRImage $image 240 | * @param integer $row 241 | * @param integer $pixelStep Number of pixels to jump after each step when comparing interestingness 242 | * @param integer $originalLength Number of rows in the original image 243 | * @return float 244 | */ 245 | private function rowInterestingness(SLIRImage $image, $row, $pixelStep, $originalLength) 246 | { 247 | $interestingness = 0; 248 | $max = 0; 249 | 250 | if ($this->shouldCropTopAndBottom($image)) { 251 | for ($totalPixels = 0; $totalPixels < $image->getWidth(); $totalPixels += $pixelStep) { 252 | $i = $this->pixelInterestingness($image, $totalPixels, $row); 253 | 254 | // Content at the very edge of an image tends to be less interesting than 255 | // content toward the center, so we give it a little extra push away from the edge 256 | //$i += min($row, $originalLength - $row, $originalLength * .04); 257 | 258 | $max = max($i, $max); 259 | $interestingness += $i; 260 | } 261 | } else { 262 | for ($totalPixels = 0; $totalPixels < $image->getHeight(); $totalPixels += $pixelStep) { 263 | $i = $this->pixelInterestingness($image, $row, $totalPixels); 264 | 265 | // Content at the very edge of an image tends to be less interesting than 266 | // content toward the center, so we give it a little extra push away from the edge 267 | //$i += min($row, $originalLength - $row, $originalLength * .04); 268 | 269 | $max = max($i, $max); 270 | $interestingness += $i; 271 | } 272 | } 273 | 274 | return $interestingness + (($max - ($interestingness / ($totalPixels / $pixelStep))) * ($totalPixels / $pixelStep)); 275 | } 276 | 277 | /** 278 | * Get the interestingness value of a pixel 279 | * 280 | * @since 2.0 281 | * @param SLIRImage $image 282 | * @param integer $x x-axis position of pixel to calculate 283 | * @param integer $y y-axis position of pixel to calculate 284 | * @return float 285 | */ 286 | private function pixelInterestingness(SLIRImage $image, $x, $y) 287 | { 288 | if (!isset($this->colors[$x][$y][self::PIXEL_INTERESTINGNESS])) { 289 | // Ensure this pixel's color information has already been loaded 290 | $this->loadPixelInfo($image, $x, $y); 291 | 292 | // Calculate each neighboring pixel's Delta E in relation to this 293 | // pixel 294 | $this->calculateDeltas($image, $x, $y); 295 | 296 | // Calculate the interestingness of this pixel based on neighboring 297 | // pixels' Delta E in relation to this pixel 298 | $this->calculateInterestingness($x, $y); 299 | } // if 300 | 301 | return $this->colors[$x][$y][self::PIXEL_INTERESTINGNESS]; 302 | } 303 | 304 | /** 305 | * Load the color information of the requested pixel into the $colors array 306 | * 307 | * @since 2.0 308 | * @param SLIRImage $image 309 | * @param integer $x x-axis position of pixel to calculate 310 | * @param integer $y y-axis position of pixel to calculate 311 | * @return boolean 312 | */ 313 | private function loadPixelInfo(SLIRImage $image, $x, $y) 314 | { 315 | if ($x < 0 || $x >= $image->getWidth() || $y < 0 || $y >= $image->getHeight()) { 316 | return false; 317 | } 318 | 319 | if (!isset($this->colors[$x])) { 320 | $this->colors[$x] = array(); 321 | } 322 | 323 | if (!isset($this->colors[$x][$y])) { 324 | $this->colors[$x][$y] = array(); 325 | } 326 | 327 | if (!isset($this->colors[$x][$y][self::PIXEL_INTERESTINGNESS]) && !isset($this->colors[$x][$y][self::PIXEL_LAB])) { 328 | $this->colors[$x][$y][self::PIXEL_LAB] = $this->evaluateColor(imagecolorat($image->getImage(), $x, $y)); 329 | } 330 | 331 | return true; 332 | } 333 | 334 | /** 335 | * Calculates each adjacent pixel's Delta E in relation to the pixel requested 336 | * 337 | * @since 2.0 338 | * @param SLIRImage $image 339 | * @param integer $x x-axis position of pixel to calculate 340 | * @param integer $y y-axis position of pixel to calculate 341 | * @return boolean 342 | */ 343 | private function calculateDeltas(SLIRImage $image, $x, $y) 344 | { 345 | // Calculate each adjacent pixel's Delta E in relation to the current 346 | // pixel (top left, top center, top right, center left, center right, 347 | // bottom left, bottom center, and bottom right) 348 | 349 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d-1-1'])) { 350 | $this->calculateDelta($image, $x, $y, -1, -1); 351 | } 352 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d0-1'])) { 353 | $this->calculateDelta($image, $x, $y, 0, -1); 354 | } 355 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d1-1'])) { 356 | $this->calculateDelta($image, $x, $y, 1, -1); 357 | } 358 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d-10'])) { 359 | $this->calculateDelta($image, $x, $y, -1, 0); 360 | } 361 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d10'])) { 362 | $this->calculateDelta($image, $x, $y, 1, 0); 363 | } 364 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d-11'])) { 365 | $this->calculateDelta($image, $x, $y, -1, 1); 366 | } 367 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d01'])) { 368 | $this->calculateDelta($image, $x, $y, 0, 1); 369 | } 370 | if (!isset($this->colors[$x][$y][self::PIXEL_DELTA_E]['d11'])) { 371 | $this->calculateDelta($image, $x, $y, 1, 1); 372 | } 373 | 374 | return true; 375 | } 376 | 377 | /** 378 | * Calculates and stores requested pixel's Delta E in relation to comparison pixel 379 | * 380 | * @since 2.0 381 | * @param SLIRImage $image 382 | * @param integer $xA x-axis position of pixel to calculate 383 | * @param integer $yA y-axis position of pixel to calculate 384 | * @param integer $xMove number of pixels to move on the x-axis to find comparison pixel 385 | * @param integer $yMove number of pixels to move on the y-axis to find comparison pixel 386 | * @return boolean 387 | */ 388 | private function calculateDelta(SLIRImage $image, $xA, $yA, $xMove, $yMove) 389 | { 390 | $xB = $xA + $xMove; 391 | $yB = $yA + $yMove; 392 | 393 | // Pixel is outside of the image, so we cant't calculate the Delta E 394 | if ($xB < 0 || $xB >= $image->getWidth() || $yB < 0 || $yB >= $image->getHeight()) { 395 | return null; 396 | } 397 | 398 | if (!isset($this->colors[$xA][$yA][self::PIXEL_LAB])) { 399 | $this->loadPixelInfo($image, $xA, $yA); 400 | } 401 | 402 | if (!isset($this->colors[$xB][$yB][self::PIXEL_LAB])) { 403 | $this->loadPixelInfo($image, $xB, $yB); 404 | } 405 | 406 | $delta = $this->deltaE($this->colors[$xA][$yA][self::PIXEL_LAB], $this->colors[$xB][$yB][self::PIXEL_LAB]); 407 | 408 | $this->colors[$xA][$yA][self::PIXEL_DELTA_E]["d$xMove$yMove"] = $delta; 409 | 410 | $xBMove = $xMove * -1; 411 | $yBMove = $yMove * -1; 412 | $this->colors[$xB][$yB][self::PIXEL_DELTA_E]["d$xBMove$yBMove"] =& $this->colors[$xA][$yA][self::PIXEL_DELTA_E]["d$xMove$yMove"]; 413 | 414 | return true; 415 | } 416 | 417 | /** 418 | * Calculates and stores a pixel's overall interestingness value 419 | * 420 | * @since 2.0 421 | * @param integer $x x-axis position of pixel to calculate 422 | * @param integer $y y-axis position of pixel to calculate 423 | * @return boolean 424 | */ 425 | private function calculateInterestingness($x, $y) 426 | { 427 | // The interestingness is the average of the pixel's Delta E values 428 | $this->colors[$x][$y][self::PIXEL_INTERESTINGNESS] = array_sum($this->colors[$x][$y][self::PIXEL_DELTA_E]) 429 | / count(array_filter($this->colors[$x][$y][self::PIXEL_DELTA_E], 'is_numeric')); 430 | 431 | return true; 432 | } 433 | 434 | /** 435 | * @since 2.0 436 | * @param integer $int 437 | * @return array 438 | */ 439 | private function evaluateColor($int) 440 | { 441 | $rgb = $this->colorIndexToRGB($int); 442 | $xyz = $this->RGBtoXYZ($rgb); 443 | $lab = $this->XYZtoHunterLab($xyz); 444 | 445 | return $lab; 446 | } 447 | 448 | /** 449 | * @since 2.0 450 | * @param integer $int 451 | * @return array 452 | */ 453 | private function colorIndexToRGB($int) 454 | { 455 | $a = (255 - (($int >> 24) & 0xFF)) / 255; 456 | $r = (($int >> 16) & 0xFF) * $a; 457 | $g = (($int >> 8) & 0xFF) * $a; 458 | $b = ($int & 0xFF) * $a; 459 | 460 | return array( 461 | self::RGB_RED => $r, 462 | self::RGB_GREEN => $g, 463 | self::RGB_BLUE => $b, 464 | ); 465 | } 466 | 467 | /** 468 | * @since 2.0 469 | * @param array $rgb 470 | * @return array XYZ 471 | * @link http://easyrgb.com/index.php?X=MATH&H=02#text2 472 | */ 473 | private function RGBtoXYZ($rgb) 474 | { 475 | $r = $rgb[self::RGB_RED] / 255; 476 | $g = $rgb[self::RGB_GREEN] / 255; 477 | $b = $rgb[self::RGB_BLUE] / 255; 478 | 479 | if ($r > 0.04045) { 480 | $r = pow((($r + 0.055) / 1.055), 2.4); 481 | } else { 482 | $r = $r / 12.92; 483 | } 484 | 485 | if ($g > 0.04045) { 486 | $g = pow((($g + 0.055) / 1.055), 2.4); 487 | } else { 488 | $g = $g / 12.92; 489 | } 490 | 491 | if ($b > 0.04045) { 492 | $b = pow((($b + 0.055) / 1.055), 2.4); 493 | } else { 494 | $b = $b / 12.92; 495 | } 496 | 497 | $r *= 100; 498 | $g *= 100; 499 | $b *= 100; 500 | 501 | //Observer. = 2°, Illuminant = D65 502 | return array( 503 | self::XYZ_X => $r * 0.4124 + $g * 0.3576 + $b * 0.1805, 504 | self::XYZ_Y => $r * 0.2126 + $g * 0.7152 + $b * 0.0722, 505 | self::XYZ_Z => $r * 0.0193 + $g * 0.1192 + $b * 0.9505, 506 | ); 507 | } 508 | 509 | /** 510 | * @link http://www.easyrgb.com/index.php?X=MATH&H=05#text5 511 | */ 512 | private function XYZtoHunterLab($xyz) 513 | { 514 | if ($xyz[self::XYZ_Y] == 0) { 515 | return array( 516 | self::LAB_L => 0, 517 | self::LAB_A => 0, 518 | self::LAB_B => 0, 519 | ); 520 | } 521 | 522 | return array( 523 | self::LAB_L => 10 * sqrt($xyz[self::XYZ_Y]), 524 | self::LAB_A => 17.5 * (((1.02 * $xyz[self::XYZ_X]) - $xyz[self::XYZ_Y]) / sqrt($xyz[self::XYZ_Y])), 525 | self::LAB_B => 7 * (($xyz[self::XYZ_Y] - (0.847 * $xyz[self::XYZ_Z])) / sqrt($xyz[self::XYZ_Y])), 526 | ); 527 | } 528 | 529 | /** 530 | * Converts a color from RGB colorspace to CIE-L*ab colorspace 531 | * @since 2.0 532 | * @param array $xyz 533 | * @return array LAB 534 | * @link http://www.easyrgb.com/index.php?X=MATH&H=05#text5 535 | */ 536 | private function XYZtoCIELAB($xyz) 537 | { 538 | $refX = 100; 539 | $refY = 100; 540 | $refZ = 100; 541 | 542 | $x = $xyz[self::XYZ_X] / $refX; 543 | $y = $xyz[self::XYZ_Y] / $refY; 544 | $z = $xyz[self::XYZ_Z] / $refZ; 545 | 546 | if ($x > 0.008856) { 547 | $x = pow($x, 1/3); 548 | } else { 549 | $x = (7.787 * $x) + (16 / 116); 550 | } 551 | 552 | if ($y > 0.008856) { 553 | $y = pow($y, 1/3); 554 | } else { 555 | $y = (7.787 * $y) + (16 / 116); 556 | } 557 | 558 | if ($z > 0.008856) { 559 | $z = pow($z, 1/3); 560 | } else { 561 | $z = (7.787 * $z) + (16 / 116); 562 | } 563 | 564 | return array( 565 | self::LAB_L => (116 * $y) - 16, 566 | self::LAB_A => 500 * ($x - $y), 567 | self::LAB_B => 200 * ($y - $z), 568 | ); 569 | } 570 | 571 | /** 572 | * @since 2.0 573 | * @param array $labA LAB color array 574 | * @param array $labB LAB color array 575 | * @return float 576 | */ 577 | private function deltaE($labA, $labB) 578 | { 579 | return sqrt( 580 | (pow($labA[self::LAB_L] - $labB[self::LAB_L], 2)) 581 | + (pow($labA[self::LAB_A] - $labB[self::LAB_A], 2)) 582 | + (pow($labA[self::LAB_B] - $labB[self::LAB_B], 2)) 583 | ); 584 | } 585 | 586 | /** 587 | * Compute the Delta E 2000 value of two colors in the LAB colorspace 588 | * 589 | * @link http://en.wikipedia.org/wiki/Color_difference#CIEDE2000 590 | * @link http://easyrgb.com/index.php?X=DELT&H=05#text5 591 | * @since 2.0 592 | * @param array $labA LAB color array 593 | * @param array $labB LAB color array 594 | * @return float 595 | */ 596 | private function deltaE2000($labA, $labB) 597 | { 598 | $weightL = 1; // Lightness 599 | $weightC = 1; // Chroma 600 | $weightH = 1; // Hue 601 | 602 | $xCA = sqrt($labA[self::LAB_A] * $labA[self::LAB_A] + $labA[self::LAB_B] * $labA[self::LAB_B]); 603 | $xCB = sqrt($labB[self::LAB_A] * $labB[self::LAB_A] + $labB[self::LAB_B] * $labB[self::LAB_B]); 604 | $xCX = ($xCA + $xCB) / 2; 605 | $xGX = 0.5 * (1 - sqrt((pow($xCX, 7)) / ((pow($xCX, 7)) + (pow(25, 7))))); 606 | $xNN = (1 + $xGX) * $labA[self::LAB_A]; 607 | $xCA = sqrt($xNN * $xNN + $labA[self::LAB_B] * $labA[self::LAB_B]); 608 | $xHA = $this->LABtoHue($xNN, $labA[self::LAB_B]); 609 | $xNN = (1 + $xGX) * $labB[self::LAB_A]; 610 | $xCB = sqrt($xNN * $xNN + $labB[self::LAB_B] * $labB[self::LAB_B]); 611 | $xHB = $this->LABtoHue($xNN, $labB[self::LAB_B]); 612 | $xDL = $labB[self::LAB_L] - $labA[self::LAB_L]; 613 | $xDC = $xCB - $xCA; 614 | 615 | if (($xCA * $xCB) == 0) { 616 | $xDH = 0; 617 | } else { 618 | $xNN = round($xHB - $xHA, 12); 619 | if (abs($xNN) <= 180) { 620 | $xDH = $xHB - $xHA; 621 | } else { 622 | if ($xNN > 180) { 623 | $xDH = $xHB - $xHA - 360; 624 | } else { 625 | $xDH = $xHB - $xHA + 360; 626 | } 627 | } // if 628 | } // if 629 | 630 | $xDH = 2 * sqrt($xCA * $xCB) * sin(rad2deg($xDH / 2)); 631 | $xLX = ($labA[self::LAB_L] + $labB[self::LAB_L]) / 2; 632 | $xCY = ($xCA + $xCB) / 2; 633 | 634 | if (($xCA * $xCB) == 0) { 635 | $xHX = $xHA + $xHB; 636 | } else { 637 | $xNN = abs(round($xHA - $xHB, 12)); 638 | if ($xNN > 180) { 639 | if (($xHB + $xHA) < 360) { 640 | $xHX = $xHA + $xHB + 360; 641 | } else { 642 | $xHX = $xHA + $xHB - 360; 643 | } 644 | } else { 645 | $xHX = $xHA + $xHB; 646 | } // if 647 | $xHX /= 2; 648 | } // if 649 | 650 | $xTX = 1 - 0.17 * cos(rad2deg($xHX - 30)) 651 | + 0.24 * cos(rad2deg(2 * $xHX)) 652 | + 0.32 * cos(rad2deg(3 * $xHX + 6)) 653 | - 0.20 * cos(rad2deg(4 * $xHX - 63)); 654 | 655 | $xPH = 30 * exp(- (($xHX - 275) / 25) * (($xHX - 275) / 25)); 656 | $xRC = 2 * sqrt((pow($xCY, 7)) / ((pow($xCY, 7)) + (pow(25, 7)))); 657 | $xSL = 1 + ((0.015 * (($xLX - 50) * ($xLX - 50))) 658 | / sqrt(20 + (($xLX - 50) * ($xLX - 50)))); 659 | $xSC = 1 + 0.045 * $xCY; 660 | $xSH = 1 + 0.015 * $xCY * $xTX; 661 | $xRT = - sin(rad2deg(2 * $xPH)) * $xRC; 662 | $xDL = $xDL / $weightL * $xSL; 663 | $xDC = $xDC / $weightC * $xSC; 664 | $xDH = $xDH / $weightH * $xSH; 665 | 666 | $delta = sqrt(pow($xDL, 2) + pow($xDC, 2) + pow($xDH, 2) + $xRT * $xDC * $xDH); 667 | return (is_nan($delta)) ? 1 : $delta / 100; 668 | } 669 | 670 | /** 671 | * Compute the Delta CMC value of two colors in the LAB colorspace 672 | * 673 | * @since 2.0 674 | * @param array $labA LAB color array 675 | * @param array $labB LAB color array 676 | * @return float 677 | * @link http://easyrgb.com/index.php?X=DELT&H=06#text6 678 | */ 679 | private function deltaCMC($labA, $labB) 680 | { 681 | // if $weightL is 2 and $weightC is 1, it means that the lightness 682 | // will contribute half as much importance to the delta as the chroma 683 | $weightL = 2; // Lightness 684 | $weightC = 1; // Chroma 685 | 686 | $xCA = sqrt((pow($labA[self::LAB_A], 2)) + (pow($labA[self::LAB_B], 2))); 687 | $xCB = sqrt((pow($labB[self::LAB_A], 2)) + (pow($labB[self::LAB_B], 2))); 688 | $xff = sqrt((pow($xCA, 4)) / ((pow($xCA, 4)) + 1900)); 689 | $xHA = $this->LABtoHue($labA[self::LAB_A], $labA[self::LAB_B]); 690 | 691 | if ($xHA < 164 || $xHA > 345) { 692 | $xTT = 0.36 + abs(0.4 * cos(deg2rad(35 + $xHA))); 693 | } else { 694 | $xTT = 0.56 + abs(0.2 * cos(deg2rad(168 + $xHA))); 695 | } 696 | 697 | if ($labA[self::LAB_L] < 16) { 698 | $xSL = 0.511; 699 | } else { 700 | $xSL = (0.040975 * $labA[self::LAB_L]) / (1 + (0.01765 * $labA[self::LAB_L])); 701 | } 702 | 703 | $xSC = ((0.0638 * $xCA) / (1 + (0.0131 * $xCA))) + 0.638; 704 | $xSH = (($xff * $xTT) + 1 - $xff) * $xSC; 705 | $xDH = sqrt(pow($labB[self::LAB_A] - $labA[self::LAB_A], 2) + pow($labB[self::LAB_B] - $labA[self::LAB_B], 2) - pow($xCB - $xCA, 2)); 706 | $xSL = ($labB[self::LAB_L] - $labA[self::LAB_L]) / $weightL * $xSL; 707 | $xSC = ($xCB - $xCA) / $weightC * $xSC; 708 | $xSH = $xDH / $xSH; 709 | 710 | $delta = sqrt(pow($xSL, 2) + pow($xSC, 2) + pow($xSH, 2)); 711 | return (is_nan($delta)) ? 1 : $delta; 712 | } 713 | 714 | /** 715 | * @since 2.0 716 | * @param integer $a 717 | * @param integer $b 718 | * @return CIE-H° value 719 | */ 720 | private function LABtoHue($a, $b) 721 | { 722 | $bias = 0; 723 | 724 | if ($a >= 0 && $b == 0) { 725 | return 0; 726 | } 727 | if ($a < 0 && $b == 0) { 728 | return 180; 729 | } 730 | if ($a == 0 && $b > 0) { 731 | return 90; 732 | } 733 | if ($a == 0 && $b < 0) { 734 | return 270; 735 | } 736 | if ($a > 0 && $b > 0) { 737 | $bias = 0; 738 | } 739 | if ($a < 0) { 740 | $bias = 180; 741 | } 742 | if ($a > 0 && $b < 0) { 743 | $bias = 360; 744 | } 745 | 746 | return (rad2deg(atan($b / $a)) + $bias); 747 | } 748 | 749 | /** 750 | * Calculates the crop offset using an algorithm that tries to determine 751 | * the most interesting portion of the image to keep. 752 | * 753 | * @since 2.0 754 | * @param SLIRImage $image 755 | * @return array Associative array with the keys of x and y that specify the top left corner of the box that should be cropped 756 | */ 757 | public function getCrop(SLIRImage $image) 758 | { 759 | // Try contrast detection 760 | $o = $this->cropSmartOffsetRows($image); 761 | 762 | $crop = array( 763 | 'x' => 0, 764 | 'y' => 0, 765 | ); 766 | 767 | if ($o === false) { 768 | return true; 769 | } else if ($this->shouldCropTopAndBottom($image)) { 770 | $crop['y'] = $o; 771 | } else { 772 | $crop['x'] = $o; 773 | } 774 | 775 | return $crop; 776 | } 777 | 778 | } --------------------------------------------------------------------------------