Completed
Push — master ( 54317e...8bfa50 )
by Joas
24:47
created

OC_Image::data()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 30
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 25
nc 11
nop 0
dl 0
loc 30
rs 6.7272
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Andreas Fischer <[email protected]>
6
 * @author Bartek Przybylski <[email protected]>
7
 * @author Bart Visscher <[email protected]>
8
 * @author Björn Schießle <[email protected]>
9
 * @author Byron Marohn <[email protected]>
10
 * @author Christopher Schäpers <[email protected]>
11
 * @author Georg Ehrke <[email protected]>
12
 * @author j-ed <[email protected]>
13
 * @author Joas Schilling <[email protected]>
14
 * @author Johannes Willnecker <[email protected]>
15
 * @author Jörn Friedrich Dreyer <[email protected]>
16
 * @author Lukas Reschke <[email protected]>
17
 * @author Morris Jobke <[email protected]>
18
 * @author Olivier Paroz <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Thomas Müller <[email protected]>
21
 * @author Thomas Tanghus <[email protected]>
22
 *
23
 * @license AGPL-3.0
24
 *
25
 * This code is free software: you can redistribute it and/or modify
26
 * it under the terms of the GNU Affero General Public License, version 3,
27
 * as published by the Free Software Foundation.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32
 * GNU Affero General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU Affero General Public License, version 3,
35
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
36
 *
37
 */
38
39
/**
40
 * Class for basic image manipulation
41
 */
42
class OC_Image implements \OCP\IImage {
43
	/** @var false|resource */
44
	protected $resource = false; // tmp resource.
45
	/** @var int */
46
	protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
47
	/** @var string */
48
	protected $mimeType = 'image/png'; // Default to png
49
	/** @var int */
50
	protected $bitDepth = 24;
51
	/** @var null|string */
52
	protected $filePath = null;
53
	/** @var finfo */
54
	private $fileInfo;
55
	/** @var \OCP\ILogger */
56
	private $logger;
57
	/** @var \OCP\IConfig */
58
	private $config;
59
	/** @var array */
60
	private $exif;
61
62
	/**
63
	 * Get mime type for an image file.
64
	 *
65
	 * @param string|null $filePath The path to a local image file.
66
	 * @return string The mime type if the it could be determined, otherwise an empty string.
67
	 */
68
	static public function getMimeTypeForFile($filePath) {
69
		// exif_imagetype throws "read error!" if file is less than 12 byte
70
		if ($filePath !== null && filesize($filePath) > 11) {
71
			$imageType = exif_imagetype($filePath);
72
		} else {
73
			$imageType = false;
74
		}
75
		return $imageType ? image_type_to_mime_type($imageType) : '';
76
	}
77
78
	/**
79
	 * Constructor.
80
	 *
81
	 * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by
82
	 * an imagecreate* function.
83
	 * @param \OCP\ILogger $logger
84
	 * @param \OCP\IConfig $config
85
	 */
86
	public function __construct($imageRef = null, \OCP\ILogger $logger = null, \OCP\IConfig $config = null) {
87
		$this->logger = $logger;
88
		if ($logger === null) {
89
			$this->logger = \OC::$server->getLogger();
90
		}
91
		$this->config = $config;
92
		if ($config === null) {
93
			$this->config = \OC::$server->getConfig();
94
		}
95
96
		if (\OC_Util::fileInfoLoaded()) {
97
			$this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
98
		}
99
100
		if ($imageRef !== null) {
101
			$this->load($imageRef);
102
		}
103
	}
104
105
	/**
106
	 * Determine whether the object contains an image resource.
107
	 *
108
	 * @return bool
109
	 */
110
	public function valid() { // apparently you can't name a method 'empty'...
111
		return is_resource($this->resource);
112
	}
113
114
	/**
115
	 * Returns the MIME type of the image or an empty string if no image is loaded.
116
	 *
117
	 * @return string
118
	 */
119
	public function mimeType() {
120
		return $this->valid() ? $this->mimeType : '';
121
	}
122
123
	/**
124
	 * Returns the width of the image or -1 if no image is loaded.
125
	 *
126
	 * @return int
127
	 */
128
	public function width() {
129
		return $this->valid() ? imagesx($this->resource) : -1;
130
	}
131
132
	/**
133
	 * Returns the height of the image or -1 if no image is loaded.
134
	 *
135
	 * @return int
136
	 */
137
	public function height() {
138
		return $this->valid() ? imagesy($this->resource) : -1;
139
	}
140
141
	/**
142
	 * Returns the width when the image orientation is top-left.
143
	 *
144
	 * @return int
145
	 */
146 View Code Duplication
	public function widthTopLeft() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
		$o = $this->getOrientation();
148
		$this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, array('app' => 'core'));
149
		switch ($o) {
150
			case -1:
151
			case 1:
152
			case 2: // Not tested
153
			case 3:
154
			case 4: // Not tested
155
				return $this->width();
156
			case 5: // Not tested
157
			case 6:
158
			case 7: // Not tested
159
			case 8:
160
				return $this->height();
161
		}
162
		return $this->width();
163
	}
164
165
	/**
166
	 * Returns the height when the image orientation is top-left.
167
	 *
168
	 * @return int
169
	 */
170 View Code Duplication
	public function heightTopLeft() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
171
		$o = $this->getOrientation();
172
		$this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, array('app' => 'core'));
173
		switch ($o) {
174
			case -1:
175
			case 1:
176
			case 2: // Not tested
177
			case 3:
178
			case 4: // Not tested
179
				return $this->height();
180
			case 5: // Not tested
181
			case 6:
182
			case 7: // Not tested
183
			case 8:
184
				return $this->width();
185
		}
186
		return $this->height();
187
	}
188
189
	/**
190
	 * Outputs the image.
191
	 *
192
	 * @param string $mimeType
193
	 * @return bool
194
	 */
195
	public function show($mimeType = null) {
196
		if ($mimeType === null) {
197
			$mimeType = $this->mimeType();
198
		}
199
		header('Content-Type: ' . $mimeType);
200
		return $this->_output(null, $mimeType);
201
	}
202
203
	/**
204
	 * Saves the image.
205
	 *
206
	 * @param string $filePath
207
	 * @param string $mimeType
208
	 * @return bool
209
	 */
210
211
	public function save($filePath = null, $mimeType = null) {
212
		if ($mimeType === null) {
213
			$mimeType = $this->mimeType();
214
		}
215
		if ($filePath === null) {
216
			if ($this->filePath === null) {
217
				$this->logger->error(__METHOD__ . '(): called with no path.', array('app' => 'core'));
218
				return false;
219
			} else {
220
				$filePath = $this->filePath;
221
			}
222
		}
223
		return $this->_output($filePath, $mimeType);
224
	}
225
226
	/**
227
	 * Outputs/saves the image.
228
	 *
229
	 * @param string $filePath
230
	 * @param string $mimeType
231
	 * @return bool
232
	 * @throws Exception
233
	 */
234
	private function _output($filePath = null, $mimeType = null) {
235
		if ($filePath) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filePath of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
236
			if (!file_exists(dirname($filePath))) {
237
				mkdir(dirname($filePath), 0777, true);
238
			}
239
			$isWritable = is_writable(dirname($filePath));
240
			if (!$isWritable) {
241
				$this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', array('app' => 'core'));
242
				return false;
243
			} elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) {
244
				$this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', array('app' => 'core'));
245
				return false;
246
			}
247
		}
248
		if (!$this->valid()) {
249
			return false;
250
		}
251
252
		$imageType = $this->imageType;
253
		if ($mimeType !== null) {
254
			switch ($mimeType) {
255
				case 'image/gif':
256
					$imageType = IMAGETYPE_GIF;
257
					break;
258
				case 'image/jpeg':
259
					$imageType = IMAGETYPE_JPEG;
260
					break;
261
				case 'image/png':
262
					$imageType = IMAGETYPE_PNG;
263
					break;
264
				case 'image/x-xbitmap':
265
					$imageType = IMAGETYPE_XBM;
266
					break;
267
				case 'image/bmp':
268
				case 'image/x-ms-bmp':
269
					$imageType = IMAGETYPE_BMP;
270
					break;
271
				default:
272
					throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
273
			}
274
		}
275
276
		switch ($imageType) {
277
			case IMAGETYPE_GIF:
278
				$retVal = imagegif($this->resource, $filePath);
279
				break;
280
			case IMAGETYPE_JPEG:
281
				$retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality());
282
				break;
283
			case IMAGETYPE_PNG:
284
				$retVal = imagepng($this->resource, $filePath);
285
				break;
286
			case IMAGETYPE_XBM:
287
				if (function_exists('imagexbm')) {
288
					$retVal = imagexbm($this->resource, $filePath);
289
				} else {
290
					throw new Exception('\OC_Image::_output(): imagexbm() is not supported.');
291
				}
292
293
				break;
294
			case IMAGETYPE_WBMP:
295
				$retVal = imagewbmp($this->resource, $filePath);
296
				break;
297
			case IMAGETYPE_BMP:
298
				$retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
0 ignored issues
show
Security Bug introduced by
It seems like $this->resource can also be of type false; however, imagebmp() does only seem to accept resource, did you maybe forget to handle an error condition?
Loading history...
299
				break;
300
			default:
301
				$retVal = imagepng($this->resource, $filePath);
302
		}
303
		return $retVal;
304
	}
305
306
	/**
307
	 * Prints the image when called as $image().
308
	 */
309
	public function __invoke() {
310
		return $this->show();
311
	}
312
313
	/**
314
	 * @return resource Returns the image resource in any.
315
	 */
316
	public function resource() {
317
		return $this->resource;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression $this->resource; of type false|resource adds false to the return on line 317 which is incompatible with the return type declared by the interface OCP\IImage::resource of type resource. It seems like you forgot to handle an error condition.
Loading history...
318
	}
319
320
	/**
321
	 * @return null|string Returns the raw image data.
322
	 */
323
	public function data() {
324
		if (!$this->valid()) {
325
			return null;
326
		}
327
		ob_start();
328
		switch ($this->mimeType) {
329
			case "image/png":
330
				$res = imagepng($this->resource);
331
				break;
332
			case "image/jpeg":
333
				$quality = $this->getJpegQuality();
334
				if ($quality !== null) {
335
					$res = imagejpeg($this->resource, null, $quality);
336
				} else {
337
					$res = imagejpeg($this->resource);
338
				}
339
				break;
340
			case "image/gif":
341
				$res = imagegif($this->resource);
342
				break;
343
			default:
344
				$res = imagepng($this->resource);
345
				$this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', array('app' => 'core'));
346
				break;
347
		}
348
		if (!$res) {
349
			$this->logger->error('OC_Image->data. Error getting image data.', array('app' => 'core'));
350
		}
351
		return ob_get_clean();
352
	}
353
354
	/**
355
	 * @return string - base64 encoded, which is suitable for embedding in a VCard.
356
	 */
357
	public function __toString() {
358
		return base64_encode($this->data());
359
	}
360
361
	/**
362
	 * @return int|null
363
	 */
364
	protected function getJpegQuality() {
365
		$quality = $this->config->getAppValue('preview', 'jpeg_quality', 90);
366
		if ($quality !== null) {
367
			$quality = min(100, max(10, (int) $quality));
368
		}
369
		return $quality;
370
	}
371
372
	/**
373
	 * (I'm open for suggestions on better method name ;)
374
	 * Get the orientation based on EXIF data.
375
	 *
376
	 * @return int The orientation or -1 if no EXIF data is available.
377
	 */
378
	public function getOrientation() {
379
		if ($this->exif !== null) {
380
			return $this->exif['Orientation'];
381
		}
382
383
		if ($this->imageType !== IMAGETYPE_JPEG) {
384
			$this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', array('app' => 'core'));
385
			return -1;
386
		}
387
		if (!is_callable('exif_read_data')) {
388
			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core'));
389
			return -1;
390
		}
391
		if (!$this->valid()) {
392
			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core'));
393
			return -1;
394
		}
395
		if (is_null($this->filePath) || !is_readable($this->filePath)) {
396
			$this->logger->debug('OC_Image->fixOrientation() No readable file path set.', array('app' => 'core'));
397
			return -1;
398
		}
399
		$exif = @exif_read_data($this->filePath, 'IFD0');
400
		if (!$exif) {
401
			return -1;
402
		}
403
		if (!isset($exif['Orientation'])) {
404
			return -1;
405
		}
406
		$this->exif = $exif;
407
		return $exif['Orientation'];
408
	}
409
410
	public function readExif($data) {
411
		if (!is_callable('exif_read_data')) {
412
			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core'));
413
			return;
414
		}
415
		if (!$this->valid()) {
416
			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core'));
417
			return;
418
		}
419
420
		$exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
421
		if (!$exif) {
422
			return;
423
		}
424
		if (!isset($exif['Orientation'])) {
425
			return;
426
		}
427
		$this->exif = $exif;
428
	}
429
430
	/**
431
	 * (I'm open for suggestions on better method name ;)
432
	 * Fixes orientation based on EXIF data.
433
	 *
434
	 * @return bool.
0 ignored issues
show
Documentation introduced by
The doc-type bool. could not be parsed: Unknown type name "bool." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
435
	 */
436
	public function fixOrientation() {
437
		$o = $this->getOrientation();
438
		$this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, array('app' => 'core'));
439
		$rotate = 0;
440
		$flip = false;
441
		switch ($o) {
442
			case -1:
443
				return false; //Nothing to fix
444
			case 1:
445
				$rotate = 0;
446
				break;
447
			case 2:
448
				$rotate = 0;
449
				$flip = true;
450
				break;
451
			case 3:
452
				$rotate = 180;
453
				break;
454
			case 4:
455
				$rotate = 180;
456
				$flip = true;
457
				break;
458
			case 5:
459
				$rotate = 90;
460
				$flip = true;
461
				break;
462
			case 6:
463
				$rotate = 270;
464
				break;
465
			case 7:
466
				$rotate = 270;
467
				$flip = true;
468
				break;
469
			case 8:
470
				$rotate = 90;
471
				break;
472
		}
473
		if($flip && function_exists('imageflip')) {
474
			imageflip($this->resource, IMG_FLIP_HORIZONTAL);
475
		}
476
		if ($rotate) {
477
			$res = imagerotate($this->resource, $rotate, 0);
478
			if ($res) {
479
				if (imagealphablending($res, true)) {
480
					if (imagesavealpha($res, true)) {
481
						imagedestroy($this->resource);
482
						$this->resource = $res;
483
						return true;
484
					} else {
485
						$this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', array('app' => 'core'));
486
						return false;
487
					}
488
				} else {
489
					$this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', array('app' => 'core'));
490
					return false;
491
				}
492
			} else {
493
				$this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', array('app' => 'core'));
494
				return false;
495
			}
496
		}
497
		return false;
498
	}
499
500
	/**
501
	 * Loads an image from a local file, a base64 encoded string or a resource created by an imagecreate* function.
502
	 *
503
	 * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by an imagecreate* function or a file resource (file handle    ).
504
	 * @return resource|false An image resource or false on error
505
	 */
506
	public function load($imageRef) {
507
		if (is_resource($imageRef)) {
508
			if (get_resource_type($imageRef) === 'gd') {
509
				$this->resource = $imageRef;
510
				return $this->resource;
511
			} elseif (in_array(get_resource_type($imageRef), array('file', 'stream'))) {
512
				return $this->loadFromFileHandle($imageRef);
513
			}
514
		} elseif ($this->loadFromBase64($imageRef) !== false) {
0 ignored issues
show
Bug introduced by
It seems like $imageRef defined by parameter $imageRef on line 506 can also be of type resource; however, OC_Image::loadFromBase64() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
515
			return $this->resource;
516
		} elseif ($this->loadFromFile($imageRef) !== false) {
0 ignored issues
show
Bug introduced by
It seems like $imageRef defined by parameter $imageRef on line 506 can also be of type resource; however, OC_Image::loadFromFile() does only seem to accept boolean|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
517
			return $this->resource;
518
		} elseif ($this->loadFromData($imageRef) !== false) {
0 ignored issues
show
Bug introduced by
It seems like $imageRef defined by parameter $imageRef on line 506 can also be of type resource; however, OC_Image::loadFromData() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
519
			return $this->resource;
520
		}
521
		$this->logger->debug(__METHOD__ . '(): could not load anything. Giving up!', array('app' => 'core'));
522
		return false;
523
	}
524
525
	/**
526
	 * Loads an image from an open file handle.
527
	 * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
528
	 *
529
	 * @param resource $handle
530
	 * @return resource|false An image resource or false on error
531
	 */
532
	public function loadFromFileHandle($handle) {
533
		$contents = stream_get_contents($handle);
534
		if ($this->loadFromData($contents)) {
535
			return $this->resource;
536
		}
537
		return false;
538
	}
539
540
	/**
541
	 * Loads an image from a local file.
542
	 *
543
	 * @param bool|string $imagePath The path to a local file.
544
	 * @return bool|resource An image resource or false on error
545
	 */
546
	public function loadFromFile($imagePath = false) {
547
		// exif_imagetype throws "read error!" if file is less than 12 byte
548
		if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) {
549
			return false;
550
		}
551
		$iType = exif_imagetype($imagePath);
0 ignored issues
show
Bug introduced by
It seems like $imagePath defined by parameter $imagePath on line 546 can also be of type boolean; however, exif_imagetype() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
552
		switch ($iType) {
553 View Code Duplication
			case IMAGETYPE_GIF:
554
				if (imagetypes() & IMG_GIF) {
555
					$this->resource = imagecreatefromgif($imagePath);
556
					// Preserve transparency
557
					imagealphablending($this->resource, true);
558
					imagesavealpha($this->resource, true);
559
				} else {
560
					$this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, array('app' => 'core'));
561
				}
562
				break;
563
			case IMAGETYPE_JPEG:
564 View Code Duplication
				if (imagetypes() & IMG_JPG) {
565
					$this->resource = imagecreatefromjpeg($imagePath);
566
				} else {
567
					$this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, array('app' => 'core'));
568
				}
569
				break;
570 View Code Duplication
			case IMAGETYPE_PNG:
571
				if (imagetypes() & IMG_PNG) {
572
					$this->resource = imagecreatefrompng($imagePath);
573
					// Preserve transparency
574
					imagealphablending($this->resource, true);
575
					imagesavealpha($this->resource, true);
576
				} else {
577
					$this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, array('app' => 'core'));
578
				}
579
				break;
580
			case IMAGETYPE_XBM:
581 View Code Duplication
				if (imagetypes() & IMG_XPM) {
582
					$this->resource = imagecreatefromxbm($imagePath);
583
				} else {
584
					$this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, array('app' => 'core'));
585
				}
586
				break;
587
			case IMAGETYPE_WBMP:
588 View Code Duplication
				if (imagetypes() & IMG_WBMP) {
589
					$this->resource = imagecreatefromwbmp($imagePath);
590
				} else {
591
					$this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, array('app' => 'core'));
592
				}
593
				break;
594
			case IMAGETYPE_BMP:
595
				$this->resource = $this->imagecreatefrombmp($imagePath);
0 ignored issues
show
Bug introduced by
It seems like $imagePath defined by parameter $imagePath on line 546 can also be of type boolean; however, OC_Image::imagecreatefrombmp() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
596
				break;
597
			/*
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
598
			case IMAGETYPE_TIFF_II: // (intel byte order)
599
				break;
600
			case IMAGETYPE_TIFF_MM: // (motorola byte order)
601
				break;
602
			case IMAGETYPE_JPC:
603
				break;
604
			case IMAGETYPE_JP2:
605
				break;
606
			case IMAGETYPE_JPX:
607
				break;
608
			case IMAGETYPE_JB2:
609
				break;
610
			case IMAGETYPE_SWC:
611
				break;
612
			case IMAGETYPE_IFF:
613
				break;
614
			case IMAGETYPE_ICO:
615
				break;
616
			case IMAGETYPE_SWF:
617
				break;
618
			case IMAGETYPE_PSD:
619
				break;
620
			*/
621
			default:
622
623
				// this is mostly file created from encrypted file
624
				$this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath)));
0 ignored issues
show
Bug introduced by
It seems like $imagePath defined by parameter $imagePath on line 546 can also be of type boolean; however, OC\Files\Filesystem::getLocalPath() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
625
				$iType = IMAGETYPE_PNG;
626
				$this->logger->debug('OC_Image->loadFromFile, Default', array('app' => 'core'));
627
				break;
628
		}
629
		if ($this->valid()) {
630
			$this->imageType = $iType;
0 ignored issues
show
Documentation Bug introduced by
It seems like $iType can also be of type string or boolean. However, the property $imageType is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
631
			$this->mimeType = image_type_to_mime_type($iType);
632
			$this->filePath = $imagePath;
0 ignored issues
show
Documentation Bug introduced by
It seems like $imagePath can also be of type boolean. However, the property $filePath is declared as type null|string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
633
		}
634
		return $this->resource;
635
	}
636
637
	/**
638
	 * Loads an image from a string of data.
639
	 *
640
	 * @param string $str A string of image data as read from a file.
641
	 * @return bool|resource An image resource or false on error
642
	 */
643
	public function loadFromData($str) {
644
		if (is_resource($str)) {
645
			return false;
646
		}
647
		$this->resource = @imagecreatefromstring($str);
648
		if ($this->fileInfo) {
649
			$this->mimeType = $this->fileInfo->buffer($str);
650
		}
651
		if (is_resource($this->resource)) {
652
			imagealphablending($this->resource, false);
653
			imagesavealpha($this->resource, true);
654
		}
655
656
		if (!$this->resource) {
657
			$this->logger->debug('OC_Image->loadFromFile, could not load', array('app' => 'core'));
658
			return false;
659
		}
660
		return $this->resource;
661
	}
662
663
	/**
664
	 * Loads an image from a base64 encoded string.
665
	 *
666
	 * @param string $str A string base64 encoded string of image data.
667
	 * @return bool|resource An image resource or false on error
668
	 */
669
	public function loadFromBase64($str) {
670
		if (!is_string($str)) {
671
			return false;
672
		}
673
		$data = base64_decode($str);
674
		if ($data) { // try to load from string data
675
			$this->resource = @imagecreatefromstring($data);
676
			if ($this->fileInfo) {
677
				$this->mimeType = $this->fileInfo->buffer($data);
678
			}
679
			if (!$this->resource) {
680
				$this->logger->debug('OC_Image->loadFromBase64, could not load', array('app' => 'core'));
681
				return false;
682
			}
683
			return $this->resource;
684
		} else {
685
			return false;
686
		}
687
	}
688
689
	/**
690
	 * Create a new image from file or URL
691
	 *
692
	 * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
693
	 * @version 1.00
694
	 * @param string $fileName <p>
695
	 * Path to the BMP image.
696
	 * </p>
697
	 * @return bool|resource an image resource identifier on success, <b>FALSE</b> on errors.
698
	 */
699
	private function imagecreatefrombmp($fileName) {
700 View Code Duplication
		if (!($fh = fopen($fileName, 'rb'))) {
701
			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, array('app' => 'core'));
702
			return false;
703
		}
704
		// read file header
705
		$meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
706
		// check for bitmap
707 View Code Duplication
		if ($meta['type'] != 19778) {
708
			fclose($fh);
709
			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', array('app' => 'core'));
710
			return false;
711
		}
712
		// read image header
713
		$meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
714
		// read additional 16bit header
715
		if ($meta['bits'] == 16) {
716
			$meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
717
		}
718
		// set bytes and padding
719
		$meta['bytes'] = $meta['bits'] / 8;
720
		$this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
721
		$meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
722
		if ($meta['decal'] == 4) {
723
			$meta['decal'] = 0;
724
		}
725
		// obtain imagesize
726
		if ($meta['imagesize'] < 1) {
727
			$meta['imagesize'] = $meta['filesize'] - $meta['offset'];
728
			// in rare cases filesize is equal to offset so we need to read physical size
729
			if ($meta['imagesize'] < 1) {
730
				$meta['imagesize'] = @filesize($fileName) - $meta['offset'];
731 View Code Duplication
				if ($meta['imagesize'] < 1) {
732
					fclose($fh);
733
					$this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', array('app' => 'core'));
734
					return false;
735
				}
736
			}
737
		}
738
		// calculate colors
739
		$meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
740
		// read color palette
741
		$palette = array();
742
		if ($meta['bits'] < 16) {
743
			$palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
744
			// in rare cases the color value is signed
745
			if ($palette[1] < 0) {
746
				foreach ($palette as $i => $color) {
747
					$palette[$i] = $color + 16777216;
748
				}
749
			}
750
		}
751
		// create gd image
752
		$im = imagecreatetruecolor($meta['width'], $meta['height']);
753
		if ($im == false) {
754
			fclose($fh);
755
			$this->logger->warning(
756
				'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
757
				array('app' => 'core'));
758
			return false;
759
		}
760
761
		$data = fread($fh, $meta['imagesize']);
762
		$p = 0;
763
		$vide = chr(0);
764
		$y = $meta['height'] - 1;
765
		$error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
766
		// loop through the image data beginning with the lower left corner
767
		while ($y >= 0) {
768
			$x = 0;
769
			while ($x < $meta['width']) {
770
				switch ($meta['bits']) {
771
					case 32:
772
					case 24:
773 View Code Duplication
						if (!($part = substr($data, $p, 3))) {
774
							$this->logger->warning($error, array('app' => 'core'));
775
							return $im;
776
						}
777
						$color = @unpack('V', $part . $vide);
778
						break;
779
					case 16:
780 View Code Duplication
						if (!($part = substr($data, $p, 2))) {
781
							fclose($fh);
782
							$this->logger->warning($error, array('app' => 'core'));
783
							return $im;
784
						}
785
						$color = @unpack('v', $part);
786
						$color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
787
						break;
788
					case 8:
789
						$color = @unpack('n', $vide . substr($data, $p, 1));
790
						$color[1] = (isset($palette[$color[1] + 1])) ? $palette[$color[1] + 1] : $palette[1];
791
						break;
792
					case 4:
793
						$color = @unpack('n', $vide . substr($data, floor($p), 1));
794
						$color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
795
						$color[1] = (isset($palette[$color[1] + 1])) ? $palette[$color[1] + 1] : $palette[1];
796
						break;
797
					case 1:
798
						$color = @unpack('n', $vide . substr($data, floor($p), 1));
799
						switch (($p * 8) % 8) {
800
							case 0:
801
								$color[1] = $color[1] >> 7;
802
								break;
803
							case 1:
804
								$color[1] = ($color[1] & 0x40) >> 6;
805
								break;
806
							case 2:
807
								$color[1] = ($color[1] & 0x20) >> 5;
808
								break;
809
							case 3:
810
								$color[1] = ($color[1] & 0x10) >> 4;
811
								break;
812
							case 4:
813
								$color[1] = ($color[1] & 0x8) >> 3;
814
								break;
815
							case 5:
816
								$color[1] = ($color[1] & 0x4) >> 2;
817
								break;
818
							case 6:
819
								$color[1] = ($color[1] & 0x2) >> 1;
820
								break;
821
							case 7:
822
								$color[1] = ($color[1] & 0x1);
823
								break;
824
						}
825
						$color[1] = (isset($palette[$color[1] + 1])) ? $palette[$color[1] + 1] : $palette[1];
826
						break;
827
					default:
828
						fclose($fh);
829
						$this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', array('app' => 'core'));
830
						return false;
831
				}
832
				imagesetpixel($im, $x, $y, $color[1]);
833
				$x++;
834
				$p += $meta['bytes'];
835
			}
836
			$y--;
837
			$p += $meta['decal'];
838
		}
839
		fclose($fh);
840
		return $im;
841
	}
842
843
	/**
844
	 * Resizes the image preserving ratio.
845
	 *
846
	 * @param integer $maxSize The maximum size of either the width or height.
847
	 * @return bool
848
	 */
849
	public function resize($maxSize) {
850 View Code Duplication
		if (!$this->valid()) {
851
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
852
			return false;
853
		}
854
		$widthOrig = imagesx($this->resource);
855
		$heightOrig = imagesy($this->resource);
856
		$ratioOrig = $widthOrig / $heightOrig;
857
858
		if ($ratioOrig > 1) {
859
			$newHeight = round($maxSize / $ratioOrig);
860
			$newWidth = $maxSize;
861
		} else {
862
			$newWidth = round($maxSize * $ratioOrig);
863
			$newHeight = $maxSize;
864
		}
865
866
		$this->preciseResize(round($newWidth), round($newHeight));
867
		return true;
868
	}
869
870
	/**
871
	 * @param int $width
872
	 * @param int $height
873
	 * @return bool
874
	 */
875
	public function preciseResize($width, $height) {
876 View Code Duplication
		if (!$this->valid()) {
877
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
878
			return false;
879
		}
880
		$widthOrig = imagesx($this->resource);
881
		$heightOrig = imagesy($this->resource);
882
		$process = imagecreatetruecolor($width, $height);
883
884
		if ($process == false) {
885
			$this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core'));
886
			imagedestroy($process);
887
			return false;
888
		}
889
890
		// preserve transparency
891 View Code Duplication
		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
892
			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
893
			imagealphablending($process, false);
894
			imagesavealpha($process, true);
895
		}
896
897
		imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig);
898
		if ($process == false) {
899
			$this->logger->error(__METHOD__ . '(): Error re-sampling process image', array('app' => 'core'));
900
			imagedestroy($process);
901
			return false;
902
		}
903
		imagedestroy($this->resource);
904
		$this->resource = $process;
905
		return true;
906
	}
907
908
	/**
909
	 * Crops the image to the middle square. If the image is already square it just returns.
910
	 *
911
	 * @param int $size maximum size for the result (optional)
912
	 * @return bool for success or failure
913
	 */
914
	public function centerCrop($size = 0) {
915
		if (!$this->valid()) {
916
			$this->logger->error('OC_Image->centerCrop, No image loaded', array('app' => 'core'));
917
			return false;
918
		}
919
		$widthOrig = imagesx($this->resource);
920
		$heightOrig = imagesy($this->resource);
921
		if ($widthOrig === $heightOrig and $size == 0) {
922
			return true;
923
		}
924
		$ratioOrig = $widthOrig / $heightOrig;
925
		$width = $height = min($widthOrig, $heightOrig);
926
927
		if ($ratioOrig > 1) {
928
			$x = ($widthOrig / 2) - ($width / 2);
929
			$y = 0;
930
		} else {
931
			$y = ($heightOrig / 2) - ($height / 2);
932
			$x = 0;
933
		}
934
		if ($size > 0) {
935
			$targetWidth = $size;
936
			$targetHeight = $size;
937
		} else {
938
			$targetWidth = $width;
939
			$targetHeight = $height;
940
		}
941
		$process = imagecreatetruecolor($targetWidth, $targetHeight);
942
		if ($process == false) {
943
			$this->logger->error('OC_Image->centerCrop, Error creating true color image', array('app' => 'core'));
944
			imagedestroy($process);
945
			return false;
946
		}
947
948
		// preserve transparency
949 View Code Duplication
		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
950
			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
951
			imagealphablending($process, false);
952
			imagesavealpha($process, true);
953
		}
954
955
		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
956 View Code Duplication
		if ($process == false) {
957
			$this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, array('app' => 'core'));
958
			imagedestroy($process);
959
			return false;
960
		}
961
		imagedestroy($this->resource);
962
		$this->resource = $process;
963
		return true;
964
	}
965
966
	/**
967
	 * Crops the image from point $x$y with dimension $wx$h.
968
	 *
969
	 * @param int $x Horizontal position
970
	 * @param int $y Vertical position
971
	 * @param int $w Width
972
	 * @param int $h Height
973
	 * @return bool for success or failure
974
	 */
975
	public function crop($x, $y, $w, $h) {
976 View Code Duplication
		if (!$this->valid()) {
977
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
978
			return false;
979
		}
980
		$process = imagecreatetruecolor($w, $h);
981
		if ($process == false) {
982
			$this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core'));
983
			imagedestroy($process);
984
			return false;
985
		}
986
987
		// preserve transparency
988 View Code Duplication
		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
989
			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
990
			imagealphablending($process, false);
991
			imagesavealpha($process, true);
992
		}
993
994
		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
995 View Code Duplication
		if ($process == false) {
996
			$this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, array('app' => 'core'));
997
			imagedestroy($process);
998
			return false;
999
		}
1000
		imagedestroy($this->resource);
1001
		$this->resource = $process;
1002
		return true;
1003
	}
1004
1005
	/**
1006
	 * Resizes the image to fit within a boundary while preserving ratio.
1007
	 *
1008
	 * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up
1009
	 *
1010
	 * @param integer $maxWidth
1011
	 * @param integer $maxHeight
1012
	 * @return bool
1013
	 */
1014
	public function fitIn($maxWidth, $maxHeight) {
1015 View Code Duplication
		if (!$this->valid()) {
1016
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
1017
			return false;
1018
		}
1019
		$widthOrig = imagesx($this->resource);
1020
		$heightOrig = imagesy($this->resource);
1021
		$ratio = $widthOrig / $heightOrig;
1022
1023
		$newWidth = min($maxWidth, $ratio * $maxHeight);
1024
		$newHeight = min($maxHeight, $maxWidth / $ratio);
1025
1026
		$this->preciseResize(round($newWidth), round($newHeight));
1027
		return true;
1028
	}
1029
1030
	/**
1031
	 * Shrinks larger images to fit within specified boundaries while preserving ratio.
1032
	 *
1033
	 * @param integer $maxWidth
1034
	 * @param integer $maxHeight
1035
	 * @return bool
1036
	 */
1037
	public function scaleDownToFit($maxWidth, $maxHeight) {
1038 View Code Duplication
		if (!$this->valid()) {
1039
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
1040
			return false;
1041
		}
1042
		$widthOrig = imagesx($this->resource);
1043
		$heightOrig = imagesy($this->resource);
1044
1045
		if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) {
1046
			return $this->fitIn($maxWidth, $maxHeight);
1047
		}
1048
1049
		return false;
1050
	}
1051
1052
	/**
1053
	 * Destroys the current image and resets the object
1054
	 */
1055
	public function destroy() {
1056
		if ($this->valid()) {
1057
			imagedestroy($this->resource);
1058
		}
1059
		$this->resource = null;
1060
	}
1061
1062
	public function __destruct() {
1063
		$this->destroy();
1064
	}
1065
}
1066
1067
if (!function_exists('imagebmp')) {
1068
	/**
1069
	 * Output a BMP image to either the browser or a file
1070
	 *
1071
	 * @link http://www.ugia.cn/wp-data/imagebmp.php
1072
	 * @author legend <[email protected]>
1073
	 * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
1074
	 * @author mgutt <[email protected]>
1075
	 * @version 1.00
1076
	 * @param resource $im
1077
	 * @param string $fileName [optional] <p>The path to save the file to.</p>
1078
	 * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
1079
	 * @param int $compression [optional]
1080
	 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
1081
	 */
1082
	function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
1083
		if (!in_array($bit, array(1, 4, 8, 16, 24, 32))) {
1084
			$bit = 24;
1085
		} else if ($bit == 32) {
1086
			$bit = 24;
1087
		}
1088
		$bits = pow(2, $bit);
1089
		imagetruecolortopalette($im, true, $bits);
1090
		$width = imagesx($im);
1091
		$height = imagesy($im);
1092
		$colorsNum = imagecolorstotal($im);
1093
		$rgbQuad = '';
1094
		if ($bit <= 8) {
1095
			for ($i = 0; $i < $colorsNum; $i++) {
1096
				$colors = imagecolorsforindex($im, $i);
1097
				$rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
1098
			}
1099
			$bmpData = '';
1100
			if ($compression == 0 || $bit < 8) {
1101
				$compression = 0;
1102
				$extra = '';
1103
				$padding = 4 - ceil($width / (8 / $bit)) % 4;
1104
				if ($padding % 4 != 0) {
1105
					$extra = str_repeat("\0", $padding);
1106
				}
1107
				for ($j = $height - 1; $j >= 0; $j--) {
1108
					$i = 0;
1109
					while ($i < $width) {
1110
						$bin = 0;
1111
						$limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
1112
						for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
1113
							$index = imagecolorat($im, $i, $j);
1114
							$bin |= $index << $k;
1115
							$i++;
1116
						}
1117
						$bmpData .= chr($bin);
1118
					}
1119
					$bmpData .= $extra;
1120
				}
1121
			} // RLE8
1122
			else if ($compression == 1 && $bit == 8) {
1123
				for ($j = $height - 1; $j >= 0; $j--) {
1124
					$lastIndex = "\0";
1125
					$sameNum = 0;
1126
					for ($i = 0; $i <= $width; $i++) {
1127
						$index = imagecolorat($im, $i, $j);
1128
						if ($index !== $lastIndex || $sameNum > 255) {
1129
							if ($sameNum != 0) {
1130
								$bmpData .= chr($sameNum) . chr($lastIndex);
1131
							}
1132
							$lastIndex = $index;
1133
							$sameNum = 1;
1134
						} else {
1135
							$sameNum++;
1136
						}
1137
					}
1138
					$bmpData .= "\0\0";
1139
				}
1140
				$bmpData .= "\0\1";
1141
			}
1142
			$sizeQuad = strlen($rgbQuad);
1143
			$sizeData = strlen($bmpData);
1144
		} else {
1145
			$extra = '';
1146
			$padding = 4 - ($width * ($bit / 8)) % 4;
1147
			if ($padding % 4 != 0) {
1148
				$extra = str_repeat("\0", $padding);
1149
			}
1150
			$bmpData = '';
1151
			for ($j = $height - 1; $j >= 0; $j--) {
1152
				for ($i = 0; $i < $width; $i++) {
1153
					$index = imagecolorat($im, $i, $j);
1154
					$colors = imagecolorsforindex($im, $index);
1155
					if ($bit == 16) {
1156
						$bin = 0 << $bit;
1157
						$bin |= ($colors['red'] >> 3) << 10;
1158
						$bin |= ($colors['green'] >> 3) << 5;
1159
						$bin |= $colors['blue'] >> 3;
1160
						$bmpData .= pack("v", $bin);
1161
					} else {
1162
						$bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
1163
					}
1164
				}
1165
				$bmpData .= $extra;
1166
			}
1167
			$sizeQuad = 0;
1168
			$sizeData = strlen($bmpData);
1169
			$colorsNum = 0;
1170
		}
1171
		$fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
1172
		$infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
1173
		if ($fileName != '') {
1174
			$fp = fopen($fileName, 'wb');
1175
			fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
1176
			fclose($fp);
1177
			return true;
1178
		}
1179
		echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
1180
		return true;
1181
	}
1182
}
1183
1184
if (!function_exists('exif_imagetype')) {
1185
	/**
1186
	 * Workaround if exif_imagetype does not exist
1187
	 *
1188
	 * @link http://www.php.net/manual/en/function.exif-imagetype.php#80383
1189
	 * @param string $fileName
1190
	 * @return string|boolean
1191
	 */
1192
	function exif_imagetype($fileName) {
1193
		if (($info = getimagesize($fileName)) !== false) {
1194
			return $info[2];
1195
		}
1196
		return false;
1197
	}
1198
}
1199