| <?php |
| /** |
| * Image - Provides an Interface to the GD-Library for image manipulation |
| * |
| * |
| * @license MIT-style License |
| * @author Christoph Pojer <christoph.pojer@gmail.com> |
| * @author Additions: Fabian Vogelsteller <fabian.vogelsteller@gmail.com> |
| * @author Additions: Ger Hobbelt <ger@hobbelt.com> |
| * |
| * @link http://www.bin-co.com/php/scripts/classes/gd_image/ Based on work by "Binny V A" |
| * |
| * @version 1.12 |
| * Changelog |
| * - 1.12 added memory usage guestimator to warn you when attempting to process overlarge images which will silently but fataly crash PHP |
| * - 1.11 fixed $ratio in resize when both values are given |
| * - 1.1 add real resizing, with comparison of ratio |
| * - 1.01 small fixes in process method and add set memory limit to a higher value |
| */ |
| |
| |
| define('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. |
| |
| class Image { |
| /** |
| * The path to the image file |
| * |
| * @var string |
| */ |
| private $file; |
| /** |
| * The image resource |
| * |
| * @var resource |
| */ |
| private $image; |
| /** |
| * Metadata regarding the image |
| * |
| * @var array |
| */ |
| private $meta; |
| /** |
| * Flags whether the image has been manipulated by this instance in any way and has not yet been saved to disk. |
| */ |
| private $dirty; |
| |
| /** |
| * @param string $file The path to the image file |
| */ |
| public function __construct($file) |
| { |
| $this->dirty = false; |
| |
| $this->meta = self::checkFileForProcessing($file); |
| |
| // only set the new memory limit of IMAGE_PROCESSING_MEMORY_MAX_USAGE MB when the configured one is smaller: |
| if ($this->meta['fileinfo']['memory_limit'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024) |
| { |
| ini_set('memory_limit', IMAGE_PROCESSING_MEMORY_MAX_USAGE . 'M'); // handle large images |
| } |
| |
| $this->file = $file; |
| |
| if($this->meta['ext'] != 'jpeg') |
| { |
| $this->image = $this->create(); |
| |
| $fn = 'imagecreatefrom'.$this->meta['ext']; |
| $original = false; |
| if (function_exists($fn)) |
| { |
| $original = @$fn($file); |
| } |
| if (!$original) throw new Exception('imagecreate_failed:' . $fn); |
| |
| if (!@imagecopyresampled($this->image, $original, 0, 0, 0, 0, $this->meta['width'], $this->meta['height'], $this->meta['width'], $this->meta['height'])) |
| throw new Exception('cvt2truecolor_failed:' . $this->meta['width'] . ' x ' . $this->meta['height']); |
| imagedestroy($original); |
| unset($original); |
| } |
| else |
| { |
| $this->image = @imagecreatefromjpeg($file); |
| if (!$this->image) throw new Exception('imagecreate_failed:imagecreatefromjpeg'); |
| } |
| } |
| |
| public function __destruct(){ |
| if(!empty($this->image)) imagedestroy($this->image); |
| unset($this->image); |
| } |
| |
| /** |
| * Return an array of supported extensions (rather: the second parts of the mime types!) |
| * |
| * A type is listed as 'supported' when it can be READ. |
| */ |
| public static function getSupportedTypes() |
| { |
| static $supported_types; |
| |
| if (empty($supported_types)) |
| { |
| $gdi = gd_info(); |
| |
| $supported_types = array(); |
| if (!empty($gdi['GIF Read Support'])) |
| $supported_types[] = 'gif'; |
| if (!empty($gdi['PNG Support'])) |
| $supported_types[] = 'png'; |
| if (!empty($gdi['JPEG Support']) || !empty($gdi['JPG Support']) /* pre 5.3.0 */ ) |
| $supported_types[] = 'jpeg'; |
| if (!empty($gdi['WBMP Support'])) |
| $supported_types[] = 'wbmp'; |
| if (!empty($gdi['XPM Support'])) |
| $supported_types[] = 'xpm'; |
| if (!empty($gdi['XBM Support'])) |
| $supported_types[] = 'xbm'; |
| $supported_types[] = 'bmp'; |
| } |
| return $supported_types; |
| } |
| |
| /** |
| * Guestimates how much RAM memory must be available to be able to process the given image file. |
| * |
| * @return an array with key,value pairs: 'needed' specifies the guestimated minimum amount of free |
| * memory required for the given file, 'memory_limit' is an integer value representing the total |
| * amount of bytes reserved for PHP script, while 'will_fit' is a boolean which indicates whether |
| * the given image file could potentially be loaded and processed without causing fatal out-of-memory |
| * errors. |
| * The directory separator and path-corrected filespec is returned in the 'path' value. |
| * |
| * @note The given image file must exist on disk; if it does not, 'needed' and 'will_fit' keys will not |
| * be present in the returned array. |
| */ |
| public static function guestimateRequiredMemorySpace($file) |
| { |
| $val = trim(ini_get('memory_limit')); |
| $last = strtoupper(substr($val, -1, 1)); |
| $val = floatval($val); // discards the KMG postfix, allow $val to carry values beyond 2..4G |
| switch($last) |
| { |
| // The 'G' modifier is available since PHP 5.1.0 |
| case 'G': |
| $val *= 1024.0; |
| case 'M': |
| $val *= 1024.0; |
| case 'K': |
| $val *= 1024.0; |
| break; |
| } |
| $limit = $val; |
| |
| $in_use = (function_exists('memory_get_usage') ? memory_get_usage() : 1000000 /* take a wild guess, er, excuse me, 'apply a heuristic' */ ); |
| |
| $rv = array( |
| 'memory_limit' => $limit, |
| 'mem_in_use' => $in_use, |
| 'path' => $file |
| ); |
| |
| if(file_exists($file)) |
| { |
| $raw_size = @filesize($file); |
| $rv['filesize'] = $raw_size; |
| |
| $img = @getimagesize($file, $info_ex); |
| if ($img) |
| { |
| $width = $img[0]; |
| $height = $img[1]; |
| $rv['imageinfo'] = $img; |
| $rv['imageinfo_extra'] = $info_ex; |
| |
| // assume RGBA8, i.e. 4 bytes per pixel |
| // ... having had a good look with memory_get_usage() and memory_get_peak_usage(), it turns out we need |
| // a 'fudge factor' a.k.a. heuristic as the '4 bytes per pixel' estimate is off by quite a bit (if we have |
| // to believe the numbers following a single GD image load operation): |
| $needed = 4.0 * $width * $height; |
| $needed *= 34.0 / 27.0; |
| $rv['needed'] = $needed; |
| |
| // since many operations require a source and destination buffer, that'll be 2 of them buffers, thank you very much: |
| // ... however, having had a good look with memory_get_usage() and memory_get_peak_usage(), it turns out |
| // we need about triple! |
| $will_eat = $needed * 2.8; |
| // ^^^ factor = 2.8 : for very large images the estimation error is now ~ +1..8% too pessimistic. Soit! |
| // (tested with PNG images which consumed up to 475MB RAM to have their thumbnail created. This took a few |
| // seconds per image, so you might ask yourself if being able to serve such memory megalodons would be, |
| // ah, desirable, when considered from this perspective.) |
| |
| // and 'worst case' (ahem) we've got the file itself loaded in memory as well (on initial load and later save): |
| // ... but this is more than covered by the 'triple charge' already, so we ditch this one from the heuristics. |
| if (0) $will_eat += $raw_size; |
| |
| // interestingly, JPEG files only appear to require about half that space required by PNG resize processes... |
| if (!empty($img['mime']) && $img['mime'] == 'image/jpeg') |
| { |
| $will_eat /= 2.0; |
| } |
| |
| $rv['usage_guestimate'] = $will_eat; |
| |
| // now we know what we about need for this bugger, see if we got enough: |
| $does_fit = ($limit - $in_use > $will_eat); |
| $rv['usage_min_advised'] = $will_eat + $in_use; |
| $rv['will_fit'] = $does_fit; |
| } |
| else |
| { |
| // else: this is not a valid image file! |
| $rv['not_an_image_file'] = true; |
| } |
| } |
| else |
| { |
| // else: this file does not exist! |
| $rv['not_an_image_file'] = true; |
| } |
| return $rv; |
| } |
| |
| /** |
| * Check whether the given file is really an image file and whether it can be processed by our Image class, given the PHP |
| * memory restrictions. |
| * |
| * Return the meta data array when the expectation is that the given file can be processed; throw an exception otherwise. |
| */ |
| public static function checkFileForProcessing($file) |
| { |
| $finfo = self::guestimateRequiredMemorySpace($file); |
| if (!empty($finfo['not_an_image_file'])) |
| throw new Exception('no_imageinfo'); |
| |
| // is it a valid file existing on disk? |
| if (!isset($finfo['imageinfo'])) |
| throw new Exception('no_imageinfo'); |
| |
| $file = $finfo['path']; |
| |
| // only set the new memory limit of IMAGE_PROCESSING_MEMORY_MAX_USAGE MB when the configured one is smaller: |
| if ($finfo['memory_limit'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024) |
| { |
| // recalc the 'will_fit' indicator now: |
| $finfo['will_fit'] = ($finfo['usage_min_advised'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024); |
| } |
| |
| $img = $finfo['imageinfo']; |
| |
| // and will it fit in available memory if we go and load the bugger? |
| if (!$finfo['will_fit']) |
| throw new Exception('img_will_not_fit:' . ceil($finfo['usage_min_advised'] / 1E6) . ' MByte'); |
| |
| $explarr = explode('/', $img['mime']); // make sure the end() call doesn't throw an error next in E_STRICT mode: |
| $ext_from_mime = end($explarr); |
| $meta = array( |
| 'width' => $img[0], |
| 'height' => $img[1], |
| 'mime' => $img['mime'], |
| 'ext' => $ext_from_mime, |
| 'fileinfo' => $finfo |
| ); |
| |
| if($meta['ext'] == 'jpg') |
| $meta['ext'] = 'jpeg'; |
| else if($meta['ext'] == 'bmp') |
| $meta['ext'] = 'bmp'; |
| else if($meta['ext'] == 'x-ms-bmp') |
| $meta['ext'] = 'bmp'; |
| if(!in_array($meta['ext'], self::getSupportedTypes())) |
| throw new Exception('unsupported_imgfmt:' . $meta['ext']); |
| |
| return $meta; |
| } |
| |
| /** |
| * Calculate the resize dimensions of an image, given the original dimensions and size limits |
| * |
| * @param int $orig_x the original's width |
| * @param int $orig_y the original's height |
| * @param int $x the maximum width after resizing has been done |
| * @param int $y the maximum height after resizing has been done |
| * @param bool $ratio set to FALSE if the image ratio is solely to be determined |
| * by the $x and $y parameter values; when TRUE (the default) |
| * the resize operation will keep the image aspect ratio intact |
| * @param bool $resizeWhenSmaller if FALSE the images will not be resized when |
| * already smaller, if TRUE the images will always be resized |
| * @return array with 'width', 'height' and 'must_resize' component values on success; FALSE on error |
| */ |
| public static function calculate_resize_dimensions($orig_x, $orig_y, $x = null, $y = null, $ratio = true, $resizeWhenSmaller = false) |
| { |
| if(empty($orig_x) || empty($orig_y) || (empty($x) && empty($y))) return false; |
| |
| $xStart = $x; |
| $yStart = $y; |
| $ratioX = $orig_x / $orig_y; |
| $ratioY = $orig_y / $orig_x; |
| $ratio |= (empty($y) || empty($x)); // keep ratio when only width OR height is set |
| //echo 'ALLOWED: <br>'.$xStart.'x'."<br>".$yStart.'y'."<br>---------------<br>"; |
| // ->> keep the RATIO |
| if($ratio) { |
| //echo 'BEGINN: <br>'.$orig_x.'x'."<br>".$orig_y.'y'."<br><br>"; |
| // -> align to WIDTH |
| if(!empty($x) && ($x < $orig_x || $resizeWhenSmaller)) |
| $y = $x / $ratioX; |
| // -> align to HEIGHT |
| elseif(!empty($y) && ($y < $orig_y || $resizeWhenSmaller)) |
| $x = $y / $ratioY; |
| else { |
| $y = $orig_y; |
| $x = $orig_x; |
| } |
| //echo 'BET: <br>'.$x.'x'."<br>".$y.'y'."<br><br>"; |
| // ->> align to WIDTH AND HEIGHT |
| if((!empty($yStart) && $y > $yStart) || (!empty($xStart) && $x > $xStart)) { |
| if($y > $yStart) { |
| $y = $yStart; |
| $x = $y / $ratioY; |
| } elseif($x > $xStart) { |
| $x = $xStart; |
| $y = $x / $ratioX; |
| } |
| } |
| } |
| // else: ->> DONT keep the RATIO |
| |
| $x = round($x); |
| $y = round($y); |
| |
| //echo 'END: <br>'.$x.'x'."<br>".$y.'y'."<br><br>"; |
| $rv = array( |
| 'width' => $x, |
| 'height' => $y, |
| 'must_resize' => false |
| ); |
| // speedup? only do the resize operation when it must happen: |
| if ($x != $orig_x || $y != $orig_y) |
| { |
| $rv['must_resize'] = true; |
| } |
| return $rv; |
| } |
| |
| /** |
| * Returns the size of the image |
| * |
| * @return array |
| */ |
| public function getSize(){ |
| return array( |
| 'width' => $this->meta['width'], |
| 'height' => $this->meta['height'], |
| ); |
| } |
| |
| |
| /** |
| * Returns a copy of the meta information of the image |
| * |
| * @return array |
| */ |
| public function getMetaInfo(){ |
| return array_merge(array(), (is_array($this->meta) ? $this->meta : array())); |
| } |
| |
| |
| /** |
| * Returns TRUE when the image data have been altered by this instance's operations, FALSE when the content has not (yet) been touched. |
| * |
| * @return boolean |
| */ |
| public function isDirty(){ |
| return $this->dirty; |
| } |
| |
| |
| /** |
| * Creates a new, empty image with the desired size |
| * |
| * @param int $x |
| * @param int $y |
| * @param string $ext |
| * @return resource GD image handle on success; throws an exception on failure |
| */ |
| private function create($x = null, $y = null, $ext = null){ |
| if(!$x) $x = $this->meta['width']; |
| if(!$y) $y = $this->meta['height']; |
| |
| $image = @imagecreatetruecolor($x, $y); |
| if (!$image) throw new Exception('imagecreatetruecolor_failed'); |
| if(!$ext) $ext = $this->meta['ext']; |
| if($ext == 'png'){ |
| if (!@imagealphablending($image, false)) |
| throw new Exception('imagealphablending_failed'); |
| $alpha = @imagecolorallocatealpha($image, 0, 0, 0, 127); |
| if (!$alpha) throw new Exception('imageallocalpha50pctgrey_failed'); |
| imagefilledrectangle($image, 0, 0, $x, $y, $alpha); |
| } |
| |
| return $image; |
| } |
| |
| /** |
| * Replaces the image resource with the given parameter |
| * |
| * @param resource $new |
| */ |
| private function set($new){ |
| if(!empty($this->image)) imagedestroy($this->image); |
| $this->dirty = true; |
| $this->image = $new; |
| |
| $this->meta['width'] = imagesx($this->image); |
| $this->meta['height'] = imagesy($this->image); |
| } |
| |
| /** |
| * Returns the path to the image file |
| * |
| * @return string |
| */ |
| public function getImagePath(){ |
| return $this->file; |
| } |
| |
| /** |
| * Returns the resource of the image file |
| * |
| * @return resource |
| */ |
| public function getResource(){ |
| return $this->image; |
| } |
| |
| /** |
| * Rotates the image by the given angle |
| * |
| * @param int $angle |
| * @param array $bgcolor An indexed array with red/green/blue/alpha values |
| * @return resource Image resource on success; throws an exception on failure |
| */ |
| public function rotate($angle, $bgcolor = null){ |
| if(empty($this->image) || !$angle || $angle>=360) return $this; |
| |
| $alpha = (is_array($bgcolor) ? @imagecolorallocatealpha($this->image, $bgcolor[0], $bgcolor[1], $bgcolor[2], !empty($bgcolor[3]) ? $bgcolor[3] : null) : $bgcolor); |
| if (!$alpha) throw new Exception('imagecolorallocatealpha_failed'); |
| $img = @imagerotate($this->image, $angle, $alpha); |
| if (!$img) throw new Exception('imagerotate_failed'); |
| $this->set($img); |
| unset($img); |
| |
| return $this; |
| } |
| |
| /** |
| * Resizes the image to the given size, automatically calculates |
| * the new ratio if parameter {@link $ratio} is set to true |
| * |
| * @param int $x the maximum width after resizing has been done |
| * @param int $y the maximum height after resizing has been done |
| * @param bool $ratio set to FALSE if the image ratio is solely to be determined |
| * by the $x and $y parameter values; when TRUE (the default) |
| * the resize operation will keep the image aspect ratio intact |
| * @param bool $resizeWhenSmaller if FALSE the images will not be resized when |
| * already smaller, if TRUE the images will always be resized |
| * @return resource Image resource on success; throws an exception on failure |
| */ |
| public function resize($x = null, $y = null, $ratio = true, $resizeWhenSmaller = false) |
| { |
| if(empty($this->image) || (empty($x) && empty($y))) |
| { |
| throw new Exception('resize_inerr'); |
| } |
| |
| $dims = Image::calculate_resize_dimensions($this->meta['width'], $this->meta['height'], $x, $y, $ratio, $resizeWhenSmaller); |
| if ($dims === false) |
| { |
| throw new Exception('resize_inerr:' . $this->meta['width'] . ' x ' . $this->meta['height']); |
| } |
| |
| // speedup? only do the resize operation when it must happen: |
| if ($dims['must_resize']) |
| { |
| $new = $this->create($dims['width'], $dims['height']); |
| if(@imagecopyresampled($new, $this->image, 0, 0, 0, 0, $dims['width'], $dims['height'], $this->meta['width'], $this->meta['height'])) { |
| $this->set($new); |
| unset($new); |
| return $this; |
| } |
| unset($new); |
| throw new Exception('imagecopyresampled_failed:' . $this->meta['width'] . ' x ' . $this->meta['height']); |
| } |
| else |
| { |
| return $this; |
| } |
| } |
| |
| /** |
| * Crops the image. The values are given like margin/padding values in css |
| * |
| * <b>Example</b> |
| * <ul> |
| * <li>crop(10) - Crops by 10px on all sides</li> |
| * <li>crop(10, 5) - Crops by 10px on top and bottom and by 5px on left and right sides</li> |
| * <li>crop(10, 5, 5) - Crops by 10px on top and by 5px on left, right and bottom sides</li> |
| * <li>crop(10, 5, 3, 2) - Crops by 10px on top, 5px by right, 3px by bottom and 2px by left sides</li> |
| * </ul> |
| * |
| * @param int $top |
| * @param int $right |
| * @param int $bottom |
| * @param int $left |
| * @return Image |
| */ |
| public function crop($top, $right = null, $bottom = null, $left = null){ |
| if(empty($this->image)) return $this; |
| |
| if(!is_numeric($right) && !is_numeric($bottom) && !is_numeric($left)) |
| $right = $bottom = $left = $top; |
| |
| if(!is_numeric($bottom) && !is_numeric($left)){ |
| $bottom = $top; |
| $left = $right; |
| } |
| |
| if(!is_numeric($left)) |
| $left = $right; |
| |
| $x = $this->meta['width']-$left-$right; |
| $y = $this->meta['height']-$top-$bottom; |
| |
| if($x<0 || $y<0) return $this; |
| |
| $new = $this->create($x, $y); |
| if (!@imagecopy($new, $this->image, 0, 0, $left, $top, $x, $y)) |
| throw new Exception('imagecopy_failed'); |
| |
| $this->set($new); |
| unset($new); |
| |
| return $this; |
| } |
| |
| /** |
| * Flips the image horizontally or vertically. To Flip both copy multiple single pixel strips around instead |
| * of just using ->rotate(180): no image distortion this way. |
| * |
| * @see Image::rotate() |
| * @param string $type Either horizontal or vertical |
| * @return Image |
| */ |
| public function flip($type){ |
| if(empty($this->image) || !in_array($type, array('horizontal', 'vertical'))) return $this; |
| |
| $new = $this->create(); |
| |
| if($type=='horizontal') |
| { |
| for($x=0;$x<$this->meta['width'];$x++) |
| { |
| if (!@imagecopy($new, $this->image, $this->meta['width']-$x-1, 0, $x, 0, 1, $this->meta['height'])) |
| throw new Exception('imageflip_failed'); |
| } |
| } |
| elseif($type=='vertical') |
| { |
| for($y=0;$y<$this->meta['height'];$y++) |
| { |
| if (!@imagecopy($new, $this->image, 0, $this->meta['height']-$y-1, 0, $y, $this->meta['width'], 1)) |
| throw new Exception('imageflip_failed'); |
| } |
| } |
| |
| $this->set($new); |
| unset($new); |
| |
| return $this; |
| } |
| |
| /** |
| * Stores the image in the desired directory or overwrite the old one |
| * |
| * @param string $ext |
| * @param string $file |
| * @param int $quality the amount of lossy compression to apply to the saved file |
| * @param boolean $store_original_if_unaltered (default: FALSE) set to TRUE if you want to copy the |
| * original instead of saving the loaded copy when no |
| * edits to the image have occurred. (set to TRUE when |
| * you like to keep animated GIFs intact when they have |
| * not been cropped, rescaled, etc., for instance) |
| * |
| * @return Image object |
| */ |
| public function process($ext = null, $file = null, $quality = 60, $store_original_if_unaltered = true){ |
| if(empty($this->image)) return $this; |
| |
| if(!$ext) $ext = $this->meta['ext']; |
| $ext = strtolower($ext); |
| |
| if($ext=='jpg') $ext = 'jpeg'; |
| else if($ext=='png') imagesavealpha($this->image, true); |
| |
| if($file == null) |
| $file = $this->file; |
| if(!$file) throw new Exception('process_nofile'); |
| if(!is_dir(dirname($file))) throw new Exception('process_nodir'); |
| if ($store_original_if_unaltered && !$this->isDirty() && $ext == $this->meta['ext']) |
| { |
| // copy original instead of saving the internal representation: |
| $rv = true; |
| if ($file != $this->file) |
| { |
| $rv = @copy($this->file, $file); |
| } |
| } |
| else |
| { |
| $rv = false; |
| $fn = 'image'.$ext; |
| if (function_exists($fn)) |
| { |
| if($ext == 'jpeg') |
| $rv = @$fn($this->image, $file, $quality); |
| elseif($ext == 'png') |
| $rv = @$fn($this->image, $file, 9); // PNG is lossless: always maximum compression! |
| else |
| $rv = @$fn($this->image, $file); |
| } |
| } |
| if (!$rv) |
| throw new Exception($fn . '_failed'); |
| |
| // If there is a new filename change the internal name too |
| $this->file = $file; |
| |
| return $this; |
| } |
| |
| /** |
| * Saves the image to the given path |
| * |
| * @param string $file Leave empty to replace the original file |
| * @param int $quality the amount of lossy compression to apply to the saved file |
| * @param boolean $store_original_if_unaltered (default: FALSE) set to TRUE if you want to copy the |
| * original instead of saving the loaded copy when no |
| * edits to the image have occurred. (set to TRUE when |
| * you like to keep animated GIFs intact when they have |
| * not been cropped, rescaled, etc., for instance) |
| * |
| * @return Image |
| */ |
| public function save($file = null, $quality = 60, $store_original_if_unaltered = true){ |
| if(empty($this->image)) return $this; |
| |
| if(!$file) $file = $this->file; |
| |
| $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); |
| if(!$ext){ |
| $file .= '.'.$this->meta['ext']; |
| $ext = $this->meta['ext']; |
| } |
| |
| if($ext=='jpg') $ext = 'jpeg'; |
| |
| return $this->process($ext, $file, $quality, $store_original_if_unaltered); |
| } |
| |
| /** |
| * Outputs the manipulated image. Implicitly overwrites the old one on disk. |
| * |
| * @return Image |
| */ |
| public function show(){ |
| if(empty($this->image)) return $this; |
| |
| header('Content-type: '.$this->meta['mime']); |
| return $this->process(); |
| } |
| } |
| |
| |
| |
| |
| |
| |
| if (!function_exists('imagecreatefrombmp')) |
| { |
| /** |
| * http://nl3.php.net/manual/en/function.imagecreatefromwbmp.php#86214 |
| */ |
| function imagecreatefrombmp($filepath) |
| { |
| // Load the image into a string |
| $filesize = @filesize($filepath); |
| if ($filesize < 108 + 4) |
| return false; |
| |
| $read = file_get_contents($filepath); |
| if ($file === false) |
| return false; |
| |
| $temp = unpack("H*",$read); |
| unset($read); // reduce memory consumption |
| $hex = $temp[1]; |
| unset($temp); |
| $header = substr($hex, 0, 108); |
| |
| // Process the header |
| // Structure: http://www.fastgraph.com/help/bmp_header_format.html |
| if (substr($header, 0, 4) == '424d') |
| { |
| // Cut it in parts of 2 bytes |
| $header_parts = str_split($header, 2); |
| |
| // Get the width 4 bytes |
| $width = hexdec($header_parts[19] . $header_parts[18]); |
| |
| // Get the height 4 bytes |
| $height = hexdec($header_parts[23] . $header_parts[22]); |
| |
| // Unset the header params |
| unset($header_parts); |
| } |
| |
| // Define starting X and Y |
| $x = 0; |
| $y = 1; |
| |
| // Create newimage |
| $image = imagecreatetruecolor($width, $height); |
| if ($image === false) |
| return $image; |
| |
| // Grab the body from the image |
| $body = substr($hex, 108); |
| unset($hex); |
| |
| // Calculate if padding at the end-line is needed |
| // Divided by two to keep overview. |
| // 1 byte = 2 HEX-chars |
| $body_size = strlen($body) / 2; |
| $header_size = $width * $height; |
| |
| // Use end-line padding? Only when needed |
| $usePadding = ($body_size > $header_size * 3 + 4); |
| |
| // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption |
| // Calculate the next DWORD-position in the body |
| for ($i = 0; $i < $body_size; $i += 3) |
| { |
| // Calculate line-ending and padding |
| if ($x >= $width) |
| { |
| // If padding needed, ignore image-padding |
| // Shift i to the ending of the current 32-bit-block |
| if ($usePadding) |
| $i += $width % 4; |
| |
| // Reset horizontal position |
| $x = 0; |
| |
| // Raise the height-position (bottom-up) |
| $y++; |
| |
| // Reached the image-height? Break the for-loop |
| if ($y > $height) |
| break; |
| } |
| |
| // Calculation of the RGB-pixel (defined as BGR in image-data) |
| // Define $i_pos as absolute position in the body |
| $i_pos = $i * 2; |
| $r = hexdec($body[$i_pos+4] . $body[$i_pos+5]); |
| $g = hexdec($body[$i_pos+2] . $body[$i_pos+3]); |
| $b = hexdec($body[$i_pos] . $body[$i_pos+1]); |
| |
| // Calculate and draw the pixel |
| $color = imagecolorallocate($image, $r, $g, $b); |
| if ($color === false) |
| { |
| imagedestroy($image); |
| return false; |
| } |
| imagesetpixel($image, $x, $height - $y, $color); |
| |
| // Raise the horizontal position |
| $x++; |
| } |
| |
| // Unset the body / free the memory |
| unset($body); |
| |
| // Return image-object |
| return $image; |
| } |
| } |