2 namespace Utility\Upload;
7 * jQuery File Upload Plugin PHP Class 5.18.3
8 * https://github.com/blueimp/jQuery-File-Upload
10 * Copyright 2010, Sebastian Tschan
13 * Licensed under the MIT license:
14 * http://www.opensource.org/licenses/MIT
20 // PHP File Upload error message codes:
21 // http://php.net/manual/en/features.file-upload.errors.php
22 protected $error_messages = array(
23 1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
24 2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
25 3 => 'The uploaded file was only partially uploaded',
26 4 => 'No file was uploaded',
27 6 => 'Missing a temporary folder',
28 7 => 'Failed to write file to disk',
29 8 => 'A PHP extension stopped the file upload',
30 'post_max_size' => 'The uploaded file exceeds the post_max_size directive in php.ini',
31 'max_file_size' => 'File is too big',
32 'min_file_size' => 'File is too small',
33 'accept_file_types' => 'Filetype not allowed',
34 'max_number_of_files' => 'Maximum number of files exceeded',
35 'max_width' => 'Image exceeds maximum width',
36 'min_width' => 'Image requires a minimum width',
37 'max_height' => 'Image exceeds maximum height',
38 'min_height' => 'Image requires a minimum height'
41 function __construct($options = null, $initialize = true) {
42 $this->options = array(
43 'script_url' => $this->get_full_url().'/',
44 'upload_dir' => \Utility\Registry::getConfigParam('ImagePath'),
45 'upload_url' => \Utility\Registry::getConfigParam('ImageUrl'),
48 'param_name' => 'files',
49 // Set the following option to 'POST', if your server does not support
50 // DELETE requests. This is a parameter sent to the client:
51 'delete_type' => 'DELETE',
52 'access_control_allow_origin' => '*',
53 'access_control_allow_credentials' => false,
54 'access_control_allow_methods' => array(
62 'access_control_allow_headers' => array(
65 'Content-Disposition',
68 // Enable to provide file downloads via GET requests to the PHP script:
69 'download_via_php' => false,
70 // Defines which files can be displayed inline when downloaded:
71 'inline_file_types' => '/\.(gif|jpe?g|png)$/i',
72 // Defines which files (based on their names) are accepted for upload:
73 'accept_file_types' => '/.+$/i',
74 // The php.ini settings upload_max_filesize and post_max_size
75 // take precedence over the following max_file_size setting:
76 'max_file_size' => null,
78 // The maximum number of files for the upload directory:
79 'max_number_of_files' => null,
80 // Image resolution restrictions:
85 // Set the following option to false to enable resumable uploads:
86 'discard_aborted_uploads' => true,
87 // Set to true to rotate images based on EXIF meta data, if available:
88 'orient_image' => false,
89 'image_versions' => array(
90 // Restrict the size of uploaded images:
96 // Uncomment the following to create medium sized images:
105 $this->options = array_merge($this->options, $options);
113 * Download external file
114 * @param string $sourceUri
115 * @param string $destinationPath
118 public function externalDownload($sourceUri)
121 $destinationPath = '/tmp/' . sha1( 'ext_' . time() . rand(1, 50000) ) . '.ext';
122 $fp = fopen ($destinationPath, 'w+');
126 curl_setopt( $ch, CURLOPT_URL, str_replace(" ", "%20", $sourceUri) );
129 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
130 curl_setopt( $ch, CURLOPT_BINARYTRANSFER, true );
131 curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
134 * Increase timeout to download big file
136 curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 180 );
139 * Write data to local file
141 curl_setopt( $ch, CURLOPT_FILE, $fp );
147 $fileDownloaded = filesize($destinationPath) > 0 ? true : false;
149 if(filesize($destinationPath) > 0)
151 $fileName = array_pop(explode('/', $sourceUri));
153 $file = new \stdClass();
155 $file->name = $fileName;
156 $file->size = filesize($destinationPath);
158 $finfo = finfo_open(FILEINFO_MIME_TYPE);
159 $file->type = finfo_file($finfo, $destinationPath);
163 $em = \Utility\Registry::getEntityManager();
164 $oImage = new \Utility\Entity\Image();
165 $oImage->filename = $file->name;
166 $oImage->mimeType = $file->type;
167 $em->persist($oImage);
171 $file->id = $oImage->id;
173 $file->name = $oImage->id . '.' . array_pop(explode('.', $file->name));
174 $oImage->filename = $file->name;
177 $moved = rename($destinationPath, $this->options['upload_dir'] . $oImage->filename);
179 foreach($this->options['image_versions'] as $version => $options)
181 $this->create_scaled_image($file->name, $version, $options);
188 \Utility\Debug::errorLog('File not downloaded', $sourceUri);
189 throw new \Exception('File not downloaded');
194 protected function initialize() {
195 switch ($_SERVER['REQUEST_METHOD']) {
210 header('HTTP/1.1 405 Method Not Allowed');
214 protected function get_full_url() {
215 $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
217 ($https ? 'https://' : 'http://').
218 (!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
219 (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
220 ($https && $_SERVER['SERVER_PORT'] === 443 ||
221 $_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
222 substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
225 protected function get_user_id() {
230 protected function get_user_path() {
231 if ($this->options['user_dirs']) {
232 return $this->get_user_id().'/';
237 protected function get_upload_path($file_name = null, $version = null) {
238 $file_name = $file_name ? $file_name : '';
239 $version_path = empty($version) ? '' : $version.'/';
240 return $this->options['upload_dir'].$this->get_user_path()
241 .$version_path.$file_name;
244 protected function get_download_url($file_name, $version = null) {
245 if ($this->options['download_via_php']) {
246 $url = $this->options['script_url'].'?file='.rawurlencode($file_name);
248 $url .= '&version='.rawurlencode($version);
250 return $url.'&download=1';
252 $version_path = empty($version) ? '' : rawurlencode($version).'/';
253 return $this->options['upload_url'].$this->get_user_path()
254 .$version_path.rawurlencode($file_name);
257 protected function set_file_delete_properties($file) {
258 $file->delete_url = $this->options['script_url']
259 .'?file='.rawurlencode($file->name);
260 $file->delete_type = $this->options['delete_type'];
261 if ($file->delete_type !== 'DELETE') {
262 $file->delete_url .= '&_method=DELETE';
264 if ($this->options['access_control_allow_credentials']) {
265 $file->delete_with_credentials = true;
269 // Fix for overflowing signed 32 bit integers,
270 // works for sizes up to 2^32-1 bytes (4 GiB - 1):
271 protected function fix_integer_overflow($size) {
273 $size += 2.0 * (PHP_INT_MAX + 1);
278 protected function get_file_size($file_path, $clear_stat_cache = false) {
279 if ($clear_stat_cache) {
282 return $this->fix_integer_overflow(@filesize($file_path));
286 protected function is_valid_file_object($file_name) {
287 $file_path = $this->get_upload_path($file_name);
288 if (is_file($file_path) && $file_name[0] !== '.') {
294 protected function get_file_object($file_name) {
295 if ($this->is_valid_file_object($file_name)) {
296 $file = new \stdClass();
297 $file->name = $file_name;
298 $file->size = $this->get_file_size(
299 $this->get_upload_path($file_name)
301 $file->url = $this->get_download_url($file->name);
302 foreach($this->options['image_versions'] as $version => $options) {
303 if (!empty($version)) {
304 if (is_file($this->get_upload_path($file_name, $version))) {
305 $file->{$version.'_url'} = $this->get_download_url(
312 $this->set_file_delete_properties($file);
318 protected function get_file_objects($iteration_method = 'get_file_object') {
319 $upload_dir = $this->get_upload_path();
320 if (!is_dir($upload_dir)) {
321 mkdir($upload_dir, $this->options['mkdir_mode']);
323 return array_values(array_filter(array_map(
324 array($this, $iteration_method),
329 protected function count_file_objects() {
330 return count($this->get_file_objects('is_valid_file_object'));
333 protected function create_scaled_image($file_name, $version, $options) {
334 $file_path = $this->get_upload_path($file_name);
335 if (!empty($version)) {
336 $version_dir = $this->get_upload_path(null, $version);
337 if (!is_dir($version_dir)) {
338 mkdir($version_dir, $this->options['mkdir_mode']);
340 $new_file_path = $version_dir.'/'.$file_name;
342 $new_file_path = $file_path;
344 list($img_width, $img_height) = @getimagesize($file_path);
345 if (!$img_width || !$img_height) {
349 $options['max_width'] / $img_width,
350 $options['max_height'] / $img_height
353 if ($file_path !== $new_file_path) {
354 return copy($file_path, $new_file_path);
358 $new_width = $img_width * $scale;
359 $new_height = $img_height * $scale;
360 $new_img = @imagecreatetruecolor($new_width, $new_height);
361 switch (strtolower(substr(strrchr($file_name, '.'), 1))) {
364 $src_img = @imagecreatefromjpeg($file_path);
365 $write_image = 'imagejpeg';
366 $image_quality = isset($options['jpeg_quality']) ?
367 $options['jpeg_quality'] : 75;
370 @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
371 $src_img = @imagecreatefromgif($file_path);
372 $write_image = 'imagegif';
373 $image_quality = null;
376 @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
377 @imagealphablending($new_img, false);
378 @imagesavealpha($new_img, true);
379 $src_img = @imagecreatefrompng($file_path);
380 $write_image = 'imagepng';
381 $image_quality = isset($options['png_quality']) ?
382 $options['png_quality'] : 9;
387 $success = $src_img && @imagecopyresampled(
395 ) && $write_image($new_img, $new_file_path, $image_quality);
396 // Free up memory (imagedestroy does not delete files):
397 @imagedestroy($src_img);
398 @imagedestroy($new_img);
402 protected function get_error_message($error) {
403 return array_key_exists($error, $this->error_messages) ?
404 $this->error_messages[$error] : $error;
407 function get_config_bytes($val) {
409 $last = strtolower($val[strlen($val)-1]);
418 return $this->fix_integer_overflow($val);
421 protected function validate($uploaded_file, $file, $error, $index) {
423 $file->error = $this->get_error_message($error);
426 $content_length = $this->fix_integer_overflow(intval($_SERVER['CONTENT_LENGTH']));
427 if ($content_length > $this->get_config_bytes(ini_get('post_max_size'))) {
428 $file->error = $this->get_error_message('post_max_size');
431 if (!preg_match($this->options['accept_file_types'], $file->name)) {
432 $file->error = $this->get_error_message('accept_file_types');
435 if ($uploaded_file && is_uploaded_file($uploaded_file)) {
436 $file_size = $this->get_file_size($uploaded_file);
438 $file_size = $content_length;
440 if ($this->options['max_file_size'] && (
441 $file_size > $this->options['max_file_size'] ||
442 $file->size > $this->options['max_file_size'])
444 $file->error = $this->get_error_message('max_file_size');
447 if ($this->options['min_file_size'] &&
448 $file_size < $this->options['min_file_size']) {
449 $file->error = $this->get_error_message('min_file_size');
452 if (is_int($this->options['max_number_of_files']) && (
453 $this->count_file_objects() >= $this->options['max_number_of_files'])
455 $file->error = $this->get_error_message('max_number_of_files');
458 list($img_width, $img_height) = @getimagesize($uploaded_file);
459 if (is_int($img_width)) {
460 if ($this->options['max_width'] && $img_width > $this->options['max_width']) {
461 $file->error = $this->get_error_message('max_width');
464 if ($this->options['max_height'] && $img_height > $this->options['max_height']) {
465 $file->error = $this->get_error_message('max_height');
468 if ($this->options['min_width'] && $img_width < $this->options['min_width']) {
469 $file->error = $this->get_error_message('min_width');
472 if ($this->options['min_height'] && $img_height < $this->options['min_height']) {
473 $file->error = $this->get_error_message('min_height');
480 protected function upcount_name_callback($matches) {
481 $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1;
482 $ext = isset($matches[2]) ? $matches[2] : '';
483 return ' ('.$index.')'.$ext;
486 protected function upcount_name($name) {
487 return preg_replace_callback(
488 '/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/',
489 array($this, 'upcount_name_callback'),
495 protected function trim_file_name($name, $type, $index, $content_range) {
496 // Remove path information and dots around the filename, to prevent uploading
497 // into different directories or replacing hidden system files.
498 // Also remove control characters and spaces (\x00..\x20) around the filename:
499 $file_name = trim(basename(stripslashes($name)), ".\x00..\x20");
500 // Add missing file extension for known image types:
501 if (strpos($file_name, '.') === false &&
502 preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
503 $file_name .= '.'.$matches[1];
505 while(is_dir($this->get_upload_path($file_name))) {
506 $file_name = $this->upcount_name($file_name);
508 $uploaded_bytes = $this->fix_integer_overflow(intval($content_range[1]));
509 while(is_file($this->get_upload_path($file_name))) {
510 if ($uploaded_bytes === $this->get_file_size(
511 $this->get_upload_path($file_name))) {
514 $file_name = $this->upcount_name($file_name);
519 protected function handle_form_data($file, $index) {
520 // Handle form data, e.g. $_REQUEST['description'][$index]
523 protected function orient_image($file_path) {
524 $exif = @exif_read_data($file_path);
525 if ($exif === false) {
528 $orientation = intval(@$exif['Orientation']);
529 if (!in_array($orientation, array(3, 6, 8))) {
532 $image = @imagecreatefromjpeg($file_path);
533 switch ($orientation) {
535 $image = @imagerotate($image, 180, 0);
538 $image = @imagerotate($image, 270, 0);
541 $image = @imagerotate($image, 90, 0);
546 $success = imagejpeg($image, $file_path);
547 // Free up memory (imagedestroy does not delete files):
548 @imagedestroy($image);
552 protected function handle_file_upload($uploaded_file, $name, $size, $type, $error,
553 $index = null, $content_range = null) {
554 $file = new \stdClass();
555 $file->name = $this->trim_file_name($name, $type, $index, $content_range);
556 $file->size = $this->fix_integer_overflow(intval($size));
558 $em = \Utility\Registry::getEntityManager();
559 $oImage = new \Utility\Entity\Image();
560 $oImage->filename = $file->name;
561 $oImage->mimeType = $type;
562 $em->persist($oImage);
564 $parts = explode('.', $file->name);
565 $file->name = $oImage->id . '.' . array_pop($parts);
566 $oImage->filename = $file->name;
568 if ($this->validate($uploaded_file, $file, $error, $index)) {
569 $this->handle_form_data($file, $index);
570 $upload_dir = $this->get_upload_path();
571 if (!is_dir($upload_dir)) {
572 mkdir($upload_dir, $this->options['mkdir_mode']);
574 $parts = explode('.', $file->name);
575 $file_path = $this->get_upload_path($file->name);
576 $append_file = $content_range && is_file($file_path) &&
577 $file->size > $this->get_file_size($file_path);
578 if ($uploaded_file && is_uploaded_file($uploaded_file)) {
579 // multipart/formdata uploads (POST method uploads)
580 if (file_exists($file_path))
587 fopen($uploaded_file, 'r'),
591 move_uploaded_file($uploaded_file, $file_path);
594 // Non-multipart uploads (PUT method support)
597 fopen('php://input', 'r'),
598 $append_file ? FILE_APPEND : 0
601 $file_size = $this->get_file_size($file_path, $append_file);
602 //if ($file_size === $file->size) {
604 if ($this->options['orient_image']) {
605 $this->orient_image($file_path);
607 $file->url = $this->get_download_url($file->name);
608 foreach($this->options['image_versions'] as $version => $options) {
609 if ($this->create_scaled_image($file->name, $version, $options)) {
610 if (!empty($version)) {
611 $file->{$version.'_url'} = $this->get_download_url(
616 $file_size = $this->get_file_size($file_path, true);
620 $file->id = $oImage->id;
621 $file->name = $oImage->filename;
622 } else if (!$content_range && $this->options['discard_aborted_uploads']) {
624 $file->error = 'abort';
626 $file->size = $file_size;
627 $this->set_file_delete_properties($file);
632 private function customResizeImage($imgString, $mimeType)
647 case 'image/x-windows-bmp':
653 $tmpFile = APPLICATION_PATH . '/../public/files/'
654 . mt_rand(10000000, 99999999) . $fileExt;
655 $bytesWritten = file_put_contents($tmpFile, $imgString);
656 list($img_width, $img_height) = getimagesize($tmpFile);
658 if (!$img_width || !$img_height)
662 $src_img = imagecreatefromstring($imgString);
671 $new_width = $img_width * $scale;
672 $new_height = $img_height * $scale;
673 $new_img = @imagecreatetruecolor($new_width, $new_height);
678 $write_image = 'imagejpeg';
682 @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
683 $write_image = 'imagegif';
684 $image_quality = null;
687 @imagecolortransparent($new_img, @imagecolorallocate($new_img, 0, 0, 0));
688 @imagealphablending($new_img, false);
689 @imagesavealpha($new_img, true);
690 $write_image = 'imagepng';
694 case 'image/x-windows-bmp':
695 $write_image = 'imagebmp';
696 $image_quality = null;
701 $success = @imagecopyresampled(
712 if ('imagebmp' == $write_image)
714 BMP::imagebmp($new_img, $tmpFile);
718 $write_image($new_img, $tmpFile, $image_quality);
720 @imagedestroy($src_img);
721 @imagedestroy($new_img);
722 $imgString = file_get_contents($tmpFile);
727 @imagedestroy($src_img);
728 @imagedestroy($new_img);
732 protected function generate_response($content, $print_response = true) {
733 if ($print_response) {
734 $json = json_encode($content);
735 $redirect = isset($_REQUEST['redirect']) ?
736 stripslashes($_REQUEST['redirect']) : null;
738 header('Location: '.sprintf($redirect, rawurlencode($json)));
742 if (isset($_SERVER['HTTP_CONTENT_RANGE']) && is_array($content) &&
743 is_object($content[0]) && $content[0]->size) {
744 header('Range: 0-'.($this->fix_integer_overflow(intval($content[0]->size)) - 1));
751 protected function get_version_param() {
752 return isset($_GET['version']) ? basename(stripslashes($_GET['version'])) : null;
755 protected function get_file_name_param() {
756 return isset($_GET['file']) ? basename(stripslashes($_GET['file'])) : null;
759 protected function get_file_type($file_path) {
760 switch (strtolower(pathinfo($file_path, PATHINFO_EXTENSION))) {
773 protected function download() {
774 if (!$this->options['download_via_php']) {
775 header('HTTP/1.1 403 Forbidden');
778 $file_name = $this->get_file_name_param();
779 if ($this->is_valid_file_object($file_name)) {
780 $file_path = $this->get_upload_path($file_name, $this->get_version_param());
781 if (is_file($file_path)) {
782 if (!preg_match($this->options['inline_file_types'], $file_name)) {
783 header('Content-Description: File Transfer');
784 header('Content-Type: application/octet-stream');
785 header('Content-Disposition: attachment; filename="'.$file_name.'"');
786 header('Content-Transfer-Encoding: binary');
788 // Prevent Internet Explorer from MIME-sniffing the content-type:
789 header('X-Content-Type-Options: nosniff');
790 header('Content-Type: '.$this->get_file_type($file_path));
791 header('Content-Disposition: inline; filename="'.$file_name.'"');
793 header('Content-Length: '.$this->get_file_size($file_path));
794 header('Last-Modified: '.gmdate('D, d M Y H:i:s T', filemtime($file_path)));
795 readfile($file_path);
800 protected function send_content_type_header() {
801 header('Vary: Accept');
802 if (isset($_SERVER['HTTP_ACCEPT']) &&
803 (strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false)) {
804 header('Content-type: application/json');
806 header('Content-type: text/plain');
810 protected function send_access_control_headers() {
811 header('Access-Control-Allow-Origin: '.$this->options['access_control_allow_origin']);
812 header('Access-Control-Allow-Credentials: '
813 .($this->options['access_control_allow_credentials'] ? 'true' : 'false'));
814 header('Access-Control-Allow-Methods: '
815 .implode(', ', $this->options['access_control_allow_methods']));
816 header('Access-Control-Allow-Headers: '
817 .implode(', ', $this->options['access_control_allow_headers']));
820 public function head() {
821 header('Pragma: no-cache');
822 header('Cache-Control: no-store, no-cache, must-revalidate');
823 header('Content-Disposition: inline; filename="files.json"');
824 // Prevent Internet Explorer from MIME-sniffing the content-type:
825 header('X-Content-Type-Options: nosniff');
826 if ($this->options['access_control_allow_origin']) {
827 $this->send_access_control_headers();
829 $this->send_content_type_header();
832 public function get($print_response = true) {
833 if ($print_response && isset($_GET['download'])) {
834 return $this->download();
836 $file_name = $this->get_file_name_param();
838 $info = $this->get_file_object($file_name);
840 $info = $this->get_file_objects();
842 return $this->generate_response($info, $print_response);
845 public function post($print_response = true) {
846 if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') {
847 return $this->delete($print_response);
849 $upload = isset($_FILES[$this->options['param_name']]) ?
850 $_FILES[$this->options['param_name']] : null;
851 // Parse the Content-Disposition header, if available:
852 $file_name = isset($_SERVER['HTTP_CONTENT_DISPOSITION']) ?
853 rawurldecode(preg_replace(
856 $_SERVER['HTTP_CONTENT_DISPOSITION']
858 $file_type = isset($_SERVER['HTTP_CONTENT_DESCRIPTION']) ?
859 $_SERVER['HTTP_CONTENT_DESCRIPTION'] : null;
860 // Parse the Content-Range header, which has the following form:
861 // Content-Range: bytes 0-524287/2000000
862 $content_range = isset($_SERVER['HTTP_CONTENT_RANGE']) ?
863 preg_split('/[^0-9]+/', $_SERVER['HTTP_CONTENT_RANGE']) : null;
864 $size = $content_range ? $content_range[3] : null;
866 if ($upload && is_array($upload['tmp_name'])) {
867 // param_name is an array identifier like "files[]",
868 // $_FILES is a multi-dimensional array:
869 foreach ($upload['tmp_name'] as $index => $value) {
870 $info[] = $this->handle_file_upload(
871 $upload['tmp_name'][$index],
872 $file_name ? $file_name : $upload['name'][$index],
873 $size ? $size : $upload['size'][$index],
874 $file_type ? $file_type : $upload['type'][$index],
875 $upload['error'][$index],
881 // param_name is a single object identifier like "file",
882 // $_FILES is a one-dimensional array:
883 $info[] = $this->handle_file_upload(
884 isset($upload['tmp_name']) ? $upload['tmp_name'] : null,
885 $file_name ? $file_name : (isset($upload['name']) ?
886 $upload['name'] : null),
887 $size ? $size : (isset($upload['size']) ?
888 $upload['size'] : $_SERVER['CONTENT_LENGTH']),
889 $file_type ? $file_type : (isset($upload['type']) ?
890 $upload['type'] : $_SERVER['CONTENT_TYPE']),
891 isset($upload['error']) ? $upload['error'] : null,
896 return $this->generate_response($info, $print_response);
899 public function delete($print_response = true) {
900 $file_name = $this->get_file_name_param();
901 $file_path = $this->get_upload_path($file_name);
902 $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path);
904 foreach($this->options['image_versions'] as $version => $options) {
905 if (!empty($version)) {
906 $file = $this->get_upload_path($file_name, $version);
907 if (is_file($file)) {
913 return $this->generate_response($success, $print_response);
926 // Read 1,4,8,24,32bit BMP files
927 // Save 24bit BMP files
932 // Article about this class: http://de77.com/php/read-and-write-bmp-in-php-imagecreatefrombmp-imagebmp
933 // First-version: 07.02.2010
934 // Version: 21.08.2010
938 public static function imagebmp(&$img, $filename = false)
940 $wid = imagesx($img);
941 $hei = imagesy($img);
942 $wid_pad = str_pad('', $wid % 4, "\0");
944 $size = 54 + ($wid + $wid_pad) * $hei * 3; //fixed
946 //prepare & save header
947 $header['identifier'] = 'BM';
948 $header['file_size'] = self::dword($size);
949 $header['reserved'] = self::dword(0);
950 $header['bitmap_data'] = self::dword(54);
951 $header['header_size'] = self::dword(40);
952 $header['width'] = self::dword($wid);
953 $header['height'] = self::dword($hei);
954 $header['planes'] = self::word(1);
955 $header['bits_per_pixel'] = self::word(24);
956 $header['compression'] = self::dword(0);
957 $header['data_size'] = self::dword(0);
958 $header['h_resolution'] = self::dword(0);
959 $header['v_resolution'] = self::dword(0);
960 $header['colors'] = self::dword(0);
961 $header['important_colors'] = self::dword(0);
965 $f = fopen($filename, "wb");
966 foreach ($header AS $h)
972 for ($y=$hei-1; $y>=0; $y--)
974 for ($x=0; $x<$wid; $x++)
976 $rgb = imagecolorat($img, $x, $y);
977 fwrite($f, byte3($rgb));
979 fwrite($f, $wid_pad);
985 foreach ($header AS $h)
991 for ($y=$hei-1; $y>=0; $y--)
993 for ($x=0; $x<$wid; $x++)
995 $rgb = imagecolorat($img, $x, $y);
996 echo self::byte3($rgb);
1003 public static function imagecreatefrombmp($filename)
1005 $f = fopen($filename, "rb");
1008 $header = fread($f, 54);
1009 $header = unpack( 'c2identifier/Vfile_size/Vreserved/Vbitmap_data/Vheader_size/' .
1010 'Vwidth/Vheight/vplanes/vbits_per_pixel/Vcompression/Vdata_size/'.
1011 'Vh_resolution/Vv_resolution/Vcolors/Vimportant_colors', $header);
1013 if ($header['identifier1'] != 66 or $header['identifier2'] != 77)
1015 die('Not a valid bmp file');
1018 if (!in_array($header['bits_per_pixel'], array(24, 32, 8, 4, 1)))
1020 die('Only 1, 4, 8, 24 and 32 bit BMP images are supported');
1023 $bps = $header['bits_per_pixel']; //bits per pixel
1024 $wid2 = ceil(($bps/8 * $header['width']) / 4) * 4;
1025 $colors = pow(2, $bps);
1027 $wid = $header['width'];
1028 $hei = $header['height'];
1030 $img = imagecreatetruecolor($header['width'], $header['height']);
1035 for ($i=0; $i<$colors; $i++)
1037 $palette[] = self::undword(fread($f, 4));
1044 imagealphablending($img, false);
1045 imagesavealpha($img, true);
1051 for ($y=$hei-1; $y>=0; $y--)
1053 $row = fread($f, $wid2);
1054 $pixels = self::str_split2($row, $bps, $palette);
1055 for ($x=0; $x<$wid; $x++)
1057 self::makepixel($img, $x, $y, $pixels[$x], $bps);
1065 private static function str_split2($row, $bps, $palette)
1070 case 24: return str_split($row, $bps/8);
1071 case 8: $out = array();
1072 $count = strlen($row);
1073 for ($i=0; $i<$count; $i++)
1075 $out[] = $palette[ ord($row[$i]) ];
1078 case 4: $out = array();
1079 $count = strlen($row);
1080 for ($i=0; $i<$count; $i++)
1082 $roww = ord($row[$i]);
1083 $out[] = $palette[ ($roww & 240) >> 4 ];
1084 $out[] = $palette[ ($roww & 15) ];
1087 case 1: $out = array();
1088 $count = strlen($row);
1089 for ($i=0; $i<$count; $i++)
1091 $roww = ord($row[$i]);
1092 $out[] = $palette[ ($roww & 128) >> 7 ];
1093 $out[] = $palette[ ($roww & 64) >> 6 ];
1094 $out[] = $palette[ ($roww & 32) >> 5 ];
1095 $out[] = $palette[ ($roww & 16) >> 4 ];
1096 $out[] = $palette[ ($roww & 8) >> 3 ];
1097 $out[] = $palette[ ($roww & 4) >> 2 ];
1098 $out[] = $palette[ ($roww & 2) >> 1 ];
1099 $out[] = $palette[ ($roww & 1) ];
1105 private static function makepixel($img, $x, $y, $str, $bps)
1109 case 32 : $a = ord($str[0]);
1112 $d = 256 - ord($str[3]); //TODO: gives imperfect results
1113 $pixel = $d*256*256*256 + $c*256*256 + $b*256 + $a;
1114 imagesetpixel($img, $x, $y, $pixel);
1116 case 24 : $a = ord($str[0]);
1119 $pixel = $c*256*256 + $b*256 + $a;
1120 imagesetpixel($img, $x, $y, $pixel);
1124 case 1 : imagesetpixel($img, $x, $y, $str);
1129 private static function byte3($n)
1131 return chr($n & 255) . chr(($n >> 8) & 255) . chr(($n >> 16) & 255);
1134 private static function undword($n)
1136 $r = unpack("V", $n);
1140 private static function dword($n)
1142 return pack("V", $n);
1145 private static function word($n)
1147 return pack("v", $n);
1151 function imagebmp(&$img, $filename = false)
1153 return BMP::imagebmp($img, $filename);
1156 function imagecreatefrombmp($filename)
1158 return BMP::imagecreatefrombmp($filename);