PageRenderTime 63ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/webroot/thumber.php

https://bitbucket.org/nova-atlantis/metools
PHP | 312 lines | 173 code | 32 blank | 107 comment | 26 complexity | 1020cb6e57a7255b19822419f66ce0fc MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. // --------------------------------------------------------------------------
  3. // define paths
  4. // --------------------------------------------------------------------------
  5. define('PATH_TO_THUMBS', 'files/thumbs/');
  6. define('PATH_TO_LOGS', '');
  7. // --------------------------------------------------------------------------
  8. // activate error handling
  9. // --------------------------------------------------------------------------
  10. ini_set('error_reporting', E_ALL);
  11. ini_set('display_errors', 0);
  12. ini_set('error_log', 'thumber_errors.log');
  13. ini_set('log_errors', 1);
  14. function myErrorHandler($errno, $errstr, $errfile, $errline) {
  15. Thumber::error($errstr);
  16. return true;
  17. }
  18. set_error_handler('myErrorHandler');
  19. // --------------------------------------------------------------------------
  20. // set memory limit if necessary
  21. // --------------------------------------------------------------------------
  22. ini_set('memory_limit', '50M');
  23. // if the above doesn’t work:
  24. // add/ modify the .htaccess file (in the same directory as this file):
  25. // php_value memory_limit 50M
  26. // --------------------------------------------------------------------------
  27. // instantiate the Thumber class
  28. // --------------------------------------------------------------------------
  29. $thumber = new Thumber();
  30. /**
  31. * Thumber
  32. *
  33. * please drop me a note if you like it, have comments/suggestions/wishes,
  34. * found a bug, or just to say hello.
  35. *
  36. * @copyright Copyright (c) 2008, 2009, 2010, 2011 Peter Chylewski
  37. * released under the gnu license v3 <http://www.gnu.org/licenses/gpl.html>
  38. * @author Peter Chylewski <peter@boring.ch>
  39. * @version 0.5.6
  40. *
  41. * history:
  42. *
  43. * 0.5.4
  44. * - much faster image output via fpasstru instead of a redirect
  45. *
  46. * 0.5.5
  47. * - parameters 'w' and 'h' - if both set - define a 'box' - the output of distorted images is no longer possible
  48. * - substituted an '_' with an 'x' in the thumb filename that makes more sense,
  49. * e.g. 'cross_red_10x10.png' instead of 'cross_red_10_10.png'
  50. * - added alpha channel support for pngs and gifs
  51. *
  52. * 0.5.6
  53. * - cleaned up the code, improved comments
  54. * - force the creation of a new thumbnail if the creation date of the cached one is older
  55. * than the orginal’s modification date
  56. * - better error handling
  57. *
  58. * to to:
  59. * - cache purging
  60. * - implement / finalize proper error handling
  61. * - auto detect presence of an alpha channel in the image
  62. *
  63. * nice to have (maybe)
  64. *
  65. * - 'hot linking' of original files (through CURL or so)
  66. */
  67. class Thumber {
  68. private $pathToImage, $pathToThumb;
  69. private $imageType;
  70. private $imageWidth, $imageHeight;
  71. private $thumbArea;
  72. private $thumbWidth, $thumbHeight;
  73. function __construct() {
  74. $this->_logic();
  75. }
  76. private function _logic() {
  77. // --------------------------------------------------------------------------
  78. // what this program is supposed to do
  79. // --------------------------------------------------------------------------
  80. $this->pathToImage = isset($_GET['img']) ? base64_decode($_GET['img']) : '';
  81. if(!file_exists($this->pathToImage))
  82. trigger_error('input image not found at ' . $this->pathToImage, E_USER_ERROR);
  83. $this->thumbArea = isset($_GET['a']) ? $_GET['a'] : null;
  84. $this->thumbWidth = isset($_GET['w']) ? $_GET['w'] : null;
  85. $this->thumbHeight = isset($_GET['h']) ? $_GET['h'] : null;
  86. $this->_gatherInfo();
  87. $this->_calculateThumbDimensions();
  88. $this->_serveThumb();
  89. }
  90. private function _gatherInfo() {
  91. // --------------------------------------------------------------------------
  92. // determine the file type and the dimensions of the original image
  93. // --------------------------------------------------------------------------
  94. // right now, only 'gif', 'jpg' and 'png' files work as input,
  95. // but future versions of the GD library might understand more formats
  96. $types = array(
  97. 1 => 'gif',
  98. 2 => 'jpg',
  99. 3 => 'png',
  100. 4 => 'swf',
  101. 5 => 'psd',
  102. 6 => 'bmp',
  103. 7 => 'tiff(intel byte order)',
  104. 8 => 'tiff(motorola byte order)',
  105. 9 => 'jpc',
  106. 10 => 'jp2',
  107. 11 => 'jpx',
  108. 12 => 'jb2',
  109. 13 => 'swc',
  110. 14 => 'iff',
  111. 15 => 'wbmp',
  112. 16 => 'xbm'
  113. );
  114. $info = getimagesize($this->pathToImage);
  115. $this->imageWidth = $info[0];
  116. $this->imageHeight = $info[1];
  117. $this->imageType = $types[$info[2]];
  118. }
  119. private function _calculateThumbDimensions() {
  120. if(isset($this->thumbArea)) {
  121. // --------------------------------------------------------------------------
  122. // if the 'a' (for area) parameter has been set, calculate the thumb
  123. // dimensions so that their product will approximate the required area
  124. // (given in square pixels)
  125. // --------------------------------------------------------------------------
  126. $imageArea = $this->imageWidth * $this->imageHeight;
  127. $sizeRatio = $this->thumbArea / $imageArea;
  128. $this->thumbWidth = ceil($this->thumbArea / $this->imageHeight);
  129. $this->thumbHeight = ceil($this->thumbArea / $this->imageWidth);
  130. }
  131. else if(isset($this->thumbWidth) && isset($this->thumbHeight)) {
  132. // --------------------------------------------------------------------------
  133. // if both the width and the height have been given, calculate a bounding box
  134. // --------------------------------------------------------------------------
  135. if($this->imageWidth < $this->imageHeight)
  136. $sizeRatio = $this->imageHeight / $this->thumbHeight;
  137. else
  138. $sizeRatio = $this->imageWidth / $this->thumbWidth;
  139. $this->thumbWidth = ceil($this->imageWidth / $sizeRatio);
  140. $this->thumbHeight = ceil($this->imageHeight / $sizeRatio);
  141. }
  142. else {
  143. // --------------------------------------------------------------------------
  144. // if the width has not been given, calculate it from the height
  145. // if the height has not been given, calculate it from the width
  146. // --------------------------------------------------------------------------
  147. if(!isset($this->thumbWidth)) {
  148. $sizeRatio = $this->imageHeight / $this->thumbHeight;
  149. $this->thumbWidth = ceil($this->imageWidth / $sizeRatio);
  150. }
  151. else if(!isset($this->thumbHeight)) {
  152. $sizeRatio = $this->imageWidth / $this->thumbWidth;
  153. $this->thumbHeight = ceil($this->imageHeight / $sizeRatio);
  154. }
  155. }
  156. // --------------------------------------------------------------------------
  157. // make sure the thumbnail isn’t bigger than the original image (disputable)
  158. // --------------------------------------------------------------------------
  159. if($this->thumbWidth > $this->imageWidth || $this->thumbHeight > $this->imageHeight) {
  160. $this->thumbWidth = $this->imageWidth;
  161. $this->thumbHeight = $this->imageHeight;
  162. }
  163. // --------------------------------------------------------------------------
  164. // now that we know the definitive dimensions of our thumbnail (as integers),
  165. // why not use those to label the file properly?
  166. // --------------------------------------------------------------------------
  167. $pathParts = pathinfo($this->pathToImage);
  168. $this->pathToThumb = PATH_TO_THUMBS
  169. . $pathParts['filename']
  170. . '_' . $this->thumbWidth
  171. . 'x' . $this->thumbHeight
  172. . '.' . $pathParts['extension'];
  173. }
  174. private function _serveThumb() {
  175. // --------------------------------------------------------------------------
  176. // if the thumbnail image already exists, serve it;
  177. // otherwise generate one
  178. // --------------------------------------------------------------------------
  179. #$this->_generateThumb(); return; // force the generation of a new thumbnail (for testing)
  180. if(file_exists($this->pathToThumb)) {
  181. #self::error(filemtime($this->pathToImage) . '->' . filemtime($this->pathToThumb));
  182. // force the creation of a new thumbnail if the modification date of the cached one is older than the orginal’s
  183. if(filemtime($this->pathToImage) > filemtime($this->pathToThumb)) {
  184. $this->_generateThumb();
  185. return;
  186. }
  187. #$pathToThumb = ltrim($this->pathToThumb);
  188. // open the file in binary mode
  189. $fp = fopen($this->pathToThumb, 'rb');
  190. // send the right headers
  191. header('Content-Type: image/' . ($this->imageType == 'jpg' ? 'jpeg' : $this->imageType));
  192. header('Content-Disposition: inline; filename=' . urlencode(basename($this->pathToThumb)) . '');
  193. header('Content-Length: ' . filesize($this->pathToThumb));
  194. // stream it through
  195. fpassthru($fp);
  196. fclose($fp);
  197. exit;
  198. }
  199. elseif(file_exists($this->pathToImage))
  200. $this->_generateThumb();
  201. }
  202. private function _generateThumb() {
  203. // --------------------------------------------------------------------------
  204. // create an image from the input image file
  205. // --------------------------------------------------------------------------
  206. switch($this->imageType) {
  207. case 'jpg':
  208. $image = @imagecreatefromjpeg($this->pathToImage);
  209. break;
  210. case 'gif':
  211. $image = @imagecreatefromgif($this->pathToImage);
  212. break;
  213. case 'png':
  214. $image = @imagecreatefrompng($this->pathToImage);
  215. break;
  216. }
  217. if($image === false) {
  218. trigger_error('image could not be created', 1024);
  219. print 'nöööööö';
  220. exit;
  221. }
  222. // --------------------------------------------------------------------------
  223. // create the thumbnail image and paste the original into it in its new
  224. // dimensions
  225. // --------------------------------------------------------------------------
  226. $thumbImage = @ImageCreateTrueColor($this->thumbWidth, $this->thumbHeight);
  227. if($this->imageType == 'png' || $this->imageType == 'gif')
  228. imagealphablending($thumbImage, false);
  229. // copy image and paste it into the thumb image
  230. ImageCopyResampled($thumbImage, $image, 0, 0, 0, 0, $this->thumbWidth, $this->thumbHeight, $this->imageWidth, $this->imageHeight);
  231. if($this->imageType == 'png' || $this->imageType == 'gif') {
  232. ImageSaveAlpha($thumbImage, true);
  233. // we don’t sharpen thumbs that might contain alpha channels, because it produces nasty borders
  234. // to do: detect alpha channel in the original image
  235. }
  236. else {
  237. // --------------------------------------------------------------------------
  238. // sharpen it a little
  239. // --------------------------------------------------------------------------
  240. if(function_exists('imageconvolution')) {
  241. $sharpen = array(array(-1, -1, -1),
  242. array(-1, 34, -1),
  243. array(-1, -1, -1)
  244. );
  245. $divisor = array_sum(array_map('array_sum', $sharpen));
  246. imageconvolution($thumbImage, $sharpen, $divisor, 0);
  247. }
  248. }
  249. // --------------------------------------------------------------------------
  250. // spit it out
  251. // --------------------------------------------------------------------------
  252. switch($this->imageType) {
  253. case 'jpg':
  254. imagejpeg($thumbImage, $this->pathToThumb, 80);
  255. header('Content-type: image/jpeg');
  256. imagejpeg($thumbImage, NULL, 80);
  257. break;
  258. case 'gif':
  259. imagegif($thumbImage, $this->pathToThumb);
  260. header('Content-type: image/gif');
  261. imagegif($thumbImage, NULL);
  262. break;
  263. case 'png':
  264. imagepng($thumbImage, $this->pathToThumb);
  265. header('Content-type: image/png');
  266. imagepng($thumbImage, NULL);
  267. break;
  268. }
  269. imagedestroy($image);
  270. imagedestroy($thumbImage);
  271. exit;
  272. }
  273. public static function error($msg) {
  274. ob_end_clean();
  275. #header('HTTP/1.1 500 Internal Server Error');
  276. echo $msg;
  277. die();
  278. }
  279. }
  280. ?>