source: trunk/plugins/MootoolsFileManager/mootools-filemanager/Assets/Connector/Image.class.php @ 1371

Last change on this file since 1371 was 1371, checked in by gogo, 15 months ago

Updates to MootoolsFileManager? - most importantly removal of Flash dependancy, now uses HTML5 file uploads.

Also now updated the default MooTools? to 1.6.0 - you can of course load your own MooTools? first (before Xinha), anything down to about 1.3 works I think, if you load yours then it will be used instead.

Other updates from https://github.com/sleemanj/mootools-filemanager included in this update are:

  • Remove space from allowed characters in filenames, they will be converted to _ on upload
  • Fix for a small (inconsequential) uppercase extension bug
  • Add some more php extensions to the "unsafe" list
  • Fix image resize on upload to be more space efficient
File size: 23.9 KB
Line 
1<?php
2/**
3 * Image - Provides an Interface to the GD-Library for image manipulation
4 *
5 *
6 * @license MIT-style License
7 * @author Christoph Pojer <christoph.pojer@gmail.com>
8 * @author Additions: Fabian Vogelsteller <fabian.vogelsteller@gmail.com>
9 * @author Additions: Ger Hobbelt <ger@hobbelt.com>
10 *
11 * @link http://www.bin-co.com/php/scripts/classes/gd_image/ Based on work by "Binny V A"
12 *
13 * @version 1.12
14 * Changelog
15 *    - 1.12 added memory usage guestimator to warn you when attempting to process overlarge images which will silently but fataly crash PHP
16 *    - 1.11 fixed $ratio in resize when both values are given
17 *    - 1.1 add real resizing, with comparison of ratio
18 *    - 1.01 small fixes in process method and add set memory limit to a higher value
19 */
20
21
22define('IMAGE_PROCESSING_MEMORY_MAX_USAGE', 160); // memory_limit setting, in Megabytes; increase when Image class reports too often the images don't fit in memory.
23
24class Image {
25        /**
26         * The path to the image file
27         *
28         * @var string
29         */
30        private $file;
31        /**
32         * The image resource
33         *
34         * @var resource
35         */
36        private $image;
37        /**
38         * Metadata regarding the image
39         *
40         * @var array
41         */
42        private $meta;
43        /**
44         * Flags whether the image has been manipulated by this instance in any way and has not yet been saved to disk.
45         */
46        private $dirty;
47
48        /**
49         * @param string $file The path to the image file
50         */
51        public function __construct($file)
52        {
53                $this->dirty = false;
54
55                $this->meta = self::checkFileForProcessing($file);
56
57                // only set the new memory limit of IMAGE_PROCESSING_MEMORY_MAX_USAGE MB when the configured one is smaller:
58                if ($this->meta['fileinfo']['memory_limit'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024)
59                {
60                        ini_set('memory_limit', IMAGE_PROCESSING_MEMORY_MAX_USAGE . 'M'); //  handle large images
61                }
62
63                $this->file = $file;
64
65                if($this->meta['ext'] != 'jpeg')
66                {
67                        $this->image = $this->create();
68
69                        $fn = 'imagecreatefrom'.$this->meta['ext'];
70                        $original = false;
71                        if (function_exists($fn))
72                        {
73                                $original = @$fn($file);
74                        }
75                        if (!$original) throw new Exception('imagecreate_failed:' . $fn);
76
77                        if (!@imagecopyresampled($this->image, $original, 0, 0, 0, 0, $this->meta['width'], $this->meta['height'], $this->meta['width'], $this->meta['height']))
78                                throw new Exception('cvt2truecolor_failed:' . $this->meta['width'] . ' x ' . $this->meta['height']);
79                        imagedestroy($original);
80                        unset($original);
81                }
82                else
83                {
84                        $this->image = @imagecreatefromjpeg($file);
85                        if (!$this->image) throw new Exception('imagecreate_failed:imagecreatefromjpeg');
86                }
87        }
88
89        public function __destruct(){
90                if(!empty($this->image)) imagedestroy($this->image);
91                unset($this->image);
92        }
93
94        /**
95         * Return an array of supported extensions (rather: the second parts of the mime types!)
96         *
97         * A type is listed as 'supported' when it can be READ.
98         */
99        public static function getSupportedTypes()
100        {
101                static $supported_types;
102
103                if (empty($supported_types))
104                {
105                        $gdi = gd_info();
106
107                        $supported_types = array();
108                        if (!empty($gdi['GIF Read Support']))
109                                $supported_types[] = 'gif';
110                        if (!empty($gdi['PNG Support']))
111                                $supported_types[] = 'png';
112                        if (!empty($gdi['JPEG Support']) || !empty($gdi['JPG Support']) /* pre 5.3.0 */ )
113                                $supported_types[] = 'jpeg';
114                        if (!empty($gdi['WBMP Support']))
115                                $supported_types[] = 'wbmp';
116                        if (!empty($gdi['XPM Support']))
117                                $supported_types[] = 'xpm';
118                        if (!empty($gdi['XBM Support']))
119                                $supported_types[] = 'xbm';
120                        $supported_types[] = 'bmp';
121                }
122                return $supported_types;
123        }
124
125        /**
126         * Guestimates how much RAM memory must be available to be able to process the given image file.
127         *
128         * @return an array with key,value pairs: 'needed' specifies the guestimated minimum amount of free
129         *         memory required for the given file, 'memory_limit' is an integer value representing the total
130         *         amount of bytes reserved for PHP script, while 'will_fit' is a boolean which indicates whether
131         *         the given image file could potentially be loaded and processed without causing fatal out-of-memory
132         *         errors.
133         *         The directory separator and path-corrected filespec is returned in the 'path' value.
134         *
135         * @note The given image file must exist on disk; if it does not, 'needed' and 'will_fit' keys will not
136         *       be present in the returned array.
137         */
138        public static function guestimateRequiredMemorySpace($file)
139        {
140                $val = trim(ini_get('memory_limit'));
141                $last = strtoupper(substr($val, -1, 1));
142                $val = floatval($val); // discards the KMG postfix, allow $val to carry values beyond 2..4G
143                switch($last)
144                {
145                // The 'G' modifier is available since PHP 5.1.0
146                case 'G':
147                        $val *= 1024.0;
148                case 'M':
149                        $val *= 1024.0;
150                case 'K':
151                        $val *= 1024.0;
152                        break;
153                }
154                $limit = $val;
155
156                $in_use = (function_exists('memory_get_usage') ? memory_get_usage() : 1000000 /* take a wild guess, er, excuse me, 'apply a heuristic' */ );
157
158                $rv = array(
159                        'memory_limit' => $limit,
160                        'mem_in_use' => $in_use,
161                        'path' => $file
162                        );
163
164                if(file_exists($file))
165                {
166                        $raw_size = @filesize($file);
167                        $rv['filesize'] = $raw_size;
168
169                        $img = @getimagesize($file, $info_ex);
170                        if ($img)
171                        {
172                                $width = $img[0];
173                                $height = $img[1];
174                                $rv['imageinfo'] = $img;
175                                $rv['imageinfo_extra'] = $info_ex;
176
177                                // assume RGBA8, i.e. 4 bytes per pixel
178                                // ... having had a good look with memory_get_usage() and memory_get_peak_usage(), it turns out we need
179                                // a 'fudge factor' a.k.a. heuristic as the '4 bytes per pixel' estimate is off by quite a bit (if we have
180                                // to believe the numbers following a single GD image load operation):
181                                $needed = 4.0 * $width * $height;
182                                $needed *= 34.0 / 27.0;
183                                $rv['needed'] = $needed;
184
185                                // since many operations require a source and destination buffer, that'll be 2 of them buffers, thank you very much:
186                                // ... however, having had a good look with memory_get_usage() and memory_get_peak_usage(), it turns out
187                                // we need about triple!
188                                $will_eat = $needed * 2.8;
189                                // ^^^ factor = 2.8 : for very large images the estimation error is now ~ +1..8% too pessimistic. Soit!
190                                //     (tested with PNG images which consumed up to 475MB RAM to have their thumbnail created. This took a few
191                                //      seconds per image, so you might ask yourself if being able to serve such memory megalodons would be,
192                                //      ah, desirable, when considered from this perspective.)
193
194                                // and 'worst case' (ahem) we've got the file itself loaded in memory as well (on initial load and later save):
195                                // ... but this is more than covered by the 'triple charge' already, so we ditch this one from the heuristics.
196                                if (0) $will_eat += $raw_size;
197
198                                // interestingly, JPEG files only appear to require about half that space required by PNG resize processes...
199                                if (!empty($img['mime']) && $img['mime'] == 'image/jpeg')
200                                {
201                                        $will_eat /= 2.0;
202                                }
203
204                                $rv['usage_guestimate'] = $will_eat;
205
206                                // now we know what we about need for this bugger, see if we got enough:
207                                $does_fit = ($limit - $in_use > $will_eat);
208                                $rv['usage_min_advised'] = $will_eat + $in_use;
209                                $rv['will_fit'] = $does_fit;
210                        }
211                        else
212                        {
213                                // else: this is not a valid image file!
214                                $rv['not_an_image_file'] = true;
215                        }
216                }
217                else
218                {
219                        // else: this file does not exist!
220                        $rv['not_an_image_file'] = true;
221                }
222                return $rv;
223        }
224
225        /**
226         * Check whether the given file is really an image file and whether it can be processed by our Image class, given the PHP
227         * memory restrictions.
228         *
229         * Return the meta data array when the expectation is that the given file can be processed; throw an exception otherwise.
230         */
231        public static function checkFileForProcessing($file)
232        {
233                $finfo = self::guestimateRequiredMemorySpace($file);
234                if (!empty($finfo['not_an_image_file']))
235                        throw new Exception('no_imageinfo');
236
237                // is it a valid file existing on disk?
238                if (!isset($finfo['imageinfo']))
239                        throw new Exception('no_imageinfo');
240
241                $file = $finfo['path'];
242
243                // only set the new memory limit of IMAGE_PROCESSING_MEMORY_MAX_USAGE MB when the configured one is smaller:
244                if ($finfo['memory_limit'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024)
245                {
246                        // recalc the 'will_fit' indicator now:
247                        $finfo['will_fit'] = ($finfo['usage_min_advised'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024);
248                }
249
250                $img = $finfo['imageinfo'];
251
252                // and will it fit in available memory if we go and load the bugger?
253                if (!$finfo['will_fit'])
254                        throw new Exception('img_will_not_fit:' . ceil($finfo['usage_min_advised'] / 1E6) . ' MByte');
255
256                $explarr = explode('/', $img['mime']); // make sure the end() call doesn't throw an error next in E_STRICT mode:
257                $ext_from_mime = end($explarr);
258                $meta = array(
259                        'width' => $img[0],
260                        'height' => $img[1],
261                        'mime' => $img['mime'],
262                        'ext' => $ext_from_mime,
263                        'fileinfo' => $finfo
264                );
265
266                if($meta['ext'] == 'jpg')
267                        $meta['ext'] = 'jpeg';
268                else if($meta['ext'] == 'bmp')
269                        $meta['ext'] = 'bmp';
270                else if($meta['ext'] == 'x-ms-bmp')
271                        $meta['ext'] = 'bmp';
272                if(!in_array($meta['ext'], self::getSupportedTypes()))
273                        throw new Exception('unsupported_imgfmt:' . $meta['ext']);
274
275                return $meta;
276        }
277
278        /**
279         * Calculate the resize dimensions of an image, given the original dimensions and size limits
280         *
281         * @param int $orig_x the original's width
282         * @param int $orig_y the original's height
283         * @param int $x the maximum width after resizing has been done
284         * @param int $y the maximum height after resizing has been done
285         * @param bool $ratio set to FALSE if the image ratio is solely to be determined
286         *                    by the $x and $y parameter values; when TRUE (the default)
287         *                    the resize operation will keep the image aspect ratio intact
288         * @param bool $resizeWhenSmaller if FALSE the images will not be resized when
289         *                    already smaller, if TRUE the images will always be resized
290         * @return array with 'width', 'height' and 'must_resize' component values on success; FALSE on error
291         */
292        public static function calculate_resize_dimensions($orig_x, $orig_y, $x = null, $y = null, $ratio = true, $resizeWhenSmaller = false)
293        {
294                if(empty($orig_x) || empty($orig_y) || (empty($x) && empty($y))) return false;
295
296                $xStart = $x;
297                $yStart = $y;
298                $ratioX = $orig_x / $orig_y;
299                $ratioY = $orig_y / $orig_x;
300                $ratio |= (empty($y) || empty($x)); // keep ratio when only width OR height is set
301                //echo 'ALLOWED: <br>'.$xStart.'x'."<br>".$yStart.'y'."<br>---------------<br>";
302                // ->> keep the RATIO
303                if($ratio) {
304                        //echo 'BEGINN: <br>'.$orig_x.'x'."<br>".$orig_y.'y'."<br><br>";
305                        // -> align to WIDTH
306                        if(!empty($x) && ($x < $orig_x || $resizeWhenSmaller))
307                                $y = $x / $ratioX;
308                        // -> align to HEIGHT
309                        elseif(!empty($y) && ($y < $orig_y || $resizeWhenSmaller))
310                                $x = $y / $ratioY;
311                        else {
312                                $y = $orig_y;
313                                $x = $orig_x;
314                        }
315                        //echo 'BET: <br>'.$x.'x'."<br>".$y.'y'."<br><br>";
316                        // ->> align to WIDTH AND HEIGHT
317                        if((!empty($yStart) && $y > $yStart) || (!empty($xStart) && $x > $xStart)) {
318                                if($y > $yStart) {
319                                        $y = $yStart;
320                                        $x = $y / $ratioY;
321                                } elseif($x > $xStart) {
322                                        $x = $xStart;
323                                        $y = $x / $ratioX;
324                                }
325                        }
326                }
327                // else: ->> DONT keep the RATIO
328
329                $x = round($x);
330                $y = round($y);
331
332                //echo 'END: <br>'.$x.'x'."<br>".$y.'y'."<br><br>";
333                $rv = array(
334                        'width' => $x,
335                        'height' => $y,
336                        'must_resize' => false
337                );
338                // speedup? only do the resize operation when it must happen:
339                if ($x != $orig_x || $y != $orig_y)
340                {
341                        $rv['must_resize'] = true;
342                }
343                return $rv;
344        }
345
346        /**
347         * Returns the size of the image
348         *
349         * @return array
350         */
351        public function getSize(){
352                return array(
353                        'width' => $this->meta['width'],
354                        'height' => $this->meta['height'],
355                );
356        }
357
358
359        /**
360         * Returns a copy of the meta information of the image
361         *
362         * @return array
363         */
364        public function getMetaInfo(){
365                return array_merge(array(), (is_array($this->meta) ? $this->meta : array()));
366        }
367
368
369        /**
370         * Returns TRUE when the image data have been altered by this instance's operations, FALSE when the content has not (yet) been touched.
371         *
372         * @return boolean
373         */
374        public function isDirty(){
375                return $this->dirty;
376        }
377
378
379        /**
380         * Creates a new, empty image with the desired size
381         *
382         * @param int $x
383         * @param int $y
384         * @param string $ext
385         * @return resource GD image handle on success; throws an exception on failure
386         */
387        private function create($x = null, $y = null, $ext = null){
388                if(!$x) $x = $this->meta['width'];
389                if(!$y) $y = $this->meta['height'];
390
391                $image = @imagecreatetruecolor($x, $y);
392                if (!$image) throw new Exception('imagecreatetruecolor_failed');
393                if(!$ext) $ext = $this->meta['ext'];
394                if($ext == 'png'){
395                        if (!@imagealphablending($image, false))
396                                throw new Exception('imagealphablending_failed');
397                        $alpha = @imagecolorallocatealpha($image, 0, 0, 0, 127);
398                        if (!$alpha) throw new Exception('imageallocalpha50pctgrey_failed');
399                        imagefilledrectangle($image, 0, 0, $x, $y, $alpha);
400                }
401
402                return $image;
403        }
404
405        /**
406         * Replaces the image resource with the given parameter
407         *
408         * @param resource $new
409         */
410        private function set($new){
411                if(!empty($this->image)) imagedestroy($this->image);
412                        $this->dirty = true;
413                        $this->image = $new;
414
415                        $this->meta['width'] = imagesx($this->image);
416                        $this->meta['height'] = imagesy($this->image);
417        }
418
419        /**
420         * Returns the path to the image file
421         *
422         * @return string
423         */
424        public function getImagePath(){
425                return $this->file;
426        }
427
428        /**
429         * Returns the resource of the image file
430         *
431         * @return resource
432         */
433        public function getResource(){
434                return $this->image;
435        }
436
437        /**
438         * Rotates the image by the given angle
439         *
440         * @param int $angle
441         * @param array $bgcolor An indexed array with red/green/blue/alpha values
442         * @return resource Image resource on success; throws an exception on failure
443         */
444        public function rotate($angle, $bgcolor = null){
445                if(empty($this->image) || !$angle || $angle>=360) return $this;
446
447                $alpha = (is_array($bgcolor) ? @imagecolorallocatealpha($this->image, $bgcolor[0], $bgcolor[1], $bgcolor[2], !empty($bgcolor[3]) ? $bgcolor[3] : null) : $bgcolor);
448                if (!$alpha) throw new Exception('imagecolorallocatealpha_failed');
449                $img = @imagerotate($this->image, $angle, $alpha);
450                if (!$img) throw new Exception('imagerotate_failed');
451                $this->set($img);
452                unset($img);
453
454                return $this;
455        }
456
457        /**
458         * Resizes the image to the given size, automatically calculates
459         * the new ratio if parameter {@link $ratio} is set to true
460         *
461         * @param int $x the maximum width after resizing has been done
462         * @param int $y the maximum height after resizing has been done
463         * @param bool $ratio set to FALSE if the image ratio is solely to be determined
464         *                    by the $x and $y parameter values; when TRUE (the default)
465         *                    the resize operation will keep the image aspect ratio intact
466         * @param bool $resizeWhenSmaller if FALSE the images will not be resized when
467         *                    already smaller, if TRUE the images will always be resized
468         * @return resource Image resource on success; throws an exception on failure
469         */
470        public function resize($x = null, $y = null, $ratio = true, $resizeWhenSmaller = false)
471        {
472                if(empty($this->image) || (empty($x) && empty($y)))
473                {
474                        throw new Exception('resize_inerr');
475                }
476
477                $dims = Image::calculate_resize_dimensions($this->meta['width'], $this->meta['height'], $x, $y, $ratio, $resizeWhenSmaller);
478                if ($dims === false)
479                {
480                        throw new Exception('resize_inerr:' . $this->meta['width'] . ' x ' . $this->meta['height']);
481                }
482
483                // speedup? only do the resize operation when it must happen:
484                if ($dims['must_resize'])
485                {
486                        $new = $this->create($dims['width'], $dims['height']);
487                        if(@imagecopyresampled($new, $this->image, 0, 0, 0, 0, $dims['width'], $dims['height'], $this->meta['width'], $this->meta['height'])) {
488                                $this->set($new);
489                                unset($new);
490                                return $this;
491                        }
492                        unset($new);
493                        throw new Exception('imagecopyresampled_failed:' . $this->meta['width'] . ' x ' . $this->meta['height']);
494                }
495                else
496                {
497                        return $this;
498                }
499        }
500
501        /**
502         * Crops the image. The values are given like margin/padding values in css
503         *
504         * <b>Example</b>
505         * <ul>
506         * <li>crop(10) - Crops by 10px on all sides</li>
507         * <li>crop(10, 5) - Crops by 10px on top and bottom and by 5px on left and right sides</li>
508         * <li>crop(10, 5, 5) - Crops by 10px on top and by 5px on left, right and bottom sides</li>
509         * <li>crop(10, 5, 3, 2) - Crops by 10px on top, 5px by right, 3px by bottom and 2px by left sides</li>
510         * </ul>
511         *
512         * @param int $top
513         * @param int $right
514         * @param int $bottom
515         * @param int $left
516         * @return Image
517         */
518        public function crop($top, $right = null, $bottom = null, $left = null){
519                if(empty($this->image)) return $this;
520
521                if(!is_numeric($right) && !is_numeric($bottom) && !is_numeric($left))
522                        $right = $bottom = $left = $top;
523
524                if(!is_numeric($bottom) && !is_numeric($left)){
525                        $bottom = $top;
526                        $left = $right;
527                }
528
529                if(!is_numeric($left))
530                        $left = $right;
531
532                $x = $this->meta['width']-$left-$right;
533                $y = $this->meta['height']-$top-$bottom;
534
535                if($x<0 || $y<0) return $this;
536
537                $new = $this->create($x, $y);
538                if (!@imagecopy($new, $this->image, 0, 0, $left, $top, $x, $y))
539                        throw new Exception('imagecopy_failed');
540
541                $this->set($new);
542                unset($new);
543
544                return $this;
545        }
546
547        /**
548         * Flips the image horizontally or vertically. To Flip both copy multiple single pixel strips around instead
549         * of just using ->rotate(180): no image distortion this way.
550         *
551         * @see Image::rotate()
552         * @param string $type Either horizontal or vertical
553         * @return Image
554         */
555        public function flip($type){
556                if(empty($this->image) || !in_array($type, array('horizontal', 'vertical'))) return $this;
557
558                $new = $this->create();
559
560                if($type=='horizontal')
561                {
562                        for($x=0;$x<$this->meta['width'];$x++)
563                        {
564                                if (!@imagecopy($new, $this->image, $this->meta['width']-$x-1, 0, $x, 0, 1, $this->meta['height']))
565                                        throw new Exception('imageflip_failed');
566                        }
567                }
568                elseif($type=='vertical')
569                {
570                        for($y=0;$y<$this->meta['height'];$y++)
571                        {
572                                if (!@imagecopy($new, $this->image, 0, $this->meta['height']-$y-1, 0, $y, $this->meta['width'], 1))
573                                        throw new Exception('imageflip_failed');
574                        }
575                }
576
577                $this->set($new);
578                unset($new);
579
580                return $this;
581        }
582
583        /**
584         * Stores the image in the desired directory or overwrite the old one
585         *
586         * @param string $ext
587         * @param string $file
588         * @param int $quality the amount of lossy compression to apply to the saved file
589         * @param boolean $store_original_if_unaltered (default: FALSE) set to TRUE if you want to copy the
590         *                                             original instead of saving the loaded copy when no
591         *                                             edits to the image have occurred. (set to TRUE when
592         *                                             you like to keep animated GIFs intact when they have
593         *                                             not been cropped, rescaled, etc., for instance)
594         *
595         * @return Image object
596         */
597        public function process($ext = null, $file = null, $quality = 60, $store_original_if_unaltered = true){
598                if(empty($this->image)) return $this;
599
600                if(!$ext) $ext = $this->meta['ext'];
601                $ext = strtolower($ext);
602
603                if($ext=='jpg') $ext = 'jpeg';
604                else if($ext=='png') imagesavealpha($this->image, true);
605
606                if($file == null)
607                        $file = $this->file;
608                if(!$file) throw new Exception('process_nofile');
609                if(!is_dir(dirname($file))) throw new Exception('process_nodir');
610                if ($store_original_if_unaltered && !$this->isDirty() && $ext == $this->meta['ext'])
611                {
612                        // copy original instead of saving the internal representation:
613                        $rv = true;
614                        if ($file != $this->file)
615                        {
616                                $rv = @copy($this->file, $file);
617                        }
618                }
619                else
620                {
621                        $rv = false;
622                        $fn = 'image'.$ext;
623                        if (function_exists($fn))
624                        {
625                                if($ext == 'jpeg')
626                                        $rv = @$fn($this->image, $file, $quality);
627                                elseif($ext == 'png')
628                                        $rv = @$fn($this->image, $file, 9); // PNG is lossless: always maximum compression!
629                                else
630                                        $rv = @$fn($this->image, $file);
631                        }
632                }
633                if (!$rv)
634                        throw new Exception($fn . '_failed');
635
636                // If there is a new filename change the internal name too
637                $this->file = $file;
638
639                return $this;
640        }
641
642        /**
643         * Saves the image to the given path
644         *
645         * @param string $file Leave empty to replace the original file
646         * @param int $quality the amount of lossy compression to apply to the saved file
647         * @param boolean $store_original_if_unaltered (default: FALSE) set to TRUE if you want to copy the
648         *                                             original instead of saving the loaded copy when no
649         *                                             edits to the image have occurred. (set to TRUE when
650         *                                             you like to keep animated GIFs intact when they have
651         *                                             not been cropped, rescaled, etc., for instance)
652         *
653         * @return Image
654         */
655        public function save($file = null, $quality = 60, $store_original_if_unaltered = true){
656                if(empty($this->image)) return $this;
657
658                if(!$file) $file = $this->file;
659
660                $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
661                if(!$ext){
662                        $file .= '.'.$this->meta['ext'];
663                        $ext = $this->meta['ext'];
664                }
665
666                if($ext=='jpg') $ext = 'jpeg';
667
668                return $this->process($ext, $file, $quality, $store_original_if_unaltered);
669        }
670
671        /**
672         * Outputs the manipulated image. Implicitly overwrites the old one on disk.
673         *
674         * @return Image
675         */
676        public function show(){
677                if(empty($this->image)) return $this;
678
679                header('Content-type: '.$this->meta['mime']);
680                return $this->process();
681        }
682}
683
684
685
686
687
688
689if (!function_exists('imagecreatefrombmp'))
690{
691        /**
692         * http://nl3.php.net/manual/en/function.imagecreatefromwbmp.php#86214
693         */
694        function imagecreatefrombmp($filepath)
695        {
696                // Load the image into a string
697                $filesize = @filesize($filepath);
698                if ($filesize < 108 + 4)
699                        return false;
700
701                $read = file_get_contents($filepath);
702                if ($file === false)
703                        return false;
704
705                $temp = unpack("H*",$read);
706                unset($read);               // reduce memory consumption
707                $hex = $temp[1];
708                unset($temp);
709                $header = substr($hex, 0, 108);
710
711                // Process the header
712                // Structure: http://www.fastgraph.com/help/bmp_header_format.html
713                if (substr($header, 0, 4) == '424d')
714                {
715                        // Cut it in parts of 2 bytes
716                        $header_parts = str_split($header, 2);
717
718                        // Get the width        4 bytes
719                        $width = hexdec($header_parts[19] . $header_parts[18]);
720
721                        // Get the height        4 bytes
722                        $height = hexdec($header_parts[23] . $header_parts[22]);
723
724                        // Unset the header params
725                        unset($header_parts);
726                }
727
728                // Define starting X and Y
729                $x = 0;
730                $y = 1;
731
732                // Create newimage
733                $image = imagecreatetruecolor($width, $height);
734                if ($image === false)
735                        return $image;
736
737                // Grab the body from the image
738                $body = substr($hex, 108);
739                unset($hex);
740
741                // Calculate if padding at the end-line is needed
742                // Divided by two to keep overview.
743                // 1 byte = 2 HEX-chars
744                $body_size = strlen($body) / 2;
745                $header_size = $width * $height;
746
747                // Use end-line padding? Only when needed
748                $usePadding = ($body_size > $header_size * 3 + 4);
749
750                // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
751                // Calculate the next DWORD-position in the body
752                for ($i = 0; $i < $body_size; $i += 3)
753                {
754                        // Calculate line-ending and padding
755                        if ($x >= $width)
756                        {
757                                // If padding needed, ignore image-padding
758                                // Shift i to the ending of the current 32-bit-block
759                                if ($usePadding)
760                                        $i += $width % 4;
761
762                                // Reset horizontal position
763                                $x = 0;
764
765                                // Raise the height-position (bottom-up)
766                                $y++;
767
768                                // Reached the image-height? Break the for-loop
769                                if ($y > $height)
770                                        break;
771                        }
772
773                        // Calculation of the RGB-pixel (defined as BGR in image-data)
774                        // Define $i_pos as absolute position in the body
775                        $i_pos = $i * 2;
776                        $r = hexdec($body[$i_pos+4] . $body[$i_pos+5]);
777                        $g = hexdec($body[$i_pos+2] . $body[$i_pos+3]);
778                        $b = hexdec($body[$i_pos] . $body[$i_pos+1]);
779
780                        // Calculate and draw the pixel
781                        $color = imagecolorallocate($image, $r, $g, $b);
782                        if ($color === false)
783                        {
784                                imagedestroy($image);
785                                return false;
786                        }
787                        imagesetpixel($image, $x, $height - $y, $color);
788
789                        // Raise the horizontal position
790                        $x++;
791                }
792
793                // Unset the body / free the memory
794                unset($body);
795
796                // Return image-object
797                return $image;
798        }
799}
Note: See TracBrowser for help on using the repository browser.