Passed
Push — master ( 5cdc85...37718d )
by Morris
38:53 queued 21:57
created

OC_Image::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
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 Bart Visscher <[email protected]>
7
 * @author Bartek Przybylski <[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 Julius Härtl <[email protected]>
16
 * @author Jörn Friedrich Dreyer <[email protected]>
17
 * @author Lukas Reschke <[email protected]>
18
 * @author Morris Jobke <[email protected]>
19
 * @author Olivier Paroz <[email protected]>
20
 * @author Robin Appelman <[email protected]>
21
 * @author Roeland Jago Douma <[email protected]>
22
 * @author Thomas Müller <[email protected]>
23
 * @author Thomas Tanghus <[email protected]>
24
 * @author Victor Dubiniuk <[email protected]>
25
 *
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
/**
43
 * Class for basic image manipulation
44
 */
45
class OC_Image implements \OCP\IImage {
46
	/** @var false|resource */
47
	protected $resource = false; // tmp resource.
48
	/** @var int */
49
	protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
50
	/** @var string */
51
	protected $mimeType = 'image/png'; // Default to png
52
	/** @var int */
53
	protected $bitDepth = 24;
54
	/** @var null|string */
55
	protected $filePath = null;
56
	/** @var finfo */
57
	private $fileInfo;
58
	/** @var \OCP\ILogger */
59
	private $logger;
60
	/** @var \OCP\IConfig */
61
	private $config;
62
	/** @var array */
63
	private $exif;
64
65
	/**
66
	 * Constructor.
67
	 *
68
	 * @param resource|string $imageRef The path to a local file, a base64 encoded string or a resource created by
69
	 * an imagecreate* function.
70
	 * @param \OCP\ILogger $logger
71
	 * @param \OCP\IConfig $config
72
	 * @throws \InvalidArgumentException in case the $imageRef parameter is not null
73
	 */
74
	public function __construct($imageRef = null, \OCP\ILogger $logger = null, \OCP\IConfig $config = null) {
75
		$this->logger = $logger;
76
		if ($logger === null) {
77
			$this->logger = \OC::$server->getLogger();
78
		}
79
		$this->config = $config;
80
		if ($config === null) {
81
			$this->config = \OC::$server->getConfig();
82
		}
83
84
		if (\OC_Util::fileInfoLoaded()) {
85
			$this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
86
		}
87
88
		if ($imageRef !== null) {
89
			throw new \InvalidArgumentException('The first parameter in the constructor is not supported anymore. Please use any of the load* methods of the image object to load an image.');
90
		}
91
	}
92
93
	/**
94
	 * Determine whether the object contains an image resource.
95
	 *
96
	 * @return bool
97
	 */
98
	public function valid() { // apparently you can't name a method 'empty'...
99
		return is_resource($this->resource);
100
	}
101
102
	/**
103
	 * Returns the MIME type of the image or an empty string if no image is loaded.
104
	 *
105
	 * @return string
106
	 */
107
	public function mimeType() {
108
		return $this->valid() ? $this->mimeType : '';
109
	}
110
111
	/**
112
	 * Returns the width of the image or -1 if no image is loaded.
113
	 *
114
	 * @return int
115
	 */
116
	public function width() {
117
		return $this->valid() ? imagesx($this->resource) : -1;
118
	}
119
120
	/**
121
	 * Returns the height of the image or -1 if no image is loaded.
122
	 *
123
	 * @return int
124
	 */
125
	public function height() {
126
		return $this->valid() ? imagesy($this->resource) : -1;
127
	}
128
129
	/**
130
	 * Returns the width when the image orientation is top-left.
131
	 *
132
	 * @return int
133
	 */
134
	public function widthTopLeft() {
135
		$o = $this->getOrientation();
136
		$this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, array('app' => 'core'));
137
		switch ($o) {
138
			case -1:
139
			case 1:
140
			case 2: // Not tested
141
			case 3:
142
			case 4: // Not tested
143
				return $this->width();
144
			case 5: // Not tested
145
			case 6:
146
			case 7: // Not tested
147
			case 8:
148
				return $this->height();
149
		}
150
		return $this->width();
151
	}
152
153
	/**
154
	 * Returns the height when the image orientation is top-left.
155
	 *
156
	 * @return int
157
	 */
158
	public function heightTopLeft() {
159
		$o = $this->getOrientation();
160
		$this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, array('app' => 'core'));
161
		switch ($o) {
162
			case -1:
163
			case 1:
164
			case 2: // Not tested
165
			case 3:
166
			case 4: // Not tested
167
				return $this->height();
168
			case 5: // Not tested
169
			case 6:
170
			case 7: // Not tested
171
			case 8:
172
				return $this->width();
173
		}
174
		return $this->height();
175
	}
176
177
	/**
178
	 * Outputs the image.
179
	 *
180
	 * @param string $mimeType
181
	 * @return bool
182
	 */
183
	public function show($mimeType = null) {
184
		if ($mimeType === null) {
185
			$mimeType = $this->mimeType();
186
		}
187
		header('Content-Type: ' . $mimeType);
188
		return $this->_output(null, $mimeType);
189
	}
190
191
	/**
192
	 * Saves the image.
193
	 *
194
	 * @param string $filePath
195
	 * @param string $mimeType
196
	 * @return bool
197
	 */
198
199
	public function save($filePath = null, $mimeType = null) {
200
		if ($mimeType === null) {
201
			$mimeType = $this->mimeType();
202
		}
203
		if ($filePath === null) {
204
			if ($this->filePath === null) {
205
				$this->logger->error(__METHOD__ . '(): called with no path.', array('app' => 'core'));
206
				return false;
207
			} else {
208
				$filePath = $this->filePath;
209
			}
210
		}
211
		return $this->_output($filePath, $mimeType);
212
	}
213
214
	/**
215
	 * Outputs/saves the image.
216
	 *
217
	 * @param string $filePath
218
	 * @param string $mimeType
219
	 * @return bool
220
	 * @throws Exception
221
	 */
222
	private function _output($filePath = null, $mimeType = null) {
223
		if ($filePath) {
224
			if (!file_exists(dirname($filePath))) {
225
				mkdir(dirname($filePath), 0777, true);
226
			}
227
			$isWritable = is_writable(dirname($filePath));
228
			if (!$isWritable) {
229
				$this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', array('app' => 'core'));
230
				return false;
231
			} elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) {
232
				$this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', array('app' => 'core'));
233
				return false;
234
			}
235
		}
236
		if (!$this->valid()) {
237
			return false;
238
		}
239
240
		$imageType = $this->imageType;
241
		if ($mimeType !== null) {
242
			switch ($mimeType) {
243
				case 'image/gif':
244
					$imageType = IMAGETYPE_GIF;
245
					break;
246
				case 'image/jpeg':
247
					$imageType = IMAGETYPE_JPEG;
248
					break;
249
				case 'image/png':
250
					$imageType = IMAGETYPE_PNG;
251
					break;
252
				case 'image/x-xbitmap':
253
					$imageType = IMAGETYPE_XBM;
254
					break;
255
				case 'image/bmp':
256
				case 'image/x-ms-bmp':
257
					$imageType = IMAGETYPE_BMP;
258
					break;
259
				default:
260
					throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
261
			}
262
		}
263
264
		switch ($imageType) {
265
			case IMAGETYPE_GIF:
266
				$retVal = imagegif($this->resource, $filePath);
267
				break;
268
			case IMAGETYPE_JPEG:
269
				$retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality());
270
				break;
271
			case IMAGETYPE_PNG:
272
				$retVal = imagepng($this->resource, $filePath);
273
				break;
274
			case IMAGETYPE_XBM:
275
				if (function_exists('imagexbm')) {
276
					$retVal = imagexbm($this->resource, $filePath);
277
				} else {
278
					throw new Exception('\OC_Image::_output(): imagexbm() is not supported.');
279
				}
280
281
				break;
282
			case IMAGETYPE_WBMP:
283
				$retVal = imagewbmp($this->resource, $filePath);
284
				break;
285
			case IMAGETYPE_BMP:
286
				$retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
0 ignored issues
show
Bug introduced by
$this->bitDepth of type integer is incompatible with the type boolean expected by parameter $compressed of imagebmp(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

286
				$retVal = imagebmp($this->resource, $filePath, /** @scrutinizer ignore-type */ $this->bitDepth);
Loading history...
287
				break;
288
			default:
289
				$retVal = imagepng($this->resource, $filePath);
290
		}
291
		return $retVal;
292
	}
293
294
	/**
295
	 * Prints the image when called as $image().
296
	 */
297
	public function __invoke() {
298
		return $this->show();
299
	}
300
301
	/**
302
	 * @param resource Returns the image resource in any.
0 ignored issues
show
Bug introduced by
The type Returns was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
303
	 * @throws \InvalidArgumentException in case the supplied resource does not have the type "gd"
304
	 */
305
	public function setResource($resource) {
306
		if (get_resource_type($resource) === 'gd') {
0 ignored issues
show
Bug introduced by
$resource of type Returns is incompatible with the type resource expected by parameter $handle of get_resource_type(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

306
		if (get_resource_type(/** @scrutinizer ignore-type */ $resource) === 'gd') {
Loading history...
307
			$this->resource = $resource;
0 ignored issues
show
Documentation Bug introduced by
It seems like $resource of type Returns is incompatible with the declared type false|resource of property $resource.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
308
			return;
309
		}
310
		throw new \InvalidArgumentException('Supplied resource is not of type "gd".');
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
Bug Best Practice introduced by
The expression return $this->resource also could return the type boolean which is incompatible with the documented return type resource.
Loading history...
318
	}
319
320
	/**
321
	 * @return string Returns the mimetype of the data. Returns the empty string
322
	 * if the data is not valid.
323
	 */
324
	public function dataMimeType() {
325
		if (!$this->valid()) {
326
			return '';
327
		}
328
329
		switch ($this->mimeType) {
330
			case 'image/png':
331
			case 'image/jpeg':
332
			case 'image/gif':
333
				return $this->mimeType;
334
			default:
335
				return 'image/png';
336
		}
337
	}
338
339
	/**
340
	 * @return null|string Returns the raw image data.
341
	 */
342
	public function data() {
343
		if (!$this->valid()) {
344
			return null;
345
		}
346
		ob_start();
347
		switch ($this->mimeType) {
348
			case "image/png":
349
				$res = imagepng($this->resource);
350
				break;
351
			case "image/jpeg":
352
				$quality = $this->getJpegQuality();
353
				if ($quality !== null) {
354
					$res = imagejpeg($this->resource, null, $quality);
355
				} else {
356
					$res = imagejpeg($this->resource);
357
				}
358
				break;
359
			case "image/gif":
360
				$res = imagegif($this->resource);
361
				break;
362
			default:
363
				$res = imagepng($this->resource);
364
				$this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', array('app' => 'core'));
365
				break;
366
		}
367
		if (!$res) {
368
			$this->logger->error('OC_Image->data. Error getting image data.', array('app' => 'core'));
369
		}
370
		return ob_get_clean();
371
	}
372
373
	/**
374
	 * @return string - base64 encoded, which is suitable for embedding in a VCard.
375
	 */
376
	public function __toString() {
377
		return base64_encode($this->data());
378
	}
379
380
	/**
381
	 * @return int|null
382
	 */
383
	protected function getJpegQuality() {
384
		$quality = $this->config->getAppValue('preview', 'jpeg_quality', 90);
385
		if ($quality !== null) {
0 ignored issues
show
introduced by
The condition $quality !== null is always true.
Loading history...
386
			$quality = min(100, max(10, (int) $quality));
387
		}
388
		return $quality;
389
	}
390
391
	/**
392
	 * (I'm open for suggestions on better method name ;)
393
	 * Get the orientation based on EXIF data.
394
	 *
395
	 * @return int The orientation or -1 if no EXIF data is available.
396
	 */
397
	public function getOrientation() {
398
		if ($this->exif !== null) {
399
			return $this->exif['Orientation'];
400
		}
401
402
		if ($this->imageType !== IMAGETYPE_JPEG) {
403
			$this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', array('app' => 'core'));
404
			return -1;
405
		}
406
		if (!is_callable('exif_read_data')) {
407
			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core'));
408
			return -1;
409
		}
410
		if (!$this->valid()) {
411
			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core'));
412
			return -1;
413
		}
414
		if (is_null($this->filePath) || !is_readable($this->filePath)) {
415
			$this->logger->debug('OC_Image->fixOrientation() No readable file path set.', array('app' => 'core'));
416
			return -1;
417
		}
418
		$exif = @exif_read_data($this->filePath, 'IFD0');
419
		if (!$exif) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $exif of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
420
			return -1;
421
		}
422
		if (!isset($exif['Orientation'])) {
423
			return -1;
424
		}
425
		$this->exif = $exif;
426
		return $exif['Orientation'];
427
	}
428
429
	public function readExif($data) {
430
		if (!is_callable('exif_read_data')) {
431
			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core'));
432
			return;
433
		}
434
		if (!$this->valid()) {
435
			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core'));
436
			return;
437
		}
438
439
		$exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
440
		if (!$exif) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $exif of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
441
			return;
442
		}
443
		if (!isset($exif['Orientation'])) {
444
			return;
445
		}
446
		$this->exif = $exif;
447
	}
448
449
	/**
450
	 * (I'm open for suggestions on better method name ;)
451
	 * Fixes orientation based on EXIF data.
452
	 *
453
	 * @return bool
454
	 */
455
	public function fixOrientation() {
456
		$o = $this->getOrientation();
457
		$this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, array('app' => 'core'));
458
		$rotate = 0;
459
		$flip = false;
460
		switch ($o) {
461
			case -1:
462
				return false; //Nothing to fix
463
			case 1:
464
				$rotate = 0;
465
				break;
466
			case 2:
467
				$rotate = 0;
468
				$flip = true;
469
				break;
470
			case 3:
471
				$rotate = 180;
472
				break;
473
			case 4:
474
				$rotate = 180;
475
				$flip = true;
476
				break;
477
			case 5:
478
				$rotate = 90;
479
				$flip = true;
480
				break;
481
			case 6:
482
				$rotate = 270;
483
				break;
484
			case 7:
485
				$rotate = 270;
486
				$flip = true;
487
				break;
488
			case 8:
489
				$rotate = 90;
490
				break;
491
		}
492
		if($flip && function_exists('imageflip')) {
493
			imageflip($this->resource, IMG_FLIP_HORIZONTAL);
0 ignored issues
show
Bug introduced by
It seems like $this->resource can also be of type boolean; however, parameter $image of imageflip() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

493
			imageflip(/** @scrutinizer ignore-type */ $this->resource, IMG_FLIP_HORIZONTAL);
Loading history...
494
		}
495
		if ($rotate) {
496
			$res = imagerotate($this->resource, $rotate, 0);
0 ignored issues
show
Bug introduced by
It seems like $this->resource can also be of type boolean; however, parameter $image of imagerotate() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

496
			$res = imagerotate(/** @scrutinizer ignore-type */ $this->resource, $rotate, 0);
Loading history...
497
			if ($res) {
0 ignored issues
show
introduced by
$res is of type resource, thus it always evaluated to false.
Loading history...
498
				if (imagealphablending($res, true)) {
499
					if (imagesavealpha($res, true)) {
500
						imagedestroy($this->resource);
501
						$this->resource = $res;
502
						return true;
503
					} else {
504
						$this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', array('app' => 'core'));
505
						return false;
506
					}
507
				} else {
508
					$this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', array('app' => 'core'));
509
					return false;
510
				}
511
			} else {
512
				$this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', array('app' => 'core'));
513
				return false;
514
			}
515
		}
516
		return false;
517
	}
518
519
	/**
520
	 * Loads an image from an open file handle.
521
	 * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
522
	 *
523
	 * @param resource $handle
524
	 * @return resource|false An image resource or false on error
525
	 */
526
	public function loadFromFileHandle($handle) {
527
		$contents = stream_get_contents($handle);
528
		if ($this->loadFromData($contents)) {
529
			return $this->resource;
530
		}
531
		return false;
532
	}
533
534
	/**
535
	 * Loads an image from a local file.
536
	 *
537
	 * @param bool|string $imagePath The path to a local file.
538
	 * @return bool|resource An image resource or false on error
539
	 */
540
	public function loadFromFile($imagePath = false) {
541
		// exif_imagetype throws "read error!" if file is less than 12 byte
542
		if (!@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) {
543
			return false;
544
		}
545
		$iType = exif_imagetype($imagePath);
546
		switch ($iType) {
547
			case IMAGETYPE_GIF:
548
				if (imagetypes() & IMG_GIF) {
549
					$this->resource = imagecreatefromgif($imagePath);
550
					// Preserve transparency
551
					imagealphablending($this->resource, true);
552
					imagesavealpha($this->resource, true);
553
				} else {
554
					$this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, array('app' => 'core'));
555
				}
556
				break;
557
			case IMAGETYPE_JPEG:
558
				if (imagetypes() & IMG_JPG) {
559
					if (getimagesize($imagePath) !== false) {
560
						$this->resource = @imagecreatefromjpeg($imagePath);
561
					} else {
562
						$this->logger->debug('OC_Image->loadFromFile, JPG image not valid: ' . $imagePath, array('app' => 'core'));
563
					}
564
				} else {
565
					$this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, array('app' => 'core'));
566
				}
567
				break;
568
			case IMAGETYPE_PNG:
569
				if (imagetypes() & IMG_PNG) {
570
					$this->resource = @imagecreatefrompng($imagePath);
571
					// Preserve transparency
572
					imagealphablending($this->resource, true);
573
					imagesavealpha($this->resource, true);
574
				} else {
575
					$this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, array('app' => 'core'));
576
				}
577
				break;
578
			case IMAGETYPE_XBM:
579
				if (imagetypes() & IMG_XPM) {
580
					$this->resource = @imagecreatefromxbm($imagePath);
581
				} else {
582
					$this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, array('app' => 'core'));
583
				}
584
				break;
585
			case IMAGETYPE_WBMP:
586
				if (imagetypes() & IMG_WBMP) {
587
					$this->resource = @imagecreatefromwbmp($imagePath);
588
				} else {
589
					$this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, array('app' => 'core'));
590
				}
591
				break;
592
			case IMAGETYPE_BMP:
593
				$this->resource = $this->imagecreatefrombmp($imagePath);
594
				break;
595
			/*
596
			case IMAGETYPE_TIFF_II: // (intel byte order)
597
				break;
598
			case IMAGETYPE_TIFF_MM: // (motorola byte order)
599
				break;
600
			case IMAGETYPE_JPC:
601
				break;
602
			case IMAGETYPE_JP2:
603
				break;
604
			case IMAGETYPE_JPX:
605
				break;
606
			case IMAGETYPE_JB2:
607
				break;
608
			case IMAGETYPE_SWC:
609
				break;
610
			case IMAGETYPE_IFF:
611
				break;
612
			case IMAGETYPE_ICO:
613
				break;
614
			case IMAGETYPE_SWF:
615
				break;
616
			case IMAGETYPE_PSD:
617
				break;
618
			*/
619
			default:
620
621
				// this is mostly file created from encrypted file
622
				$this->resource = imagecreatefromstring(\OC\Files\Filesystem::file_get_contents(\OC\Files\Filesystem::getLocalPath($imagePath)));
623
				$iType = IMAGETYPE_PNG;
624
				$this->logger->debug('OC_Image->loadFromFile, Default', array('app' => 'core'));
625
				break;
626
		}
627
		if ($this->valid()) {
628
			$this->imageType = $iType;
629
			$this->mimeType = image_type_to_mime_type($iType);
630
			$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...
631
		}
632
		return $this->resource;
633
	}
634
635
	/**
636
	 * Loads an image from a string of data.
637
	 *
638
	 * @param string $str A string of image data as read from a file.
639
	 * @return bool|resource An image resource or false on error
640
	 */
641
	public function loadFromData($str) {
642
		if (is_resource($str)) {
0 ignored issues
show
introduced by
The condition is_resource($str) is always false.
Loading history...
643
			return false;
644
		}
645
		$this->resource = @imagecreatefromstring($str);
646
		if ($this->fileInfo) {
647
			$this->mimeType = $this->fileInfo->buffer($str);
648
		}
649
		if (is_resource($this->resource)) {
650
			imagealphablending($this->resource, false);
651
			imagesavealpha($this->resource, true);
652
		}
653
654
		if (!$this->resource) {
655
			$this->logger->debug('OC_Image->loadFromFile, could not load', array('app' => 'core'));
656
			return false;
657
		}
658
		return $this->resource;
659
	}
660
661
	/**
662
	 * Loads an image from a base64 encoded string.
663
	 *
664
	 * @param string $str A string base64 encoded string of image data.
665
	 * @return bool|resource An image resource or false on error
666
	 */
667
	public function loadFromBase64($str) {
668
		if (!is_string($str)) {
0 ignored issues
show
introduced by
The condition is_string($str) is always true.
Loading history...
669
			return false;
670
		}
671
		$data = base64_decode($str);
672
		if ($data) { // try to load from string data
673
			$this->resource = @imagecreatefromstring($data);
674
			if ($this->fileInfo) {
675
				$this->mimeType = $this->fileInfo->buffer($data);
676
			}
677
			if (!$this->resource) {
678
				$this->logger->debug('OC_Image->loadFromBase64, could not load', array('app' => 'core'));
679
				return false;
680
			}
681
			return $this->resource;
682
		} else {
683
			return false;
684
		}
685
	}
686
687
	/**
688
	 * Create a new image from file or URL
689
	 *
690
	 * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
691
	 * @version 1.00
692
	 * @param string $fileName <p>
693
	 * Path to the BMP image.
694
	 * </p>
695
	 * @return bool|resource an image resource identifier on success, <b>FALSE</b> on errors.
696
	 */
697
	private function imagecreatefrombmp($fileName) {
698
		if (!($fh = fopen($fileName, 'rb'))) {
699
			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, array('app' => 'core'));
700
			return false;
701
		}
702
		// read file header
703
		$meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
704
		// check for bitmap
705
		if ($meta['type'] != 19778) {
706
			fclose($fh);
707
			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', array('app' => 'core'));
708
			return false;
709
		}
710
		// read image header
711
		$meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
712
		// read additional 16bit header
713
		if ($meta['bits'] == 16) {
714
			$meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
715
		}
716
		// set bytes and padding
717
		$meta['bytes'] = $meta['bits'] / 8;
718
		$this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
719
		$meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
720
		if ($meta['decal'] == 4) {
721
			$meta['decal'] = 0;
722
		}
723
		// obtain imagesize
724
		if ($meta['imagesize'] < 1) {
725
			$meta['imagesize'] = $meta['filesize'] - $meta['offset'];
726
			// in rare cases filesize is equal to offset so we need to read physical size
727
			if ($meta['imagesize'] < 1) {
728
				$meta['imagesize'] = @filesize($fileName) - $meta['offset'];
729
				if ($meta['imagesize'] < 1) {
730
					fclose($fh);
731
					$this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', array('app' => 'core'));
732
					return false;
733
				}
734
			}
735
		}
736
		// calculate colors
737
		$meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
738
		// read color palette
739
		$palette = array();
740
		if ($meta['bits'] < 16) {
741
			$palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
742
			// in rare cases the color value is signed
743
			if ($palette[1] < 0) {
744
				foreach ($palette as $i => $color) {
745
					$palette[$i] = $color + 16777216;
746
				}
747
			}
748
		}
749
		// create gd image
750
		$im = imagecreatetruecolor($meta['width'], $meta['height']);
751
		if ($im == false) {
752
			fclose($fh);
753
			$this->logger->warning(
754
				'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
755
				array('app' => 'core'));
756
			return false;
757
		}
758
759
		$data = fread($fh, $meta['imagesize']);
760
		$p = 0;
761
		$vide = chr(0);
762
		$y = $meta['height'] - 1;
763
		$error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
764
		// loop through the image data beginning with the lower left corner
765
		while ($y >= 0) {
766
			$x = 0;
767
			while ($x < $meta['width']) {
768
				switch ($meta['bits']) {
769
					case 32:
770
					case 24:
771
						if (!($part = substr($data, $p, 3))) {
772
							$this->logger->warning($error, array('app' => 'core'));
773
							return $im;
774
						}
775
						$color = @unpack('V', $part . $vide);
776
						break;
777
					case 16:
778
						if (!($part = substr($data, $p, 2))) {
779
							fclose($fh);
780
							$this->logger->warning($error, array('app' => 'core'));
781
							return $im;
782
						}
783
						$color = @unpack('v', $part);
784
						$color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
785
						break;
786
					case 8:
787
						$color = @unpack('n', $vide . ($data[$p] ?? ''));
788
						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
789
						break;
790
					case 4:
791
						$color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
792
						$color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
793
						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
794
						break;
795
					case 1:
796
						$color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
797
						switch (($p * 8) % 8) {
798
							case 0:
799
								$color[1] = $color[1] >> 7;
800
								break;
801
							case 1:
802
								$color[1] = ($color[1] & 0x40) >> 6;
803
								break;
804
							case 2:
805
								$color[1] = ($color[1] & 0x20) >> 5;
806
								break;
807
							case 3:
808
								$color[1] = ($color[1] & 0x10) >> 4;
809
								break;
810
							case 4:
811
								$color[1] = ($color[1] & 0x8) >> 3;
812
								break;
813
							case 5:
814
								$color[1] = ($color[1] & 0x4) >> 2;
815
								break;
816
							case 6:
817
								$color[1] = ($color[1] & 0x2) >> 1;
818
								break;
819
							case 7:
820
								$color[1] = ($color[1] & 0x1);
821
								break;
822
						}
823
						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
824
						break;
825
					default:
826
						fclose($fh);
827
						$this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', array('app' => 'core'));
828
						return false;
829
				}
830
				imagesetpixel($im, $x, $y, $color[1]);
831
				$x++;
832
				$p += $meta['bytes'];
833
			}
834
			$y--;
835
			$p += $meta['decal'];
836
		}
837
		fclose($fh);
838
		return $im;
839
	}
840
841
	/**
842
	 * Resizes the image preserving ratio.
843
	 *
844
	 * @param integer $maxSize The maximum size of either the width or height.
845
	 * @return bool
846
	 */
847
	public function resize($maxSize) {
848
		if (!$this->valid()) {
849
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
850
			return false;
851
		}
852
		$widthOrig = imagesx($this->resource);
853
		$heightOrig = imagesy($this->resource);
854
		$ratioOrig = $widthOrig / $heightOrig;
855
856
		if ($ratioOrig > 1) {
857
			$newHeight = round($maxSize / $ratioOrig);
858
			$newWidth = $maxSize;
859
		} else {
860
			$newWidth = round($maxSize * $ratioOrig);
861
			$newHeight = $maxSize;
862
		}
863
864
		$this->preciseResize((int)round($newWidth), (int)round($newHeight));
865
		return true;
866
	}
867
868
	/**
869
	 * @param int $width
870
	 * @param int $height
871
	 * @return bool
872
	 */
873
	public function preciseResize(int $width, int $height): bool {
874
		if (!$this->valid()) {
875
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
876
			return false;
877
		}
878
		$widthOrig = imagesx($this->resource);
879
		$heightOrig = imagesy($this->resource);
880
		$process = imagecreatetruecolor($width, $height);
881
882
		if ($process == false) {
883
			$this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core'));
884
			imagedestroy($process);
885
			return false;
886
		}
887
888
		// preserve transparency
889
		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
890
			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
891
			imagealphablending($process, false);
892
			imagesavealpha($process, true);
893
		}
894
895
		imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig);
896
		if ($process == false) {
897
			$this->logger->error(__METHOD__ . '(): Error re-sampling process image', array('app' => 'core'));
898
			imagedestroy($process);
899
			return false;
900
		}
901
		imagedestroy($this->resource);
902
		$this->resource = $process;
903
		return true;
904
	}
905
906
	/**
907
	 * Crops the image to the middle square. If the image is already square it just returns.
908
	 *
909
	 * @param int $size maximum size for the result (optional)
910
	 * @return bool for success or failure
911
	 */
912
	public function centerCrop($size = 0) {
913
		if (!$this->valid()) {
914
			$this->logger->error('OC_Image->centerCrop, No image loaded', array('app' => 'core'));
915
			return false;
916
		}
917
		$widthOrig = imagesx($this->resource);
918
		$heightOrig = imagesy($this->resource);
919
		if ($widthOrig === $heightOrig and $size == 0) {
920
			return true;
921
		}
922
		$ratioOrig = $widthOrig / $heightOrig;
923
		$width = $height = min($widthOrig, $heightOrig);
924
925
		if ($ratioOrig > 1) {
926
			$x = ($widthOrig / 2) - ($width / 2);
927
			$y = 0;
928
		} else {
929
			$y = ($heightOrig / 2) - ($height / 2);
930
			$x = 0;
931
		}
932
		if ($size > 0) {
933
			$targetWidth = $size;
934
			$targetHeight = $size;
935
		} else {
936
			$targetWidth = $width;
937
			$targetHeight = $height;
938
		}
939
		$process = imagecreatetruecolor($targetWidth, $targetHeight);
940
		if ($process == false) {
941
			$this->logger->error('OC_Image->centerCrop, Error creating true color image', array('app' => 'core'));
942
			imagedestroy($process);
943
			return false;
944
		}
945
946
		// preserve transparency
947
		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
948
			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
949
			imagealphablending($process, false);
950
			imagesavealpha($process, true);
951
		}
952
953
		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
954
		if ($process == false) {
955
			$this->logger->error('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, array('app' => 'core'));
956
			imagedestroy($process);
957
			return false;
958
		}
959
		imagedestroy($this->resource);
960
		$this->resource = $process;
961
		return true;
962
	}
963
964
	/**
965
	 * Crops the image from point $x$y with dimension $wx$h.
966
	 *
967
	 * @param int $x Horizontal position
968
	 * @param int $y Vertical position
969
	 * @param int $w Width
970
	 * @param int $h Height
971
	 * @return bool for success or failure
972
	 */
973
	public function crop(int $x, int $y, int $w, int $h): bool {
974
		if (!$this->valid()) {
975
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
976
			return false;
977
		}
978
		$process = imagecreatetruecolor($w, $h);
979
		if ($process == false) {
980
			$this->logger->error(__METHOD__ . '(): Error creating true color image', array('app' => 'core'));
981
			imagedestroy($process);
982
			return false;
983
		}
984
985
		// preserve transparency
986
		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
987
			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
988
			imagealphablending($process, false);
989
			imagesavealpha($process, true);
990
		}
991
992
		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
993
		if ($process == false) {
994
			$this->logger->error(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, array('app' => 'core'));
995
			imagedestroy($process);
996
			return false;
997
		}
998
		imagedestroy($this->resource);
999
		$this->resource = $process;
1000
		return true;
1001
	}
1002
1003
	/**
1004
	 * Resizes the image to fit within a boundary while preserving ratio.
1005
	 *
1006
	 * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up
1007
	 *
1008
	 * @param integer $maxWidth
1009
	 * @param integer $maxHeight
1010
	 * @return bool
1011
	 */
1012
	public function fitIn($maxWidth, $maxHeight) {
1013
		if (!$this->valid()) {
1014
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
1015
			return false;
1016
		}
1017
		$widthOrig = imagesx($this->resource);
1018
		$heightOrig = imagesy($this->resource);
1019
		$ratio = $widthOrig / $heightOrig;
1020
1021
		$newWidth = min($maxWidth, $ratio * $maxHeight);
1022
		$newHeight = min($maxHeight, $maxWidth / $ratio);
1023
1024
		$this->preciseResize((int)round($newWidth), (int)round($newHeight));
1025
		return true;
1026
	}
1027
1028
	/**
1029
	 * Shrinks larger images to fit within specified boundaries while preserving ratio.
1030
	 *
1031
	 * @param integer $maxWidth
1032
	 * @param integer $maxHeight
1033
	 * @return bool
1034
	 */
1035
	public function scaleDownToFit($maxWidth, $maxHeight) {
1036
		if (!$this->valid()) {
1037
			$this->logger->error(__METHOD__ . '(): No image loaded', array('app' => 'core'));
1038
			return false;
1039
		}
1040
		$widthOrig = imagesx($this->resource);
1041
		$heightOrig = imagesy($this->resource);
1042
1043
		if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) {
1044
			return $this->fitIn($maxWidth, $maxHeight);
1045
		}
1046
1047
		return false;
1048
	}
1049
1050
	/**
1051
	 * Destroys the current image and resets the object
1052
	 */
1053
	public function destroy() {
1054
		if ($this->valid()) {
1055
			imagedestroy($this->resource);
1056
		}
1057
		$this->resource = null;
1058
	}
1059
1060
	public function __destruct() {
1061
		$this->destroy();
1062
	}
1063
}
1064
1065
if (!function_exists('imagebmp')) {
1066
	/**
1067
	 * Output a BMP image to either the browser or a file
1068
	 *
1069
	 * @link http://www.ugia.cn/wp-data/imagebmp.php
1070
	 * @author legend <[email protected]>
1071
	 * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
1072
	 * @author mgutt <[email protected]>
1073
	 * @version 1.00
1074
	 * @param resource $im
1075
	 * @param string $fileName [optional] <p>The path to save the file to.</p>
1076
	 * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
1077
	 * @param int $compression [optional]
1078
	 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
1079
	 */
1080
	function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
1081
		if (!in_array($bit, array(1, 4, 8, 16, 24, 32))) {
1082
			$bit = 24;
1083
		} else if ($bit == 32) {
1084
			$bit = 24;
1085
		}
1086
		$bits = pow(2, $bit);
1087
		imagetruecolortopalette($im, true, $bits);
1088
		$width = imagesx($im);
1089
		$height = imagesy($im);
1090
		$colorsNum = imagecolorstotal($im);
1091
		$rgbQuad = '';
1092
		if ($bit <= 8) {
1093
			for ($i = 0; $i < $colorsNum; $i++) {
1094
				$colors = imagecolorsforindex($im, $i);
1095
				$rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
1096
			}
1097
			$bmpData = '';
1098
			if ($compression == 0 || $bit < 8) {
1099
				$compression = 0;
1100
				$extra = '';
1101
				$padding = 4 - ceil($width / (8 / $bit)) % 4;
1102
				if ($padding % 4 != 0) {
1103
					$extra = str_repeat("\0", $padding);
1104
				}
1105
				for ($j = $height - 1; $j >= 0; $j--) {
1106
					$i = 0;
1107
					while ($i < $width) {
1108
						$bin = 0;
1109
						$limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
1110
						for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
1111
							$index = imagecolorat($im, $i, $j);
1112
							$bin |= $index << $k;
1113
							$i++;
1114
						}
1115
						$bmpData .= chr($bin);
1116
					}
1117
					$bmpData .= $extra;
1118
				}
1119
			} // RLE8
1120
			else if ($compression == 1 && $bit == 8) {
1121
				for ($j = $height - 1; $j >= 0; $j--) {
1122
					$lastIndex = "\0";
1123
					$sameNum = 0;
1124
					for ($i = 0; $i <= $width; $i++) {
1125
						$index = imagecolorat($im, $i, $j);
1126
						if ($index !== $lastIndex || $sameNum > 255) {
1127
							if ($sameNum != 0) {
1128
								$bmpData .= chr($sameNum) . chr($lastIndex);
0 ignored issues
show
Bug introduced by
$lastIndex of type string is incompatible with the type integer expected by parameter $ascii of chr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1128
								$bmpData .= chr($sameNum) . chr(/** @scrutinizer ignore-type */ $lastIndex);
Loading history...
1129
							}
1130
							$lastIndex = $index;
1131
							$sameNum = 1;
1132
						} else {
1133
							$sameNum++;
1134
						}
1135
					}
1136
					$bmpData .= "\0\0";
1137
				}
1138
				$bmpData .= "\0\1";
1139
			}
1140
			$sizeQuad = strlen($rgbQuad);
1141
			$sizeData = strlen($bmpData);
1142
		} else {
1143
			$extra = '';
1144
			$padding = 4 - ($width * ($bit / 8)) % 4;
1145
			if ($padding % 4 != 0) {
1146
				$extra = str_repeat("\0", $padding);
1147
			}
1148
			$bmpData = '';
1149
			for ($j = $height - 1; $j >= 0; $j--) {
1150
				for ($i = 0; $i < $width; $i++) {
1151
					$index = imagecolorat($im, $i, $j);
1152
					$colors = imagecolorsforindex($im, $index);
1153
					if ($bit == 16) {
1154
						$bin = 0 << $bit;
1155
						$bin |= ($colors['red'] >> 3) << 10;
1156
						$bin |= ($colors['green'] >> 3) << 5;
1157
						$bin |= $colors['blue'] >> 3;
1158
						$bmpData .= pack("v", $bin);
1159
					} else {
1160
						$bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
1161
					}
1162
				}
1163
				$bmpData .= $extra;
1164
			}
1165
			$sizeQuad = 0;
1166
			$sizeData = strlen($bmpData);
1167
			$colorsNum = 0;
1168
		}
1169
		$fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
1170
		$infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
1171
		if ($fileName != '') {
1172
			$fp = fopen($fileName, 'wb');
1173
			fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1173
			fwrite(/** @scrutinizer ignore-type */ $fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
Loading history...
1174
			fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1174
			fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
1175
			return true;
1176
		}
1177
		echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
1178
		return true;
1179
	}
1180
}
1181
1182
if (!function_exists('exif_imagetype')) {
1183
	/**
1184
	 * Workaround if exif_imagetype does not exist
1185
	 *
1186
	 * @link http://www.php.net/manual/en/function.exif-imagetype.php#80383
1187
	 * @param string $fileName
1188
	 * @return string|boolean
1189
	 */
1190
	function exif_imagetype($fileName) {
1191
		if (($info = getimagesize($fileName)) !== false) {
1192
			return $info[2];
1193
		}
1194
		return false;
1195
	}
1196
}
1197