├── .gitignore ├── config └── SAMPLE.config.inc.php ├── composer.json ├── SAMPLE.lighttpd ├── LICENCE ├── src └── Image │ ├── ImageFactory.php │ ├── ImagickImage.php │ ├── GDImage.php │ └── AbstractImage.php ├── SAMPLE.htaccess ├── README.md └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | #System files 2 | .DS_Store 3 | 4 | #IDE project files 5 | *.esproj 6 | nbproject 7 | *.sublime-project 8 | *.sublime-workspace 9 | 10 | #Composer 11 | vendor 12 | 13 | #Project specific 14 | cache 15 | config/config.inc.php 16 | .htaccess -------------------------------------------------------------------------------- /config/SAMPLE.config.inc.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Default 10 | */ 11 | 12 | define('CACHE_FOLDER', dirname(__FILE__).'/../cache'); 13 | define('EXPIRE_DAYS', 365); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shulard/cdn-thumbnailer", 3 | "description": "A php library which resize images dynamically. A proxy allow to use it with a cache for an image CDN.", 4 | "keywords": ["php","thumbnail","cdn","generator"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Stephane HULARD", 9 | "email": "s.hulard@chstudio.fr" 10 | } 11 | ], 12 | "require": {}, 13 | "scripts": { 14 | "post-install-cmd": [ 15 | "cp SAMPLE.htaccess .htaccess", 16 | "cp config/SAMPLE.config.inc.php config/config.inc.php", 17 | "rm -rf cache && mkdir cache", 18 | "chmod 777 cache" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SAMPLE.lighttpd: -------------------------------------------------------------------------------- 1 | ################################ EXPIRE HEADERS ################################ 2 | $HTTP["url"] =~ "\.(ico|jpe?g|png|gif|swf|gz|ttf)$" { 3 | expire.url = ( "" => "access plus 1 years" ) 4 | } 5 | 6 | ############################## UNSET ETAG HEADER ############################### 7 | etag.use-inode = "disable" 8 | etag.use-mtime = "disable" 9 | etag.use-size = "disable" 10 | static-file.etags = "disable" 11 | 12 | ################################# REWRITE URLS ################################# 13 | # Rewrite all non-file requests to /cache 14 | url.rewrite-repeat-if-not-file = ( "^/(.*)$" => "/cache/$1" ) 15 | # If file is not found from /cache/, rewrite request to index.php 16 | url.rewrite-if-not-file = ( "^/cache/(|(http|https)\/)([0-9]+x[0-9]+|[a-z]+[0-9]+|original)\/(.*)$" => "index.php?scheme=$2&path=$4&format=$3" ) 17 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Stéphane HULARD 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/Image/ImageFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Image 10 | */ 11 | 12 | /** 13 | * Factory for Image implementation 14 | * @author Stephane HULARD 15 | * @package Image 16 | */ 17 | final class ImageFactory 18 | { 19 | protected function __construct() {} 20 | 21 | /** 22 | * Initialize a valid AbstractImage implementation 23 | * @param string $path 24 | * @return AbstractImage 25 | */ 26 | public static function build($path) { 27 | //If image magick use it 28 | //..but only for non-PNG images as certain PNG images 29 | //cause ImageMagick to segfault 30 | $useImagick = strtolower(substr($path, strrpos($path, '.') + 1 )) !== 'png'; 31 | if( $useImagick && extension_loaded('imagick') ) { 32 | require_once dirname(__FILE__).'/ImagickImage.php'; 33 | $oResized = new ImagickImage($path); 34 | //Else just use GD 35 | } elseif( extension_loaded('gd') ) { 36 | require_once dirname(__FILE__).'/GDImage.php'; 37 | $oResized = new GDImage($path); 38 | } else { 39 | throw new RuntimeException('There is no valid implementation to be used to manipulate image (GD, Imagick)'); 40 | } 41 | 42 | return $oResized; 43 | } 44 | } -------------------------------------------------------------------------------- /SAMPLE.htaccess: -------------------------------------------------------------------------------- 1 | 2 | #Rewrite Engine Settings : Warning, AllowOverride need to be all for current folder 3 | RewriteEngine On 4 | RewriteBase / 5 | 6 | #If the URL start with public stop redirect 7 | RewriteRule ^cache - [L] 8 | RewriteRule ^index.php - [L] 9 | 10 | #If file already exists 11 | RewriteCond %{DOCUMENT_ROOT}/cache%{REQUEST_URI} -f 12 | RewriteRule ^(.*)$ cache/$1 [L] 13 | 14 | #Else go to controller 15 | RewriteRule ^(|(.*)\/)([0-9]+x[0-9]+|[a-z]+[0-9]+|original)\/(.*)$ index.php?path=$4&format=$3&scheme=$2 [L,QSA] 16 | 17 | 18 | ########################### MOD_DEFLATE COMPRESSION ############################ 19 | #Do not put in cache if files are already in 20 | SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip 21 | 22 | ################################ EXPIRE HEADERS ################################ 23 | 24 | ExpiresActive On 25 | ExpiresDefault "access plus 7200 seconds" 26 | ExpiresByType image/jpg "access plus 2592000 seconds" 27 | ExpiresByType image/jpeg "access plus 2592000 seconds" 28 | ExpiresByType image/png "access plus 2592000 seconds" 29 | ExpiresByType image/gif "access plus 2592000 seconds" 30 | 31 | AddType image/x-icon .ico 32 | ExpiresByType image/ico "access plus 2592000 seconds" 33 | ExpiresByType image/icon "access plus 2592000 seconds" 34 | ExpiresByType image/x-icon "access plus 2592000 seconds" 35 | ExpiresByType text/css "access plus 2592000 seconds" 36 | 37 | 38 | ############################ CACHE CONTROL HEADERS ############################# 39 | 40 | #les proxies doivent donner le bon contenu 41 | Header append Vary User-Agent env=!dont-vary 42 | 43 | 44 | Header set Cache-Control "max-age=2592000, public" 45 | 46 | 47 | # KILL THEM ETAGS 48 | Header unset ETag 49 | FileETag none 50 | 51 | 52 | ############################ PROTECT HTACCESS FILE ############################# 53 | 54 | Order allow,deny 55 | Deny from all 56 | 57 | 58 | ############################ PROTECT FOLDER LISTING ############################ 59 | Options -Indexes -------------------------------------------------------------------------------- /src/Image/ImagickImage.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Image 10 | */ 11 | 12 | require_once dirname(__FILE__).'/AbstractImage.php'; 13 | 14 | /** 15 | * Specific ImageMagick Implementation 16 | * @author Stephane HULARD 17 | * @package Image 18 | */ 19 | class ImagickImage extends AbstractImage 20 | { 21 | /** 22 | * Loaded resource 23 | * @var Imagick 24 | */ 25 | protected $resource; 26 | 27 | /** 28 | * ImagickImage constructor 29 | */ 30 | public function __construct( $sPath ) { 31 | if(!extension_loaded('imagick')) { 32 | throw new Exception('You do not have the ImageMagick PECL extension installed. This class requires this extension to function properly.'); 33 | } 34 | 35 | parent::__construct( $sPath ); 36 | } 37 | 38 | /** 39 | * @see AbstractImage::buildResource 40 | */ 41 | protected function buildResource( $sPath ) { 42 | $this->resource = new Imagick($sPath); 43 | //Limit Imagick to single thread -- https://bugs.php.net/bug.php?id=61122 44 | $this->resource->setResourceLimit(6, 1); 45 | $this->width = $this->resource->getImageWidth(); 46 | $this->height = $this->resource->getImageHeight(); 47 | } 48 | 49 | /** 50 | * @see AbstractImage::buildResource 51 | */ 52 | protected function destroyResource() { 53 | if( $this->resource instanceof Imagick ) { 54 | $this->resource->clear(); 55 | } 56 | } 57 | 58 | /** 59 | * @see AbstractImage::resizeAndCrop 60 | */ 61 | public function resizeAndCrop( $iWidth, $iHeight ) { 62 | $this->resource->cropThumbnailImage($iWidth, $iHeight); 63 | } 64 | 65 | /** 66 | * @see AbstractImage::crop 67 | */ 68 | public function crop($iX, $iY, $iWidth, $iHeight) { 69 | //Be sure that the requested crop is inside the current image 70 | if( $iX + $iWidth > $this->width || $iY + $iHeight > $this->height ) { 71 | throw new Exception( 'Crop area requested is outside the current picture !!'); 72 | } 73 | 74 | $this->resource->cropImage($iWidth, $iHeight, $iX, $iY); 75 | $this->width = $iWidth; 76 | $this->height = $iHeight; 77 | } 78 | 79 | /** 80 | * @see AbstractImage::resize 81 | */ 82 | public function resize( $iWidth = null, $iHeight = null ) { 83 | //If null given, compute a valid size 84 | if( is_null( $iWidth ) ) { 85 | $iWidth = $this->width*$iHeight/$this->height; 86 | } 87 | if( is_null( $iHeight ) ) { 88 | $iHeight = $this->height*$iWidth/$this->width; 89 | } 90 | 91 | $this->resource->scaleImage($iWidth, $iHeight); 92 | $this->width = $iWidth; 93 | $this->height = $iHeight; 94 | } 95 | 96 | /** 97 | * @see AbstractImage::save 98 | */ 99 | public function save( $sPath ) { 100 | $this->resource->writeImage($sPath); 101 | } 102 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CDNThumbnailer 2 | 3 | CDNThumbnailer is a php tool for dynamically image resizing. It is distributed with a cache and a CDN controller to allow quick files access. It include compatibility with GD and ImageMagick php extension. 4 | 5 | This tool is written in **PHP 5.2** to maximize compatibility (but in a **PHP 5.3** style). 6 | 7 | ## Documentation, help 8 | 9 | This tool can be used as a library inside a project for image resizing. It can also be included as a HTTP Proxy for image resizing. 10 | It is compatible with external images and allow dynamic resizing and serving of images. 11 | 12 | If you need to use it as a proxy, there are some initialization to perform: 13 | 14 | - Copy [SAMPLE.htaccess](SAMPLE.htaccess) to *.htaccess* (this new file is ignored by .gitignore), replace **%{DOCUMENT_ROOT}/cache** by the absolute path to the cache folder and **RewriteBase** by the valid one in your project. 15 | - Copy [config/SAMPLE.config.inc.php](config/SAMPLE.config.inc.php) to *config/config.inc.php* and update the **CACHE_FOLDER** constant if you use a custom layout (let it if not). 16 | 17 | To call the CDN, you need to use URLs like that: 18 | 19 | - **http://[yourhost]/28x28/path/to/your/image.png** : This URL request the file *cache/28x28/path/to/your/image.png* to be served. If exists, it is returned, else the file *cache/original/path/to/your/image.png* will be used as master for resize (if this file does not exists, HTTP Status code is returned as 404). 20 | - If you use external images as content source, you need to defined the scheme in the URL: **http://[yourhost]/http/28x28/host.com/distant/your/image.png**. This URL will download the image http://host.com/distant/your/image.png if exists and use it as master for resizing. The distant image is put in the original folder to forbid multiple downloads of the same image. 21 | 22 | HTTP Status code are used to manage errors: 23 | 24 | - If the original file to be used for resize does not exists, the **HTTP 404** code is sent 25 | - If the server encounter a problem during image processing, the **HTTP 500** code is sent 26 | 27 | If you find a bug, please submit an issue. If you want to contribute you're welcome! 28 | 29 | 30 | ## Licence 31 | 32 | Copyright (c) 2013 **Stéphane HULARD** 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 39 | -------------------------------------------------------------------------------- /src/Image/GDImage.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Image 10 | */ 11 | 12 | require_once dirname(__FILE__).'/AbstractImage.php'; 13 | 14 | /** 15 | * Specific GD Implementation 16 | * @author Stephane HULARD 17 | * @package Image 18 | */ 19 | class GDImage extends AbstractImage 20 | { 21 | /** 22 | * GDImage constructor 23 | */ 24 | public function __construct( $sPath ) { 25 | if(!extension_loaded('gd')) { 26 | throw new Exception('You do not have the GD Library installed. This class requires the GD library to function properly.'); 27 | } 28 | 29 | parent::__construct( $sPath ); 30 | } 31 | 32 | /** 33 | * Limitation on the GD library to GIF, PNG & JPEG 34 | * @see AbstractImage::retrieveType 35 | */ 36 | protected function retrieveType($sPath) { 37 | parent::retrieveType($sPath); 38 | 39 | if( !in_array($this->type, array(IMAGETYPE_GIF, IMAGETYPE_PNG, IMAGETYPE_JPEG)) ) { 40 | throw new Exception('Image type given is not a valid one, only GIF, PNG and JPG are allowed'); 41 | } 42 | } 43 | 44 | /** 45 | * @see AbstractImage::buildResource 46 | */ 47 | protected function buildResource( $sPath ) { 48 | $this->resource = imagecreatefromstring(file_get_contents($sPath)); 49 | $this->width = imagesx($this->resource); 50 | $this->height = imagesy($this->resource); 51 | } 52 | 53 | /** 54 | * @see AbstractImage::buildResource 55 | */ 56 | protected function destroyResource() { 57 | if(is_resource($this->resource)) { 58 | imagedestroy($this->resource); 59 | } 60 | } 61 | 62 | /** 63 | * @see AbstractImage::resize 64 | */ 65 | public function resize( $iWidth = null, $iHeight = null ) { 66 | //If null given, compute a valid size 67 | if( is_null( $iWidth ) ) { 68 | $iWidth = $this->width*$iHeight/$this->height; 69 | } 70 | if( is_null( $iHeight ) ) { 71 | $iHeight = $this->height*$iWidth/$this->width; 72 | } 73 | 74 | //Build result resource 75 | $oCurrent = $this->resource; 76 | if(function_exists("ImageCreateTrueColor")) { 77 | $oResized = ImageCreateTrueColor($iWidth,$iHeight); 78 | } else { 79 | $oResized = ImageCreate($iWidth,$iHeight); 80 | } 81 | //Save alpha 82 | imagealphablending($oResized, false); 83 | imagesavealpha($oResized, true); 84 | //Compute resize 85 | imagecopyresampled( $oResized, $this->resource, 0, 0, 0, 0, $iWidth, $iHeight, $this->width, $this->height ); 86 | 87 | //Destroy previous resource and fill properties 88 | imagedestroy($oCurrent); 89 | $this->resource = $oResized; 90 | $this->width = $iWidth; 91 | $this->height = $iHeight; 92 | } 93 | 94 | /** 95 | * @see AbstractImage::crop 96 | */ 97 | public function crop($iX, $iY, $iWidth, $iHeight) { 98 | //Be sure that the requested crop is inside the current image 99 | if( $iX + $iWidth > $this->width || $iY + $iHeight > $this->height ) { 100 | throw new Exception( 'Crop area requested is outside the current picture !!'); 101 | } 102 | 103 | //Build result resource 104 | $oCurrent = $this->resource; 105 | if(function_exists("ImageCreateTrueColor")) { 106 | $oResized = ImageCreateTrueColor($iWidth,$iHeight); 107 | } else { 108 | $oResized = ImageCreate($iWidth,$iHeight); 109 | } 110 | //Save alpha 111 | imagealphablending($oResized, false); 112 | imagesavealpha($oResized, true); 113 | //Compute resize 114 | imagecopyresampled( $oResized, $this->resource, 0, 0, $iX, $iY, $iWidth, $iHeight, $iWidth, $iHeight ); 115 | 116 | //Destroy previous resource and fill properties 117 | imagedestroy($oCurrent); 118 | $this->resource = $oResized; 119 | $this->width = $iWidth; 120 | $this->height = $iHeight; 121 | } 122 | 123 | /** 124 | * @see AbstractImage::save 125 | */ 126 | public function save( $sPath ) { 127 | //GD Can't save image without using a type function 128 | switch ($this->type) { 129 | case IMAGETYPE_PNG: 130 | imagepng($this->resource,$sPath); 131 | break; 132 | case IMAGETYPE_GIF: 133 | imagegif($this->resource,$sPath); 134 | break; 135 | case IMAGETYPE_JPEG: 136 | imagejpeg($this->resource,$sPath,100); 137 | break; 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Default 10 | */ 11 | 12 | if( !isset($_GET['path']) || !isset($_GET['format']) ) { 13 | header($_SERVER['SERVER_PROTOCOL'].' 400 Bad request', true, 400); 14 | exit(); 15 | } 16 | 17 | //Require configuration 18 | require_once dirname(__FILE__).'/config/config.inc.php'; 19 | 20 | //File path to be resized 21 | $sPath = $_GET['path']; 22 | //Image url scheme if image is an external one 23 | $sScheme = isset($_GET['scheme'])?$_GET['scheme']:null; 24 | 25 | //If there are GET parameters in the picture URL, just add it to the path 26 | $query = array_diff_key($_GET, array_flip(array('path', 'format', 'scheme'))); 27 | if( count($query) > 0 ) { 28 | $sPath .= '?'.http_build_query($query); 29 | } 30 | 31 | $sCache = realpath(CACHE_FOLDER).(isset($sScheme)?'/'.$sScheme.'/':""); 32 | //Cleanup invalid characters from the path 33 | $sCleanedPath = str_replace(array('?','=','&',':'), array('_','_','_','_'), $sPath); 34 | 35 | //Define folder structure original contains base files and format folder are in the cache 36 | $sOriginalFile = $sCache.'original/'.$sCleanedPath; 37 | $sOriginalDir = dirname($sOriginalFile); 38 | 39 | $sResizedFile = $sCache.$_GET['format'].'/'.$sCleanedPath; 40 | $sResizedDir = dirname($sResizedFile); 41 | 42 | //If the original file does not exists 43 | if( !is_file($sOriginalFile) ) { 44 | //If the scheme is defined we try to download image 45 | if( !is_null($sScheme) ) { 46 | //Initialize curl handler and make the request 47 | $oRequest = curl_init($sScheme.'://'.str_replace(' ', '%20', $sPath)); 48 | //Pretend to be a desktop browser 49 | curl_setopt($oRequest, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36'); 50 | //Try and cope with some HTTPS servers 51 | curl_setopt($oRequest, CURLOPT_SSL_VERIFYPEER, FALSE); 52 | curl_setopt($oRequest, CURLOPT_SSL_VERIFYHOST, 2); 53 | curl_setopt($oRequest, CURLOPT_SSL_CIPHER_LIST, 'TLSv1'); 54 | //Follow redirects 55 | curl_setopt($oRequest, CURLOPT_FOLLOWLOCATION, true); 56 | ob_start(); 57 | curl_exec($oRequest); 58 | $sContent = ob_get_clean(); 59 | 60 | //Retrieve last request details 61 | $aCurlInfo = curl_getinfo($oRequest); 62 | //If last request is a "200 OK", continue 63 | if( isset($aCurlInfo['http_code']) && $aCurlInfo['http_code'] == 200 ) { 64 | if( !is_dir($sOriginalDir) ) { 65 | $umask = umask(0); 66 | mkdir($sOriginalDir, 0777, true); 67 | umask($umask); 68 | } 69 | file_put_contents($sOriginalFile, $sContent); 70 | //Else, the file can't be retrieved so, send a 404 header 71 | } else { 72 | header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found', true, 404); 73 | exit(); 74 | } 75 | //Close curl handle 76 | curl_close($oRequest); 77 | //The scheme is not defined and original file is not here, file does not exists 78 | } else { 79 | header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found', true, 404); 80 | exit(); 81 | } 82 | } 83 | 84 | //If resized folder does not exists we add it 85 | if( !is_dir($sResizedDir) ) { 86 | $umask = umask(0); 87 | mkdir($sResizedDir, 0777, true); 88 | umask($umask); 89 | } 90 | 91 | try 92 | { 93 | require_once dirname(__FILE__).'/src/Image/ImageFactory.php'; 94 | $oResized = ImageFactory::build($sOriginalFile); 95 | 96 | if( !is_file($sResizedFile) ) { 97 | //Resize on width constraint only 98 | if( strpos($_GET['format'], 'w') === 0 ) { 99 | $oResized->resize(substr($_GET['format'], 1)); 100 | //Resize on height constraint only 101 | } elseif( strpos($_GET['format'], 'h') === 0 ) { 102 | $oResized->resize(null, substr($_GET['format'], 1)); 103 | } elseif( strpos($_GET['format'], 'max') === 0 ) { 104 | if( $oResized->getWidth() > $oResized->getHeight() ) { 105 | $oResized->resize(substr($_GET['format'], 3)); 106 | } else { 107 | $oResized->resize(null, substr($_GET['format'], 3)); 108 | } 109 | //Resize and crop (11x11) 110 | } else { 111 | $aFormat = explode('x', $_GET['format']); 112 | //Use built Image manipulator to resize and save the new file 113 | $oResized->resizeAndCrop($aFormat[0], $aFormat[1]); 114 | } 115 | 116 | $oResized->save($sResizedFile); 117 | } 118 | 119 | //Build valid HTTP Headers for cache and content type/length for a correct navigator management 120 | $expires = 60*60*24*EXPIRE_DAYS; 121 | header($_SERVER['SERVER_PROTOCOL'].' 200 OK', true, 200); 122 | // header("Pragma: public"); 123 | header("Cache-Control: maxage=".$expires); 124 | header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT'); 125 | header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($sResizedFile)).' GMT'); 126 | header('Content-Type: '.image_type_to_mime_type($oResized->getType())); 127 | header('Content-Length: '.filesize($sResizedFile)); 128 | echo file_get_contents($sResizedFile); 129 | //Unset ImageFactory object to make sure resources are released 130 | unset($oResized); 131 | } 132 | //If errors are sent during resizing send HTTP 500 Errors 133 | catch( ImagickException $oError ) 134 | { 135 | header($_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error', true, 500); 136 | echo $oError->getMessage(); 137 | } 138 | catch( Exception $oError ) 139 | { 140 | header($_SERVER['SERVER_PROTOCOL'].' 500 Internal Server Error', true, 500); 141 | echo $oError->getMessage(); 142 | } -------------------------------------------------------------------------------- /src/Image/AbstractImage.php: -------------------------------------------------------------------------------- 1 | 9 | * @package Image 10 | */ 11 | 12 | /** 13 | * Standard image representation 14 | * Define a canvas for different image types 15 | * @author Stephane HULARD 16 | * @package Image 17 | */ 18 | abstract class AbstractImage 19 | { 20 | /** 21 | * Image width 22 | * @var Integer 23 | */ 24 | protected $width; 25 | 26 | /** 27 | * Image height 28 | * @var Integer 29 | */ 30 | protected $height; 31 | 32 | /** 33 | * Image type. Can only be one of the supported format (JPG, PNG, GIF) 34 | * Use the predefined constant to decode the type 35 | * @var Integer 36 | */ 37 | protected $type; 38 | 39 | /** 40 | * Image resource 41 | * Word resource is used for the current image object (GD resource, Imagick instance, ...) 42 | */ 43 | protected $resource; 44 | 45 | /** 46 | * AbstractImage constructor 47 | */ 48 | public function __construct( $sPath ) { 49 | $this->width = 0; 50 | $this->height = 0; 51 | $this->type = 0; 52 | $this->resource = null; 53 | 54 | if( !is_file($sPath) || !is_readable( $sPath )) { 55 | throw new Exception("File path given is not a valid one!!"); 56 | } 57 | 58 | //Initialize image resource 59 | $this->retrieveType($sPath); 60 | $this->buildResource($sPath); 61 | } 62 | 63 | /** 64 | * AbstractImage destructor 65 | */ 66 | public function __destruct() { 67 | //Free resource memory (depends on which library is used) 68 | $this->destroyResource(); 69 | 70 | unset( $this->resource ); 71 | } 72 | 73 | /** 74 | * Width accessor 75 | * @return Integer 76 | */ 77 | public function getWidth() { 78 | return $this->width; 79 | } 80 | /** 81 | * Height accessor 82 | * @return Integer 83 | */ 84 | public function getHeight() { 85 | return $this->height; 86 | } 87 | /** 88 | * Type accessor 89 | * @return Integer 90 | */ 91 | public function getType() { 92 | return $this->type; 93 | } 94 | 95 | /** 96 | * Retrieve image type for the given file 97 | * @param String $sPath The path to be loaded as Image 98 | */ 99 | protected function retrieveType($sPath) { 100 | //If method to extract image type exists, use it 101 | if( function_exists("exif_imagetype") ) { 102 | $this->type = exif_imagetype($sPath); 103 | } elseif( function_exists("getimagesize") ) { 104 | if( ($tmp = getimagesize($sPath)) !== false ) { 105 | $this->type = $tmp[2]; 106 | } 107 | //Else use extension detection 108 | } else { 109 | $sExtension = strtolower(substr($sPath, strrpos($sPath, '.') + 1 )); 110 | switch ($sExtension) { 111 | case 'png': 112 | $this->type = IMAGETYPE_PNG; 113 | break; 114 | case 'jpeg': 115 | case 'jpg': 116 | $this->type = IMAGETYPE_JPEG; 117 | break; 118 | case 'gif': 119 | $this->type = IMAGETYPE_GIF; 120 | break; 121 | case 'bmp': 122 | $this->type = IMAGETYPE_BMP; 123 | break; 124 | default: 125 | throw new Exception('Unrecognized image format: '.$sExtension); 126 | break; 127 | } 128 | } 129 | 130 | if( in_array($this->type, array(IMAGETYPE_SWF, IMAGETYPE_PSD, IMAGETYPE_SWC)) ) { 131 | throw new Exception('Only valid images are allowed!'); 132 | } 133 | } 134 | 135 | /** 136 | * Specific resize & crop process 137 | * Try to get the most important part of the picture 138 | * @param Integer $iWidth Width of the result image 139 | * @param Integer $iHeight Height of the result image 140 | */ 141 | public function resizeAndCrop( $iWidth, $iHeight ) { 142 | $iRatio = (int) $iWidth / (int) $iHeight; 143 | 144 | // if media is a square 145 | if ($iRatio == 1) { 146 | if ($this->width > $this->height) { 147 | $this->resize(null, $iHeight); 148 | } else { 149 | $this->resize($iWidth, null); 150 | } 151 | // horizontal format 152 | } elseif ($iRatio > 1) { 153 | $iTmpRatio = $this->width / $iWidth; 154 | if (($this->height / $iTmpRatio) < $iHeight) { 155 | $this->resize(null, $iHeight); 156 | } else { 157 | $this->resize($iWidth, null); 158 | } 159 | // vertical format 160 | } elseif ($iRatio < 1) { 161 | $iTmpRatio = $this->height / $iHeight; 162 | if (($this->width / $iTmpRatio) < $iWidth) { 163 | $this->resize($iWidth, ''); 164 | } else { 165 | $this->resize('', $iHeight); 166 | } 167 | } 168 | 169 | $this->crop( 170 | $this->width/2-$iWidth/2, 171 | $this->height/2-$iHeight/2, 172 | $iWidth, 173 | $iHeight 174 | ); 175 | } 176 | 177 | /** 178 | * Build an image resource from a given file 179 | * @param String $sPath The path to be loaded as Image 180 | */ 181 | abstract protected function buildResource( $sPath ); 182 | 183 | /** 184 | * Destroy image resource if loaded 185 | */ 186 | abstract protected function destroyResource(); 187 | 188 | /** 189 | * Resize the picture at a given size 190 | * @param Integer $iWidth Width of the result image 191 | * @param Integer $iHeight Height of the result image 192 | */ 193 | abstract public function resize( $iWidth = null, $iHeight = null ); 194 | /** 195 | * Crop the picture from center at a given size 196 | * @param Integer $iX X position to start crop 197 | * @param Integer $iY Y position to start crop 198 | * @param Integer $iWidth Width to crop 199 | * @param Integer $iHeight Height to crop 200 | */ 201 | abstract public function crop( $iX, $iY, $iWidth, $iHeight ); 202 | 203 | /** 204 | * Save the current image to the given filepath 205 | * @param String $sPath path to use to save image 206 | */ 207 | abstract public function save( $sPath ); 208 | } 209 | --------------------------------------------------------------------------------