Passed
Push — master ( d0f9ea...f02117 )
by John
14:46 queued 14s
created
lib/private/legacy/OC_Image.php 1 patch
Indentation   +1359 added lines, -1359 removed lines patch added patch discarded remove patch
@@ -47,688 +47,688 @@  discard block
 block discarded – undo
47 47
  */
48 48
 class OC_Image implements \OCP\IImage {
49 49
 
50
-	// Default memory limit for images to load (128 MBytes).
51
-	protected const DEFAULT_MEMORY_LIMIT = 128;
52
-
53
-	/** @var false|resource|\GdImage */
54
-	protected $resource = false; // tmp resource.
55
-	/** @var int */
56
-	protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
57
-	/** @var string */
58
-	protected $mimeType = 'image/png'; // Default to png
59
-	/** @var int */
60
-	protected $bitDepth = 24;
61
-	/** @var null|string */
62
-	protected $filePath = null;
63
-	/** @var finfo */
64
-	private $fileInfo;
65
-	/** @var \OCP\ILogger */
66
-	private $logger;
67
-	/** @var \OCP\IConfig */
68
-	private $config;
69
-	/** @var array */
70
-	private $exif;
71
-
72
-	/**
73
-	 * Constructor.
74
-	 *
75
-	 * @param resource|string|\GdImage $imageRef The path to a local file, a base64 encoded string or a resource created by
76
-	 * an imagecreate* function.
77
-	 * @param \OCP\ILogger $logger
78
-	 * @param \OCP\IConfig $config
79
-	 * @throws \InvalidArgumentException in case the $imageRef parameter is not null
80
-	 */
81
-	public function __construct($imageRef = null, \OCP\ILogger $logger = null, \OCP\IConfig $config = null) {
82
-		$this->logger = $logger;
83
-		if ($logger === null) {
84
-			$this->logger = \OC::$server->getLogger();
85
-		}
86
-		$this->config = $config;
87
-		if ($config === null) {
88
-			$this->config = \OC::$server->getConfig();
89
-		}
90
-
91
-		if (\OC_Util::fileInfoLoaded()) {
92
-			$this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
93
-		}
94
-
95
-		if ($imageRef !== null) {
96
-			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.');
97
-		}
98
-	}
99
-
100
-	/**
101
-	 * Determine whether the object contains an image resource.
102
-	 *
103
-	 * @return bool
104
-	 */
105
-	public function valid() {
106
-		if ((is_resource($this->resource) && get_resource_type($this->resource) === 'gd') ||
107
-			(is_object($this->resource) && get_class($this->resource) === \GdImage::class)) {
108
-			return true;
109
-		}
110
-
111
-		return false;
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
-		if ($this->valid()) {
130
-			$width = imagesx($this->resource);
131
-			if ($width !== false) {
132
-				return $width;
133
-			}
134
-		}
135
-		return -1;
136
-	}
137
-
138
-	/**
139
-	 * Returns the height of the image or -1 if no image is loaded.
140
-	 *
141
-	 * @return int
142
-	 */
143
-	public function height() {
144
-		if ($this->valid()) {
145
-			$height = imagesy($this->resource);
146
-			if ($height !== false) {
147
-				return $height;
148
-			}
149
-		}
150
-		return -1;
151
-	}
152
-
153
-	/**
154
-	 * Returns the width when the image orientation is top-left.
155
-	 *
156
-	 * @return int
157
-	 */
158
-	public function widthTopLeft() {
159
-		$o = $this->getOrientation();
160
-		$this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, ['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->width();
168
-			case 5: // Not tested
169
-			case 6:
170
-			case 7: // Not tested
171
-			case 8:
172
-				return $this->height();
173
-		}
174
-		return $this->width();
175
-	}
176
-
177
-	/**
178
-	 * Returns the height when the image orientation is top-left.
179
-	 *
180
-	 * @return int
181
-	 */
182
-	public function heightTopLeft() {
183
-		$o = $this->getOrientation();
184
-		$this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, ['app' => 'core']);
185
-		switch ($o) {
186
-			case -1:
187
-			case 1:
188
-			case 2: // Not tested
189
-			case 3:
190
-			case 4: // Not tested
191
-				return $this->height();
192
-			case 5: // Not tested
193
-			case 6:
194
-			case 7: // Not tested
195
-			case 8:
196
-				return $this->width();
197
-		}
198
-		return $this->height();
199
-	}
200
-
201
-	/**
202
-	 * Outputs the image.
203
-	 *
204
-	 * @param string $mimeType
205
-	 * @return bool
206
-	 */
207
-	public function show($mimeType = null) {
208
-		if ($mimeType === null) {
209
-			$mimeType = $this->mimeType();
210
-		}
211
-		header('Content-Type: ' . $mimeType);
212
-		return $this->_output(null, $mimeType);
213
-	}
214
-
215
-	/**
216
-	 * Saves the image.
217
-	 *
218
-	 * @param string $filePath
219
-	 * @param string $mimeType
220
-	 * @return bool
221
-	 */
222
-
223
-	public function save($filePath = null, $mimeType = null) {
224
-		if ($mimeType === null) {
225
-			$mimeType = $this->mimeType();
226
-		}
227
-		if ($filePath === null) {
228
-			if ($this->filePath === null) {
229
-				$this->logger->error(__METHOD__ . '(): called with no path.', ['app' => 'core']);
230
-				return false;
231
-			} else {
232
-				$filePath = $this->filePath;
233
-			}
234
-		}
235
-		return $this->_output($filePath, $mimeType);
236
-	}
237
-
238
-	/**
239
-	 * Outputs/saves the image.
240
-	 *
241
-	 * @param string $filePath
242
-	 * @param string $mimeType
243
-	 * @return bool
244
-	 * @throws Exception
245
-	 */
246
-	private function _output($filePath = null, $mimeType = null) {
247
-		if ($filePath) {
248
-			if (!file_exists(dirname($filePath))) {
249
-				mkdir(dirname($filePath), 0777, true);
250
-			}
251
-			$isWritable = is_writable(dirname($filePath));
252
-			if (!$isWritable) {
253
-				$this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', ['app' => 'core']);
254
-				return false;
255
-			} elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) {
256
-				$this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', ['app' => 'core']);
257
-				return false;
258
-			}
259
-		}
260
-		if (!$this->valid()) {
261
-			return false;
262
-		}
263
-
264
-		$imageType = $this->imageType;
265
-		if ($mimeType !== null) {
266
-			switch ($mimeType) {
267
-				case 'image/gif':
268
-					$imageType = IMAGETYPE_GIF;
269
-					break;
270
-				case 'image/jpeg':
271
-					$imageType = IMAGETYPE_JPEG;
272
-					break;
273
-				case 'image/png':
274
-					$imageType = IMAGETYPE_PNG;
275
-					break;
276
-				case 'image/x-xbitmap':
277
-					$imageType = IMAGETYPE_XBM;
278
-					break;
279
-				case 'image/bmp':
280
-				case 'image/x-ms-bmp':
281
-					$imageType = IMAGETYPE_BMP;
282
-					break;
283
-				default:
284
-					throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
285
-			}
286
-		}
287
-
288
-		switch ($imageType) {
289
-			case IMAGETYPE_GIF:
290
-				$retVal = imagegif($this->resource, $filePath);
291
-				break;
292
-			case IMAGETYPE_JPEG:
293
-				/** @psalm-suppress InvalidScalarArgument */
294
-				imageinterlace($this->resource, (PHP_VERSION_ID >= 80000 ? true : 1));
295
-				$retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality());
296
-				break;
297
-			case IMAGETYPE_PNG:
298
-				$retVal = imagepng($this->resource, $filePath);
299
-				break;
300
-			case IMAGETYPE_XBM:
301
-				if (function_exists('imagexbm')) {
302
-					$retVal = imagexbm($this->resource, $filePath);
303
-				} else {
304
-					throw new Exception('\OC_Image::_output(): imagexbm() is not supported.');
305
-				}
306
-
307
-				break;
308
-			case IMAGETYPE_WBMP:
309
-				$retVal = imagewbmp($this->resource, $filePath);
310
-				break;
311
-			case IMAGETYPE_BMP:
312
-				$retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
313
-				break;
314
-			default:
315
-				$retVal = imagepng($this->resource, $filePath);
316
-		}
317
-		return $retVal;
318
-	}
319
-
320
-	/**
321
-	 * Prints the image when called as $image().
322
-	 */
323
-	public function __invoke() {
324
-		return $this->show();
325
-	}
326
-
327
-	/**
328
-	 * @param resource|\GdImage $resource
329
-	 * @throws \InvalidArgumentException in case the supplied resource does not have the type "gd"
330
-	 */
331
-	public function setResource($resource) {
332
-		// For PHP<8
333
-		if (is_resource($resource) && get_resource_type($resource) === 'gd') {
334
-			$this->resource = $resource;
335
-			return;
336
-		}
337
-		// PHP 8 has real objects for GD stuff
338
-		if (is_object($resource) && get_class($resource) === \GdImage::class) {
339
-			$this->resource = $resource;
340
-			return;
341
-		}
342
-		throw new \InvalidArgumentException('Supplied resource is not of type "gd".');
343
-	}
344
-
345
-	/**
346
-	 * @return false|resource|\GdImage Returns the image resource if any
347
-	 */
348
-	public function resource() {
349
-		return $this->resource;
350
-	}
351
-
352
-	/**
353
-	 * @return string Returns the mimetype of the data. Returns the empty string
354
-	 * if the data is not valid.
355
-	 */
356
-	public function dataMimeType() {
357
-		if (!$this->valid()) {
358
-			return '';
359
-		}
360
-
361
-		switch ($this->mimeType) {
362
-			case 'image/png':
363
-			case 'image/jpeg':
364
-			case 'image/gif':
365
-				return $this->mimeType;
366
-			default:
367
-				return 'image/png';
368
-		}
369
-	}
370
-
371
-	/**
372
-	 * @return null|string Returns the raw image data.
373
-	 */
374
-	public function data() {
375
-		if (!$this->valid()) {
376
-			return null;
377
-		}
378
-		ob_start();
379
-		switch ($this->mimeType) {
380
-			case "image/png":
381
-				$res = imagepng($this->resource);
382
-				break;
383
-			case "image/jpeg":
384
-				/** @psalm-suppress InvalidScalarArgument */
385
-				imageinterlace($this->resource, (PHP_VERSION_ID >= 80000 ? true : 1));
386
-				$quality = $this->getJpegQuality();
387
-				if ($quality !== null) {
388
-					$res = imagejpeg($this->resource, null, $quality);
389
-				} else {
390
-					$res = imagejpeg($this->resource);
391
-				}
392
-				break;
393
-			case "image/gif":
394
-				$res = imagegif($this->resource);
395
-				break;
396
-			default:
397
-				$res = imagepng($this->resource);
398
-				$this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', ['app' => 'core']);
399
-				break;
400
-		}
401
-		if (!$res) {
402
-			$this->logger->error('OC_Image->data. Error getting image data.', ['app' => 'core']);
403
-		}
404
-		return ob_get_clean();
405
-	}
406
-
407
-	/**
408
-	 * @return string - base64 encoded, which is suitable for embedding in a VCard.
409
-	 */
410
-	public function __toString() {
411
-		return base64_encode($this->data());
412
-	}
413
-
414
-	/**
415
-	 * @return int|null
416
-	 */
417
-	protected function getJpegQuality() {
418
-		$quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
419
-		if ($quality !== null) {
420
-			$quality = min(100, max(10, (int) $quality));
421
-		}
422
-		return $quality;
423
-	}
424
-
425
-	/**
426
-	 * (I'm open for suggestions on better method name ;)
427
-	 * Get the orientation based on EXIF data.
428
-	 *
429
-	 * @return int The orientation or -1 if no EXIF data is available.
430
-	 */
431
-	public function getOrientation() {
432
-		if ($this->exif !== null) {
433
-			return $this->exif['Orientation'];
434
-		}
435
-
436
-		if ($this->imageType !== IMAGETYPE_JPEG) {
437
-			$this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', ['app' => 'core']);
438
-			return -1;
439
-		}
440
-		if (!is_callable('exif_read_data')) {
441
-			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
442
-			return -1;
443
-		}
444
-		if (!$this->valid()) {
445
-			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
446
-			return -1;
447
-		}
448
-		if (is_null($this->filePath) || !is_readable($this->filePath)) {
449
-			$this->logger->debug('OC_Image->fixOrientation() No readable file path set.', ['app' => 'core']);
450
-			return -1;
451
-		}
452
-		$exif = @exif_read_data($this->filePath, 'IFD0');
453
-		if (!$exif) {
454
-			return -1;
455
-		}
456
-		if (!isset($exif['Orientation'])) {
457
-			return -1;
458
-		}
459
-		$this->exif = $exif;
460
-		return $exif['Orientation'];
461
-	}
462
-
463
-	public function readExif($data) {
464
-		if (!is_callable('exif_read_data')) {
465
-			$this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
466
-			return;
467
-		}
468
-		if (!$this->valid()) {
469
-			$this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
470
-			return;
471
-		}
472
-
473
-		$exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
474
-		if (!$exif) {
475
-			return;
476
-		}
477
-		if (!isset($exif['Orientation'])) {
478
-			return;
479
-		}
480
-		$this->exif = $exif;
481
-	}
482
-
483
-	/**
484
-	 * (I'm open for suggestions on better method name ;)
485
-	 * Fixes orientation based on EXIF data.
486
-	 *
487
-	 * @return bool
488
-	 */
489
-	public function fixOrientation() {
490
-		if (!$this->valid()) {
491
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
492
-			return false;
493
-		}
494
-		$o = $this->getOrientation();
495
-		$this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, ['app' => 'core']);
496
-		$rotate = 0;
497
-		$flip = false;
498
-		switch ($o) {
499
-			case -1:
500
-				return false; //Nothing to fix
501
-			case 1:
502
-				$rotate = 0;
503
-				break;
504
-			case 2:
505
-				$rotate = 0;
506
-				$flip = true;
507
-				break;
508
-			case 3:
509
-				$rotate = 180;
510
-				break;
511
-			case 4:
512
-				$rotate = 180;
513
-				$flip = true;
514
-				break;
515
-			case 5:
516
-				$rotate = 90;
517
-				$flip = true;
518
-				break;
519
-			case 6:
520
-				$rotate = 270;
521
-				break;
522
-			case 7:
523
-				$rotate = 270;
524
-				$flip = true;
525
-				break;
526
-			case 8:
527
-				$rotate = 90;
528
-				break;
529
-		}
530
-		if ($flip && function_exists('imageflip')) {
531
-			imageflip($this->resource, IMG_FLIP_HORIZONTAL);
532
-		}
533
-		if ($rotate) {
534
-			$res = imagerotate($this->resource, $rotate, 0);
535
-			if ($res) {
536
-				if (imagealphablending($res, true)) {
537
-					if (imagesavealpha($res, true)) {
538
-						imagedestroy($this->resource);
539
-						$this->resource = $res;
540
-						return true;
541
-					} else {
542
-						$this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', ['app' => 'core']);
543
-						return false;
544
-					}
545
-				} else {
546
-					$this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', ['app' => 'core']);
547
-					return false;
548
-				}
549
-			} else {
550
-				$this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', ['app' => 'core']);
551
-				return false;
552
-			}
553
-		}
554
-		return false;
555
-	}
556
-
557
-	/**
558
-	 * Loads an image from an open file handle.
559
-	 * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
560
-	 *
561
-	 * @param resource $handle
562
-	 * @return resource|\GdImage|false An image resource or false on error
563
-	 */
564
-	public function loadFromFileHandle($handle) {
565
-		$contents = stream_get_contents($handle);
566
-		if ($this->loadFromData($contents)) {
567
-			return $this->resource;
568
-		}
569
-		return false;
570
-	}
571
-
572
-	/**
573
-	 * Check if allocating an image with the given size is allowed.
574
-	 *
575
-	 * @param int $width The image width.
576
-	 * @param int $height The image height.
577
-	 * @return bool true if allocating is allowed, false otherwise
578
-	 */
579
-	private function checkImageMemory($width, $height) {
580
-		$memory_limit = $this->config->getSystemValueInt('preview_max_memory', self::DEFAULT_MEMORY_LIMIT);
581
-		if ($memory_limit < 0) {
582
-			// Not limited.
583
-			return true;
584
-		}
585
-
586
-		// Assume 32 bits per pixel.
587
-		if ($width * $height * 4 > $memory_limit * 1024 * 1024) {
588
-			$this->logger->debug('Image size of ' . $width . 'x' . $height . ' would exceed allowed memory limit of ' . $memory_limit);
589
-			return false;
590
-		}
591
-
592
-		return true;
593
-	}
594
-
595
-	/**
596
-	 * Check if loading an image file from the given path is allowed.
597
-	 *
598
-	 * @param string $path The path to a local file.
599
-	 * @return bool true if allocating is allowed, false otherwise
600
-	 */
601
-	private function checkImageSize($path) {
602
-		$size = getimagesize($path);
603
-		if (!$size) {
604
-			return true;
605
-		}
606
-
607
-		$width = $size[0];
608
-		$height = $size[1];
609
-		if (!$this->checkImageMemory($width, $height)) {
610
-			return false;
611
-		}
612
-
613
-		return true;
614
-	}
615
-
616
-	/**
617
-	 * Check if loading an image from the given data is allowed.
618
-	 *
619
-	 * @param string $data A string of image data as read from a file.
620
-	 * @return bool true if allocating is allowed, false otherwise
621
-	 */
622
-	private function checkImageDataSize($data) {
623
-		$size = getimagesizefromstring($data);
624
-		if (!$size) {
625
-			return true;
626
-		}
627
-
628
-		$width = $size[0];
629
-		$height = $size[1];
630
-		if (!$this->checkImageMemory($width, $height)) {
631
-			return false;
632
-		}
633
-
634
-		return true;
635
-	}
636
-
637
-	/**
638
-	 * Loads an image from a local file.
639
-	 *
640
-	 * @param bool|string $imagePath The path to a local file.
641
-	 * @return bool|resource|\GdImage An image resource or false on error
642
-	 */
643
-	public function loadFromFile($imagePath = false) {
644
-		// exif_imagetype throws "read error!" if file is less than 12 byte
645
-		if (is_bool($imagePath) || !@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) {
646
-			return false;
647
-		}
648
-		$iType = exif_imagetype($imagePath);
649
-		switch ($iType) {
650
-			case IMAGETYPE_GIF:
651
-				if (imagetypes() & IMG_GIF) {
652
-					if (!$this->checkImageSize($imagePath)) {
653
-						return false;
654
-					}
655
-					$this->resource = imagecreatefromgif($imagePath);
656
-					if ($this->resource) {
657
-						// Preserve transparency
658
-						imagealphablending($this->resource, true);
659
-						imagesavealpha($this->resource, true);
660
-					} else {
661
-						$this->logger->debug('OC_Image->loadFromFile, GIF image not valid: ' . $imagePath, ['app' => 'core']);
662
-					}
663
-				} else {
664
-					$this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, ['app' => 'core']);
665
-				}
666
-				break;
667
-			case IMAGETYPE_JPEG:
668
-				if (imagetypes() & IMG_JPG) {
669
-					if (!$this->checkImageSize($imagePath)) {
670
-						return false;
671
-					}
672
-					if (getimagesize($imagePath) !== false) {
673
-						$this->resource = @imagecreatefromjpeg($imagePath);
674
-					} else {
675
-						$this->logger->debug('OC_Image->loadFromFile, JPG image not valid: ' . $imagePath, ['app' => 'core']);
676
-					}
677
-				} else {
678
-					$this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, ['app' => 'core']);
679
-				}
680
-				break;
681
-			case IMAGETYPE_PNG:
682
-				if (imagetypes() & IMG_PNG) {
683
-					if (!$this->checkImageSize($imagePath)) {
684
-						return false;
685
-					}
686
-					$this->resource = @imagecreatefrompng($imagePath);
687
-					if ($this->resource) {
688
-						// Preserve transparency
689
-						imagealphablending($this->resource, true);
690
-						imagesavealpha($this->resource, true);
691
-					} else {
692
-						$this->logger->debug('OC_Image->loadFromFile, PNG image not valid: ' . $imagePath, ['app' => 'core']);
693
-					}
694
-				} else {
695
-					$this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, ['app' => 'core']);
696
-				}
697
-				break;
698
-			case IMAGETYPE_XBM:
699
-				if (imagetypes() & IMG_XPM) {
700
-					if (!$this->checkImageSize($imagePath)) {
701
-						return false;
702
-					}
703
-					$this->resource = @imagecreatefromxbm($imagePath);
704
-				} else {
705
-					$this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, ['app' => 'core']);
706
-				}
707
-				break;
708
-			case IMAGETYPE_WBMP:
709
-				if (imagetypes() & IMG_WBMP) {
710
-					if (!$this->checkImageSize($imagePath)) {
711
-						return false;
712
-					}
713
-					$this->resource = @imagecreatefromwbmp($imagePath);
714
-				} else {
715
-					$this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']);
716
-				}
717
-				break;
718
-			case IMAGETYPE_BMP:
719
-				$this->resource = $this->imagecreatefrombmp($imagePath);
720
-				break;
721
-			case IMAGETYPE_WEBP:
722
-				if (imagetypes() & IMG_WEBP) {
723
-					if (!$this->checkImageSize($imagePath)) {
724
-						return false;
725
-					}
726
-					$this->resource = @imagecreatefromwebp($imagePath);
727
-				} else {
728
-					$this->logger->debug('OC_Image->loadFromFile, webp images not supported: ' . $imagePath, ['app' => 'core']);
729
-				}
730
-				break;
731
-			/*
50
+    // Default memory limit for images to load (128 MBytes).
51
+    protected const DEFAULT_MEMORY_LIMIT = 128;
52
+
53
+    /** @var false|resource|\GdImage */
54
+    protected $resource = false; // tmp resource.
55
+    /** @var int */
56
+    protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident.
57
+    /** @var string */
58
+    protected $mimeType = 'image/png'; // Default to png
59
+    /** @var int */
60
+    protected $bitDepth = 24;
61
+    /** @var null|string */
62
+    protected $filePath = null;
63
+    /** @var finfo */
64
+    private $fileInfo;
65
+    /** @var \OCP\ILogger */
66
+    private $logger;
67
+    /** @var \OCP\IConfig */
68
+    private $config;
69
+    /** @var array */
70
+    private $exif;
71
+
72
+    /**
73
+     * Constructor.
74
+     *
75
+     * @param resource|string|\GdImage $imageRef The path to a local file, a base64 encoded string or a resource created by
76
+     * an imagecreate* function.
77
+     * @param \OCP\ILogger $logger
78
+     * @param \OCP\IConfig $config
79
+     * @throws \InvalidArgumentException in case the $imageRef parameter is not null
80
+     */
81
+    public function __construct($imageRef = null, \OCP\ILogger $logger = null, \OCP\IConfig $config = null) {
82
+        $this->logger = $logger;
83
+        if ($logger === null) {
84
+            $this->logger = \OC::$server->getLogger();
85
+        }
86
+        $this->config = $config;
87
+        if ($config === null) {
88
+            $this->config = \OC::$server->getConfig();
89
+        }
90
+
91
+        if (\OC_Util::fileInfoLoaded()) {
92
+            $this->fileInfo = new finfo(FILEINFO_MIME_TYPE);
93
+        }
94
+
95
+        if ($imageRef !== null) {
96
+            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.');
97
+        }
98
+    }
99
+
100
+    /**
101
+     * Determine whether the object contains an image resource.
102
+     *
103
+     * @return bool
104
+     */
105
+    public function valid() {
106
+        if ((is_resource($this->resource) && get_resource_type($this->resource) === 'gd') ||
107
+            (is_object($this->resource) && get_class($this->resource) === \GdImage::class)) {
108
+            return true;
109
+        }
110
+
111
+        return false;
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
+        if ($this->valid()) {
130
+            $width = imagesx($this->resource);
131
+            if ($width !== false) {
132
+                return $width;
133
+            }
134
+        }
135
+        return -1;
136
+    }
137
+
138
+    /**
139
+     * Returns the height of the image or -1 if no image is loaded.
140
+     *
141
+     * @return int
142
+     */
143
+    public function height() {
144
+        if ($this->valid()) {
145
+            $height = imagesy($this->resource);
146
+            if ($height !== false) {
147
+                return $height;
148
+            }
149
+        }
150
+        return -1;
151
+    }
152
+
153
+    /**
154
+     * Returns the width when the image orientation is top-left.
155
+     *
156
+     * @return int
157
+     */
158
+    public function widthTopLeft() {
159
+        $o = $this->getOrientation();
160
+        $this->logger->debug('OC_Image->widthTopLeft() Orientation: ' . $o, ['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->width();
168
+            case 5: // Not tested
169
+            case 6:
170
+            case 7: // Not tested
171
+            case 8:
172
+                return $this->height();
173
+        }
174
+        return $this->width();
175
+    }
176
+
177
+    /**
178
+     * Returns the height when the image orientation is top-left.
179
+     *
180
+     * @return int
181
+     */
182
+    public function heightTopLeft() {
183
+        $o = $this->getOrientation();
184
+        $this->logger->debug('OC_Image->heightTopLeft() Orientation: ' . $o, ['app' => 'core']);
185
+        switch ($o) {
186
+            case -1:
187
+            case 1:
188
+            case 2: // Not tested
189
+            case 3:
190
+            case 4: // Not tested
191
+                return $this->height();
192
+            case 5: // Not tested
193
+            case 6:
194
+            case 7: // Not tested
195
+            case 8:
196
+                return $this->width();
197
+        }
198
+        return $this->height();
199
+    }
200
+
201
+    /**
202
+     * Outputs the image.
203
+     *
204
+     * @param string $mimeType
205
+     * @return bool
206
+     */
207
+    public function show($mimeType = null) {
208
+        if ($mimeType === null) {
209
+            $mimeType = $this->mimeType();
210
+        }
211
+        header('Content-Type: ' . $mimeType);
212
+        return $this->_output(null, $mimeType);
213
+    }
214
+
215
+    /**
216
+     * Saves the image.
217
+     *
218
+     * @param string $filePath
219
+     * @param string $mimeType
220
+     * @return bool
221
+     */
222
+
223
+    public function save($filePath = null, $mimeType = null) {
224
+        if ($mimeType === null) {
225
+            $mimeType = $this->mimeType();
226
+        }
227
+        if ($filePath === null) {
228
+            if ($this->filePath === null) {
229
+                $this->logger->error(__METHOD__ . '(): called with no path.', ['app' => 'core']);
230
+                return false;
231
+            } else {
232
+                $filePath = $this->filePath;
233
+            }
234
+        }
235
+        return $this->_output($filePath, $mimeType);
236
+    }
237
+
238
+    /**
239
+     * Outputs/saves the image.
240
+     *
241
+     * @param string $filePath
242
+     * @param string $mimeType
243
+     * @return bool
244
+     * @throws Exception
245
+     */
246
+    private function _output($filePath = null, $mimeType = null) {
247
+        if ($filePath) {
248
+            if (!file_exists(dirname($filePath))) {
249
+                mkdir(dirname($filePath), 0777, true);
250
+            }
251
+            $isWritable = is_writable(dirname($filePath));
252
+            if (!$isWritable) {
253
+                $this->logger->error(__METHOD__ . '(): Directory \'' . dirname($filePath) . '\' is not writable.', ['app' => 'core']);
254
+                return false;
255
+            } elseif ($isWritable && file_exists($filePath) && !is_writable($filePath)) {
256
+                $this->logger->error(__METHOD__ . '(): File \'' . $filePath . '\' is not writable.', ['app' => 'core']);
257
+                return false;
258
+            }
259
+        }
260
+        if (!$this->valid()) {
261
+            return false;
262
+        }
263
+
264
+        $imageType = $this->imageType;
265
+        if ($mimeType !== null) {
266
+            switch ($mimeType) {
267
+                case 'image/gif':
268
+                    $imageType = IMAGETYPE_GIF;
269
+                    break;
270
+                case 'image/jpeg':
271
+                    $imageType = IMAGETYPE_JPEG;
272
+                    break;
273
+                case 'image/png':
274
+                    $imageType = IMAGETYPE_PNG;
275
+                    break;
276
+                case 'image/x-xbitmap':
277
+                    $imageType = IMAGETYPE_XBM;
278
+                    break;
279
+                case 'image/bmp':
280
+                case 'image/x-ms-bmp':
281
+                    $imageType = IMAGETYPE_BMP;
282
+                    break;
283
+                default:
284
+                    throw new Exception('\OC_Image::_output(): "' . $mimeType . '" is not supported when forcing a specific output format');
285
+            }
286
+        }
287
+
288
+        switch ($imageType) {
289
+            case IMAGETYPE_GIF:
290
+                $retVal = imagegif($this->resource, $filePath);
291
+                break;
292
+            case IMAGETYPE_JPEG:
293
+                /** @psalm-suppress InvalidScalarArgument */
294
+                imageinterlace($this->resource, (PHP_VERSION_ID >= 80000 ? true : 1));
295
+                $retVal = imagejpeg($this->resource, $filePath, $this->getJpegQuality());
296
+                break;
297
+            case IMAGETYPE_PNG:
298
+                $retVal = imagepng($this->resource, $filePath);
299
+                break;
300
+            case IMAGETYPE_XBM:
301
+                if (function_exists('imagexbm')) {
302
+                    $retVal = imagexbm($this->resource, $filePath);
303
+                } else {
304
+                    throw new Exception('\OC_Image::_output(): imagexbm() is not supported.');
305
+                }
306
+
307
+                break;
308
+            case IMAGETYPE_WBMP:
309
+                $retVal = imagewbmp($this->resource, $filePath);
310
+                break;
311
+            case IMAGETYPE_BMP:
312
+                $retVal = imagebmp($this->resource, $filePath, $this->bitDepth);
313
+                break;
314
+            default:
315
+                $retVal = imagepng($this->resource, $filePath);
316
+        }
317
+        return $retVal;
318
+    }
319
+
320
+    /**
321
+     * Prints the image when called as $image().
322
+     */
323
+    public function __invoke() {
324
+        return $this->show();
325
+    }
326
+
327
+    /**
328
+     * @param resource|\GdImage $resource
329
+     * @throws \InvalidArgumentException in case the supplied resource does not have the type "gd"
330
+     */
331
+    public function setResource($resource) {
332
+        // For PHP<8
333
+        if (is_resource($resource) && get_resource_type($resource) === 'gd') {
334
+            $this->resource = $resource;
335
+            return;
336
+        }
337
+        // PHP 8 has real objects for GD stuff
338
+        if (is_object($resource) && get_class($resource) === \GdImage::class) {
339
+            $this->resource = $resource;
340
+            return;
341
+        }
342
+        throw new \InvalidArgumentException('Supplied resource is not of type "gd".');
343
+    }
344
+
345
+    /**
346
+     * @return false|resource|\GdImage Returns the image resource if any
347
+     */
348
+    public function resource() {
349
+        return $this->resource;
350
+    }
351
+
352
+    /**
353
+     * @return string Returns the mimetype of the data. Returns the empty string
354
+     * if the data is not valid.
355
+     */
356
+    public function dataMimeType() {
357
+        if (!$this->valid()) {
358
+            return '';
359
+        }
360
+
361
+        switch ($this->mimeType) {
362
+            case 'image/png':
363
+            case 'image/jpeg':
364
+            case 'image/gif':
365
+                return $this->mimeType;
366
+            default:
367
+                return 'image/png';
368
+        }
369
+    }
370
+
371
+    /**
372
+     * @return null|string Returns the raw image data.
373
+     */
374
+    public function data() {
375
+        if (!$this->valid()) {
376
+            return null;
377
+        }
378
+        ob_start();
379
+        switch ($this->mimeType) {
380
+            case "image/png":
381
+                $res = imagepng($this->resource);
382
+                break;
383
+            case "image/jpeg":
384
+                /** @psalm-suppress InvalidScalarArgument */
385
+                imageinterlace($this->resource, (PHP_VERSION_ID >= 80000 ? true : 1));
386
+                $quality = $this->getJpegQuality();
387
+                if ($quality !== null) {
388
+                    $res = imagejpeg($this->resource, null, $quality);
389
+                } else {
390
+                    $res = imagejpeg($this->resource);
391
+                }
392
+                break;
393
+            case "image/gif":
394
+                $res = imagegif($this->resource);
395
+                break;
396
+            default:
397
+                $res = imagepng($this->resource);
398
+                $this->logger->info('OC_Image->data. Could not guess mime-type, defaulting to png', ['app' => 'core']);
399
+                break;
400
+        }
401
+        if (!$res) {
402
+            $this->logger->error('OC_Image->data. Error getting image data.', ['app' => 'core']);
403
+        }
404
+        return ob_get_clean();
405
+    }
406
+
407
+    /**
408
+     * @return string - base64 encoded, which is suitable for embedding in a VCard.
409
+     */
410
+    public function __toString() {
411
+        return base64_encode($this->data());
412
+    }
413
+
414
+    /**
415
+     * @return int|null
416
+     */
417
+    protected function getJpegQuality() {
418
+        $quality = $this->config->getAppValue('preview', 'jpeg_quality', '80');
419
+        if ($quality !== null) {
420
+            $quality = min(100, max(10, (int) $quality));
421
+        }
422
+        return $quality;
423
+    }
424
+
425
+    /**
426
+     * (I'm open for suggestions on better method name ;)
427
+     * Get the orientation based on EXIF data.
428
+     *
429
+     * @return int The orientation or -1 if no EXIF data is available.
430
+     */
431
+    public function getOrientation() {
432
+        if ($this->exif !== null) {
433
+            return $this->exif['Orientation'];
434
+        }
435
+
436
+        if ($this->imageType !== IMAGETYPE_JPEG) {
437
+            $this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', ['app' => 'core']);
438
+            return -1;
439
+        }
440
+        if (!is_callable('exif_read_data')) {
441
+            $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
442
+            return -1;
443
+        }
444
+        if (!$this->valid()) {
445
+            $this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
446
+            return -1;
447
+        }
448
+        if (is_null($this->filePath) || !is_readable($this->filePath)) {
449
+            $this->logger->debug('OC_Image->fixOrientation() No readable file path set.', ['app' => 'core']);
450
+            return -1;
451
+        }
452
+        $exif = @exif_read_data($this->filePath, 'IFD0');
453
+        if (!$exif) {
454
+            return -1;
455
+        }
456
+        if (!isset($exif['Orientation'])) {
457
+            return -1;
458
+        }
459
+        $this->exif = $exif;
460
+        return $exif['Orientation'];
461
+    }
462
+
463
+    public function readExif($data) {
464
+        if (!is_callable('exif_read_data')) {
465
+            $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', ['app' => 'core']);
466
+            return;
467
+        }
468
+        if (!$this->valid()) {
469
+            $this->logger->debug('OC_Image->fixOrientation() No image loaded.', ['app' => 'core']);
470
+            return;
471
+        }
472
+
473
+        $exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
474
+        if (!$exif) {
475
+            return;
476
+        }
477
+        if (!isset($exif['Orientation'])) {
478
+            return;
479
+        }
480
+        $this->exif = $exif;
481
+    }
482
+
483
+    /**
484
+     * (I'm open for suggestions on better method name ;)
485
+     * Fixes orientation based on EXIF data.
486
+     *
487
+     * @return bool
488
+     */
489
+    public function fixOrientation() {
490
+        if (!$this->valid()) {
491
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
492
+            return false;
493
+        }
494
+        $o = $this->getOrientation();
495
+        $this->logger->debug('OC_Image->fixOrientation() Orientation: ' . $o, ['app' => 'core']);
496
+        $rotate = 0;
497
+        $flip = false;
498
+        switch ($o) {
499
+            case -1:
500
+                return false; //Nothing to fix
501
+            case 1:
502
+                $rotate = 0;
503
+                break;
504
+            case 2:
505
+                $rotate = 0;
506
+                $flip = true;
507
+                break;
508
+            case 3:
509
+                $rotate = 180;
510
+                break;
511
+            case 4:
512
+                $rotate = 180;
513
+                $flip = true;
514
+                break;
515
+            case 5:
516
+                $rotate = 90;
517
+                $flip = true;
518
+                break;
519
+            case 6:
520
+                $rotate = 270;
521
+                break;
522
+            case 7:
523
+                $rotate = 270;
524
+                $flip = true;
525
+                break;
526
+            case 8:
527
+                $rotate = 90;
528
+                break;
529
+        }
530
+        if ($flip && function_exists('imageflip')) {
531
+            imageflip($this->resource, IMG_FLIP_HORIZONTAL);
532
+        }
533
+        if ($rotate) {
534
+            $res = imagerotate($this->resource, $rotate, 0);
535
+            if ($res) {
536
+                if (imagealphablending($res, true)) {
537
+                    if (imagesavealpha($res, true)) {
538
+                        imagedestroy($this->resource);
539
+                        $this->resource = $res;
540
+                        return true;
541
+                    } else {
542
+                        $this->logger->debug('OC_Image->fixOrientation() Error during alpha-saving', ['app' => 'core']);
543
+                        return false;
544
+                    }
545
+                } else {
546
+                    $this->logger->debug('OC_Image->fixOrientation() Error during alpha-blending', ['app' => 'core']);
547
+                    return false;
548
+                }
549
+            } else {
550
+                $this->logger->debug('OC_Image->fixOrientation() Error during orientation fixing', ['app' => 'core']);
551
+                return false;
552
+            }
553
+        }
554
+        return false;
555
+    }
556
+
557
+    /**
558
+     * Loads an image from an open file handle.
559
+     * It is the responsibility of the caller to position the pointer at the correct place and to close the handle again.
560
+     *
561
+     * @param resource $handle
562
+     * @return resource|\GdImage|false An image resource or false on error
563
+     */
564
+    public function loadFromFileHandle($handle) {
565
+        $contents = stream_get_contents($handle);
566
+        if ($this->loadFromData($contents)) {
567
+            return $this->resource;
568
+        }
569
+        return false;
570
+    }
571
+
572
+    /**
573
+     * Check if allocating an image with the given size is allowed.
574
+     *
575
+     * @param int $width The image width.
576
+     * @param int $height The image height.
577
+     * @return bool true if allocating is allowed, false otherwise
578
+     */
579
+    private function checkImageMemory($width, $height) {
580
+        $memory_limit = $this->config->getSystemValueInt('preview_max_memory', self::DEFAULT_MEMORY_LIMIT);
581
+        if ($memory_limit < 0) {
582
+            // Not limited.
583
+            return true;
584
+        }
585
+
586
+        // Assume 32 bits per pixel.
587
+        if ($width * $height * 4 > $memory_limit * 1024 * 1024) {
588
+            $this->logger->debug('Image size of ' . $width . 'x' . $height . ' would exceed allowed memory limit of ' . $memory_limit);
589
+            return false;
590
+        }
591
+
592
+        return true;
593
+    }
594
+
595
+    /**
596
+     * Check if loading an image file from the given path is allowed.
597
+     *
598
+     * @param string $path The path to a local file.
599
+     * @return bool true if allocating is allowed, false otherwise
600
+     */
601
+    private function checkImageSize($path) {
602
+        $size = getimagesize($path);
603
+        if (!$size) {
604
+            return true;
605
+        }
606
+
607
+        $width = $size[0];
608
+        $height = $size[1];
609
+        if (!$this->checkImageMemory($width, $height)) {
610
+            return false;
611
+        }
612
+
613
+        return true;
614
+    }
615
+
616
+    /**
617
+     * Check if loading an image from the given data is allowed.
618
+     *
619
+     * @param string $data A string of image data as read from a file.
620
+     * @return bool true if allocating is allowed, false otherwise
621
+     */
622
+    private function checkImageDataSize($data) {
623
+        $size = getimagesizefromstring($data);
624
+        if (!$size) {
625
+            return true;
626
+        }
627
+
628
+        $width = $size[0];
629
+        $height = $size[1];
630
+        if (!$this->checkImageMemory($width, $height)) {
631
+            return false;
632
+        }
633
+
634
+        return true;
635
+    }
636
+
637
+    /**
638
+     * Loads an image from a local file.
639
+     *
640
+     * @param bool|string $imagePath The path to a local file.
641
+     * @return bool|resource|\GdImage An image resource or false on error
642
+     */
643
+    public function loadFromFile($imagePath = false) {
644
+        // exif_imagetype throws "read error!" if file is less than 12 byte
645
+        if (is_bool($imagePath) || !@is_file($imagePath) || !file_exists($imagePath) || filesize($imagePath) < 12 || !is_readable($imagePath)) {
646
+            return false;
647
+        }
648
+        $iType = exif_imagetype($imagePath);
649
+        switch ($iType) {
650
+            case IMAGETYPE_GIF:
651
+                if (imagetypes() & IMG_GIF) {
652
+                    if (!$this->checkImageSize($imagePath)) {
653
+                        return false;
654
+                    }
655
+                    $this->resource = imagecreatefromgif($imagePath);
656
+                    if ($this->resource) {
657
+                        // Preserve transparency
658
+                        imagealphablending($this->resource, true);
659
+                        imagesavealpha($this->resource, true);
660
+                    } else {
661
+                        $this->logger->debug('OC_Image->loadFromFile, GIF image not valid: ' . $imagePath, ['app' => 'core']);
662
+                    }
663
+                } else {
664
+                    $this->logger->debug('OC_Image->loadFromFile, GIF images not supported: ' . $imagePath, ['app' => 'core']);
665
+                }
666
+                break;
667
+            case IMAGETYPE_JPEG:
668
+                if (imagetypes() & IMG_JPG) {
669
+                    if (!$this->checkImageSize($imagePath)) {
670
+                        return false;
671
+                    }
672
+                    if (getimagesize($imagePath) !== false) {
673
+                        $this->resource = @imagecreatefromjpeg($imagePath);
674
+                    } else {
675
+                        $this->logger->debug('OC_Image->loadFromFile, JPG image not valid: ' . $imagePath, ['app' => 'core']);
676
+                    }
677
+                } else {
678
+                    $this->logger->debug('OC_Image->loadFromFile, JPG images not supported: ' . $imagePath, ['app' => 'core']);
679
+                }
680
+                break;
681
+            case IMAGETYPE_PNG:
682
+                if (imagetypes() & IMG_PNG) {
683
+                    if (!$this->checkImageSize($imagePath)) {
684
+                        return false;
685
+                    }
686
+                    $this->resource = @imagecreatefrompng($imagePath);
687
+                    if ($this->resource) {
688
+                        // Preserve transparency
689
+                        imagealphablending($this->resource, true);
690
+                        imagesavealpha($this->resource, true);
691
+                    } else {
692
+                        $this->logger->debug('OC_Image->loadFromFile, PNG image not valid: ' . $imagePath, ['app' => 'core']);
693
+                    }
694
+                } else {
695
+                    $this->logger->debug('OC_Image->loadFromFile, PNG images not supported: ' . $imagePath, ['app' => 'core']);
696
+                }
697
+                break;
698
+            case IMAGETYPE_XBM:
699
+                if (imagetypes() & IMG_XPM) {
700
+                    if (!$this->checkImageSize($imagePath)) {
701
+                        return false;
702
+                    }
703
+                    $this->resource = @imagecreatefromxbm($imagePath);
704
+                } else {
705
+                    $this->logger->debug('OC_Image->loadFromFile, XBM/XPM images not supported: ' . $imagePath, ['app' => 'core']);
706
+                }
707
+                break;
708
+            case IMAGETYPE_WBMP:
709
+                if (imagetypes() & IMG_WBMP) {
710
+                    if (!$this->checkImageSize($imagePath)) {
711
+                        return false;
712
+                    }
713
+                    $this->resource = @imagecreatefromwbmp($imagePath);
714
+                } else {
715
+                    $this->logger->debug('OC_Image->loadFromFile, WBMP images not supported: ' . $imagePath, ['app' => 'core']);
716
+                }
717
+                break;
718
+            case IMAGETYPE_BMP:
719
+                $this->resource = $this->imagecreatefrombmp($imagePath);
720
+                break;
721
+            case IMAGETYPE_WEBP:
722
+                if (imagetypes() & IMG_WEBP) {
723
+                    if (!$this->checkImageSize($imagePath)) {
724
+                        return false;
725
+                    }
726
+                    $this->resource = @imagecreatefromwebp($imagePath);
727
+                } else {
728
+                    $this->logger->debug('OC_Image->loadFromFile, webp images not supported: ' . $imagePath, ['app' => 'core']);
729
+                }
730
+                break;
731
+            /*
732 732
 			case IMAGETYPE_TIFF_II: // (intel byte order)
733 733
 				break;
734 734
 			case IMAGETYPE_TIFF_MM: // (motorola byte order)
@@ -752,687 +752,687 @@  discard block
 block discarded – undo
752 752
 			case IMAGETYPE_PSD:
753 753
 				break;
754 754
 			*/
755
-			default:
756
-
757
-				// this is mostly file created from encrypted file
758
-				$data = file_get_contents($imagePath);
759
-				if (!$this->checkImageDataSize($data)) {
760
-					return false;
761
-				}
762
-				$this->resource = imagecreatefromstring($data);
763
-				$iType = IMAGETYPE_PNG;
764
-				$this->logger->debug('OC_Image->loadFromFile, Default', ['app' => 'core']);
765
-				break;
766
-		}
767
-		if ($this->valid()) {
768
-			$this->imageType = $iType;
769
-			$this->mimeType = image_type_to_mime_type($iType);
770
-			$this->filePath = $imagePath;
771
-		}
772
-		return $this->resource;
773
-	}
774
-
775
-	/**
776
-	 * Loads an image from a string of data.
777
-	 *
778
-	 * @param string $str A string of image data as read from a file.
779
-	 * @return bool|resource|\GdImage An image resource or false on error
780
-	 */
781
-	public function loadFromData($str) {
782
-		if (!is_string($str)) {
783
-			return false;
784
-		}
785
-		if (!$this->checkImageDataSize($str)) {
786
-			return false;
787
-		}
788
-		$this->resource = @imagecreatefromstring($str);
789
-		if ($this->fileInfo) {
790
-			$this->mimeType = $this->fileInfo->buffer($str);
791
-		}
792
-		if ($this->valid()) {
793
-			imagealphablending($this->resource, false);
794
-			imagesavealpha($this->resource, true);
795
-		}
796
-
797
-		if (!$this->resource) {
798
-			$this->logger->debug('OC_Image->loadFromFile, could not load', ['app' => 'core']);
799
-			return false;
800
-		}
801
-		return $this->resource;
802
-	}
803
-
804
-	/**
805
-	 * Loads an image from a base64 encoded string.
806
-	 *
807
-	 * @param string $str A string base64 encoded string of image data.
808
-	 * @return bool|resource|\GdImage An image resource or false on error
809
-	 */
810
-	public function loadFromBase64($str) {
811
-		if (!is_string($str)) {
812
-			return false;
813
-		}
814
-		$data = base64_decode($str);
815
-		if ($data) { // try to load from string data
816
-			if (!$this->checkImageDataSize($data)) {
817
-				return false;
818
-			}
819
-			$this->resource = @imagecreatefromstring($data);
820
-			if ($this->fileInfo) {
821
-				$this->mimeType = $this->fileInfo->buffer($data);
822
-			}
823
-			if (!$this->resource) {
824
-				$this->logger->debug('OC_Image->loadFromBase64, could not load', ['app' => 'core']);
825
-				return false;
826
-			}
827
-			return $this->resource;
828
-		} else {
829
-			return false;
830
-		}
831
-	}
832
-
833
-	/**
834
-	 * Create a new image from file or URL
835
-	 *
836
-	 * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
837
-	 * @version 1.00
838
-	 * @param string $fileName <p>
839
-	 * Path to the BMP image.
840
-	 * </p>
841
-	 * @return bool|resource|\GdImage an image resource identifier on success, <b>FALSE</b> on errors.
842
-	 */
843
-	private function imagecreatefrombmp($fileName) {
844
-		if (!($fh = fopen($fileName, 'rb'))) {
845
-			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, ['app' => 'core']);
846
-			return false;
847
-		}
848
-		// read file header
849
-		$meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
850
-		// check for bitmap
851
-		if ($meta['type'] != 19778) {
852
-			fclose($fh);
853
-			$this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
854
-			return false;
855
-		}
856
-		// read image header
857
-		$meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
858
-		// read additional 16bit header
859
-		if ($meta['bits'] == 16) {
860
-			$meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
861
-		}
862
-		// set bytes and padding
863
-		$meta['bytes'] = $meta['bits'] / 8;
864
-		$this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
865
-		$meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
866
-		if ($meta['decal'] == 4) {
867
-			$meta['decal'] = 0;
868
-		}
869
-		// obtain imagesize
870
-		if ($meta['imagesize'] < 1) {
871
-			$meta['imagesize'] = $meta['filesize'] - $meta['offset'];
872
-			// in rare cases filesize is equal to offset so we need to read physical size
873
-			if ($meta['imagesize'] < 1) {
874
-				$meta['imagesize'] = @filesize($fileName) - $meta['offset'];
875
-				if ($meta['imagesize'] < 1) {
876
-					fclose($fh);
877
-					$this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
878
-					return false;
879
-				}
880
-			}
881
-		}
882
-		// calculate colors
883
-		$meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
884
-		// read color palette
885
-		$palette = [];
886
-		if ($meta['bits'] < 16) {
887
-			$palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
888
-			// in rare cases the color value is signed
889
-			if ($palette[1] < 0) {
890
-				foreach ($palette as $i => $color) {
891
-					$palette[$i] = $color + 16777216;
892
-				}
893
-			}
894
-		}
895
-		if (!$this->checkImageMemory($meta['width'], $meta['height'])) {
896
-			fclose($fh);
897
-			return false;
898
-		}
899
-		// create gd image
900
-		$im = imagecreatetruecolor($meta['width'], $meta['height']);
901
-		if ($im == false) {
902
-			fclose($fh);
903
-			$this->logger->warning(
904
-				'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
905
-				['app' => 'core']);
906
-			return false;
907
-		}
908
-
909
-		$data = fread($fh, $meta['imagesize']);
910
-		$p = 0;
911
-		$vide = chr(0);
912
-		$y = $meta['height'] - 1;
913
-		$error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
914
-		// loop through the image data beginning with the lower left corner
915
-		while ($y >= 0) {
916
-			$x = 0;
917
-			while ($x < $meta['width']) {
918
-				switch ($meta['bits']) {
919
-					case 32:
920
-					case 24:
921
-						if (!($part = substr($data, $p, 3))) {
922
-							$this->logger->warning($error, ['app' => 'core']);
923
-							return $im;
924
-						}
925
-						$color = @unpack('V', $part . $vide);
926
-						break;
927
-					case 16:
928
-						if (!($part = substr($data, $p, 2))) {
929
-							fclose($fh);
930
-							$this->logger->warning($error, ['app' => 'core']);
931
-							return $im;
932
-						}
933
-						$color = @unpack('v', $part);
934
-						$color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
935
-						break;
936
-					case 8:
937
-						$color = @unpack('n', $vide . ($data[$p] ?? ''));
938
-						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
939
-						break;
940
-					case 4:
941
-						$color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
942
-						$color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
943
-						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
944
-						break;
945
-					case 1:
946
-						$color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
947
-						switch (($p * 8) % 8) {
948
-							case 0:
949
-								$color[1] = $color[1] >> 7;
950
-								break;
951
-							case 1:
952
-								$color[1] = ($color[1] & 0x40) >> 6;
953
-								break;
954
-							case 2:
955
-								$color[1] = ($color[1] & 0x20) >> 5;
956
-								break;
957
-							case 3:
958
-								$color[1] = ($color[1] & 0x10) >> 4;
959
-								break;
960
-							case 4:
961
-								$color[1] = ($color[1] & 0x8) >> 3;
962
-								break;
963
-							case 5:
964
-								$color[1] = ($color[1] & 0x4) >> 2;
965
-								break;
966
-							case 6:
967
-								$color[1] = ($color[1] & 0x2) >> 1;
968
-								break;
969
-							case 7:
970
-								$color[1] = ($color[1] & 0x1);
971
-								break;
972
-						}
973
-						$color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
974
-						break;
975
-					default:
976
-						fclose($fh);
977
-						$this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', ['app' => 'core']);
978
-						return false;
979
-				}
980
-				imagesetpixel($im, $x, $y, $color[1]);
981
-				$x++;
982
-				$p += $meta['bytes'];
983
-			}
984
-			$y--;
985
-			$p += $meta['decal'];
986
-		}
987
-		fclose($fh);
988
-		return $im;
989
-	}
990
-
991
-	/**
992
-	 * Resizes the image preserving ratio.
993
-	 *
994
-	 * @param integer $maxSize The maximum size of either the width or height.
995
-	 * @return bool
996
-	 */
997
-	public function resize($maxSize) {
998
-		if (!$this->valid()) {
999
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1000
-			return false;
1001
-		}
1002
-		$result = $this->resizeNew($maxSize);
1003
-		imagedestroy($this->resource);
1004
-		$this->resource = $result;
1005
-		return $this->valid();
1006
-	}
1007
-
1008
-	/**
1009
-	 * @param $maxSize
1010
-	 * @return resource|bool|\GdImage
1011
-	 */
1012
-	private function resizeNew($maxSize) {
1013
-		if (!$this->valid()) {
1014
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1015
-			return false;
1016
-		}
1017
-		$widthOrig = imagesx($this->resource);
1018
-		$heightOrig = imagesy($this->resource);
1019
-		$ratioOrig = $widthOrig / $heightOrig;
1020
-
1021
-		if ($ratioOrig > 1) {
1022
-			$newHeight = round($maxSize / $ratioOrig);
1023
-			$newWidth = $maxSize;
1024
-		} else {
1025
-			$newWidth = round($maxSize * $ratioOrig);
1026
-			$newHeight = $maxSize;
1027
-		}
1028
-
1029
-		return $this->preciseResizeNew((int)round($newWidth), (int)round($newHeight));
1030
-	}
1031
-
1032
-	/**
1033
-	 * @param int $width
1034
-	 * @param int $height
1035
-	 * @return bool
1036
-	 */
1037
-	public function preciseResize(int $width, int $height): bool {
1038
-		if (!$this->valid()) {
1039
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1040
-			return false;
1041
-		}
1042
-		$result = $this->preciseResizeNew($width, $height);
1043
-		imagedestroy($this->resource);
1044
-		$this->resource = $result;
1045
-		return $this->valid();
1046
-	}
1047
-
1048
-
1049
-	/**
1050
-	 * @param int $width
1051
-	 * @param int $height
1052
-	 * @return resource|bool|\GdImage
1053
-	 */
1054
-	public function preciseResizeNew(int $width, int $height) {
1055
-		if (!($width > 0) || !($height > 0)) {
1056
-			$this->logger->info(__METHOD__ . '(): Requested image size not bigger than 0', ['app' => 'core']);
1057
-			return false;
1058
-		}
1059
-		if (!$this->valid()) {
1060
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1061
-			return false;
1062
-		}
1063
-		$widthOrig = imagesx($this->resource);
1064
-		$heightOrig = imagesy($this->resource);
1065
-		$process = imagecreatetruecolor($width, $height);
1066
-		if ($process === false) {
1067
-			$this->logger->debug(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
1068
-			return false;
1069
-		}
1070
-
1071
-		// preserve transparency
1072
-		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1073
-			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1074
-			imagealphablending($process, false);
1075
-			imagesavealpha($process, true);
1076
-		}
1077
-
1078
-		$res = imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig);
1079
-		if ($res === false) {
1080
-			$this->logger->debug(__METHOD__ . '(): Error re-sampling process image', ['app' => 'core']);
1081
-			imagedestroy($process);
1082
-			return false;
1083
-		}
1084
-		return $process;
1085
-	}
1086
-
1087
-	/**
1088
-	 * Crops the image to the middle square. If the image is already square it just returns.
1089
-	 *
1090
-	 * @param int $size maximum size for the result (optional)
1091
-	 * @return bool for success or failure
1092
-	 */
1093
-	public function centerCrop($size = 0) {
1094
-		if (!$this->valid()) {
1095
-			$this->logger->debug('OC_Image->centerCrop, No image loaded', ['app' => 'core']);
1096
-			return false;
1097
-		}
1098
-		$widthOrig = imagesx($this->resource);
1099
-		$heightOrig = imagesy($this->resource);
1100
-		if ($widthOrig === $heightOrig and $size == 0) {
1101
-			return true;
1102
-		}
1103
-		$ratioOrig = $widthOrig / $heightOrig;
1104
-		$width = $height = min($widthOrig, $heightOrig);
1105
-
1106
-		if ($ratioOrig > 1) {
1107
-			$x = ($widthOrig / 2) - ($width / 2);
1108
-			$y = 0;
1109
-		} else {
1110
-			$y = ($heightOrig / 2) - ($height / 2);
1111
-			$x = 0;
1112
-		}
1113
-		if ($size > 0) {
1114
-			$targetWidth = $size;
1115
-			$targetHeight = $size;
1116
-		} else {
1117
-			$targetWidth = $width;
1118
-			$targetHeight = $height;
1119
-		}
1120
-		$process = imagecreatetruecolor($targetWidth, $targetHeight);
1121
-		if ($process === false) {
1122
-			$this->logger->debug('OC_Image->centerCrop, Error creating true color image', ['app' => 'core']);
1123
-			return false;
1124
-		}
1125
-
1126
-		// preserve transparency
1127
-		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1128
-			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1129
-			imagealphablending($process, false);
1130
-			imagesavealpha($process, true);
1131
-		}
1132
-
1133
-		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
1134
-		if ($process === false) {
1135
-			$this->logger->debug('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, ['app' => 'core']);
1136
-			return false;
1137
-		}
1138
-		imagedestroy($this->resource);
1139
-		$this->resource = $process;
1140
-		return true;
1141
-	}
1142
-
1143
-	/**
1144
-	 * Crops the image from point $x$y with dimension $wx$h.
1145
-	 *
1146
-	 * @param int $x Horizontal position
1147
-	 * @param int $y Vertical position
1148
-	 * @param int $w Width
1149
-	 * @param int $h Height
1150
-	 * @return bool for success or failure
1151
-	 */
1152
-	public function crop(int $x, int $y, int $w, int $h): bool {
1153
-		if (!$this->valid()) {
1154
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1155
-			return false;
1156
-		}
1157
-		$result = $this->cropNew($x, $y, $w, $h);
1158
-		imagedestroy($this->resource);
1159
-		$this->resource = $result;
1160
-		return $this->valid();
1161
-	}
1162
-
1163
-	/**
1164
-	 * Crops the image from point $x$y with dimension $wx$h.
1165
-	 *
1166
-	 * @param int $x Horizontal position
1167
-	 * @param int $y Vertical position
1168
-	 * @param int $w Width
1169
-	 * @param int $h Height
1170
-	 * @return resource|\GdImage|false
1171
-	 */
1172
-	public function cropNew(int $x, int $y, int $w, int $h) {
1173
-		if (!$this->valid()) {
1174
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1175
-			return false;
1176
-		}
1177
-		$process = imagecreatetruecolor($w, $h);
1178
-		if ($process === false) {
1179
-			$this->logger->debug(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
1180
-			return false;
1181
-		}
1182
-
1183
-		// preserve transparency
1184
-		if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1185
-			imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1186
-			imagealphablending($process, false);
1187
-			imagesavealpha($process, true);
1188
-		}
1189
-
1190
-		imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
1191
-		if ($process === false) {
1192
-			$this->logger->debug(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, ['app' => 'core']);
1193
-			return false;
1194
-		}
1195
-		return $process;
1196
-	}
1197
-
1198
-	/**
1199
-	 * Resizes the image to fit within a boundary while preserving ratio.
1200
-	 *
1201
-	 * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up
1202
-	 *
1203
-	 * @param integer $maxWidth
1204
-	 * @param integer $maxHeight
1205
-	 * @return bool
1206
-	 */
1207
-	public function fitIn($maxWidth, $maxHeight) {
1208
-		if (!$this->valid()) {
1209
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1210
-			return false;
1211
-		}
1212
-		$widthOrig = imagesx($this->resource);
1213
-		$heightOrig = imagesy($this->resource);
1214
-		$ratio = $widthOrig / $heightOrig;
1215
-
1216
-		$newWidth = min($maxWidth, $ratio * $maxHeight);
1217
-		$newHeight = min($maxHeight, $maxWidth / $ratio);
1218
-
1219
-		$this->preciseResize((int)round($newWidth), (int)round($newHeight));
1220
-		return true;
1221
-	}
1222
-
1223
-	/**
1224
-	 * Shrinks larger images to fit within specified boundaries while preserving ratio.
1225
-	 *
1226
-	 * @param integer $maxWidth
1227
-	 * @param integer $maxHeight
1228
-	 * @return bool
1229
-	 */
1230
-	public function scaleDownToFit($maxWidth, $maxHeight) {
1231
-		if (!$this->valid()) {
1232
-			$this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1233
-			return false;
1234
-		}
1235
-		$widthOrig = imagesx($this->resource);
1236
-		$heightOrig = imagesy($this->resource);
1237
-
1238
-		if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) {
1239
-			return $this->fitIn($maxWidth, $maxHeight);
1240
-		}
1241
-
1242
-		return false;
1243
-	}
1244
-
1245
-	public function copy(): IImage {
1246
-		$image = new OC_Image(null, $this->logger, $this->config);
1247
-		$image->resource = imagecreatetruecolor($this->width(), $this->height());
1248
-		imagecopy(
1249
-			$image->resource(),
1250
-			$this->resource(),
1251
-			0,
1252
-			0,
1253
-			0,
1254
-			0,
1255
-			$this->width(),
1256
-			$this->height()
1257
-		);
1258
-
1259
-		return $image;
1260
-	}
1261
-
1262
-	public function cropCopy(int $x, int $y, int $w, int $h): IImage {
1263
-		$image = new OC_Image(null, $this->logger, $this->config);
1264
-		$image->imageType = $this->imageType;
1265
-		$image->mimeType = $this->mimeType;
1266
-		$image->bitDepth = $this->bitDepth;
1267
-		$image->resource = $this->cropNew($x, $y, $w, $h);
1268
-
1269
-		return $image;
1270
-	}
1271
-
1272
-	public function preciseResizeCopy(int $width, int $height): IImage {
1273
-		$image = new OC_Image(null, $this->logger, $this->config);
1274
-		$image->imageType = $this->imageType;
1275
-		$image->mimeType = $this->mimeType;
1276
-		$image->bitDepth = $this->bitDepth;
1277
-		$image->resource = $this->preciseResizeNew($width, $height);
1278
-
1279
-		return $image;
1280
-	}
1281
-
1282
-	public function resizeCopy(int $maxSize): IImage {
1283
-		$image = new OC_Image(null, $this->logger, $this->config);
1284
-		$image->imageType = $this->imageType;
1285
-		$image->mimeType = $this->mimeType;
1286
-		$image->bitDepth = $this->bitDepth;
1287
-		$image->resource = $this->resizeNew($maxSize);
1288
-
1289
-		return $image;
1290
-	}
1291
-
1292
-	/**
1293
-	 * Destroys the current image and resets the object
1294
-	 */
1295
-	public function destroy() {
1296
-		if ($this->valid()) {
1297
-			imagedestroy($this->resource);
1298
-		}
1299
-		$this->resource = false;
1300
-	}
1301
-
1302
-	public function __destruct() {
1303
-		$this->destroy();
1304
-	}
755
+            default:
756
+
757
+                // this is mostly file created from encrypted file
758
+                $data = file_get_contents($imagePath);
759
+                if (!$this->checkImageDataSize($data)) {
760
+                    return false;
761
+                }
762
+                $this->resource = imagecreatefromstring($data);
763
+                $iType = IMAGETYPE_PNG;
764
+                $this->logger->debug('OC_Image->loadFromFile, Default', ['app' => 'core']);
765
+                break;
766
+        }
767
+        if ($this->valid()) {
768
+            $this->imageType = $iType;
769
+            $this->mimeType = image_type_to_mime_type($iType);
770
+            $this->filePath = $imagePath;
771
+        }
772
+        return $this->resource;
773
+    }
774
+
775
+    /**
776
+     * Loads an image from a string of data.
777
+     *
778
+     * @param string $str A string of image data as read from a file.
779
+     * @return bool|resource|\GdImage An image resource or false on error
780
+     */
781
+    public function loadFromData($str) {
782
+        if (!is_string($str)) {
783
+            return false;
784
+        }
785
+        if (!$this->checkImageDataSize($str)) {
786
+            return false;
787
+        }
788
+        $this->resource = @imagecreatefromstring($str);
789
+        if ($this->fileInfo) {
790
+            $this->mimeType = $this->fileInfo->buffer($str);
791
+        }
792
+        if ($this->valid()) {
793
+            imagealphablending($this->resource, false);
794
+            imagesavealpha($this->resource, true);
795
+        }
796
+
797
+        if (!$this->resource) {
798
+            $this->logger->debug('OC_Image->loadFromFile, could not load', ['app' => 'core']);
799
+            return false;
800
+        }
801
+        return $this->resource;
802
+    }
803
+
804
+    /**
805
+     * Loads an image from a base64 encoded string.
806
+     *
807
+     * @param string $str A string base64 encoded string of image data.
808
+     * @return bool|resource|\GdImage An image resource or false on error
809
+     */
810
+    public function loadFromBase64($str) {
811
+        if (!is_string($str)) {
812
+            return false;
813
+        }
814
+        $data = base64_decode($str);
815
+        if ($data) { // try to load from string data
816
+            if (!$this->checkImageDataSize($data)) {
817
+                return false;
818
+            }
819
+            $this->resource = @imagecreatefromstring($data);
820
+            if ($this->fileInfo) {
821
+                $this->mimeType = $this->fileInfo->buffer($data);
822
+            }
823
+            if (!$this->resource) {
824
+                $this->logger->debug('OC_Image->loadFromBase64, could not load', ['app' => 'core']);
825
+                return false;
826
+            }
827
+            return $this->resource;
828
+        } else {
829
+            return false;
830
+        }
831
+    }
832
+
833
+    /**
834
+     * Create a new image from file or URL
835
+     *
836
+     * @link http://www.programmierer-forum.de/function-imagecreatefrombmp-laeuft-mit-allen-bitraten-t143137.htm
837
+     * @version 1.00
838
+     * @param string $fileName <p>
839
+     * Path to the BMP image.
840
+     * </p>
841
+     * @return bool|resource|\GdImage an image resource identifier on success, <b>FALSE</b> on errors.
842
+     */
843
+    private function imagecreatefrombmp($fileName) {
844
+        if (!($fh = fopen($fileName, 'rb'))) {
845
+            $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName, ['app' => 'core']);
846
+            return false;
847
+        }
848
+        // read file header
849
+        $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
850
+        // check for bitmap
851
+        if ($meta['type'] != 19778) {
852
+            fclose($fh);
853
+            $this->logger->warning('imagecreatefrombmp: Can not open ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
854
+            return false;
855
+        }
856
+        // read image header
857
+        $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
858
+        // read additional 16bit header
859
+        if ($meta['bits'] == 16) {
860
+            $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
861
+        }
862
+        // set bytes and padding
863
+        $meta['bytes'] = $meta['bits'] / 8;
864
+        $this->bitDepth = $meta['bits']; //remember the bit depth for the imagebmp call
865
+        $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
866
+        if ($meta['decal'] == 4) {
867
+            $meta['decal'] = 0;
868
+        }
869
+        // obtain imagesize
870
+        if ($meta['imagesize'] < 1) {
871
+            $meta['imagesize'] = $meta['filesize'] - $meta['offset'];
872
+            // in rare cases filesize is equal to offset so we need to read physical size
873
+            if ($meta['imagesize'] < 1) {
874
+                $meta['imagesize'] = @filesize($fileName) - $meta['offset'];
875
+                if ($meta['imagesize'] < 1) {
876
+                    fclose($fh);
877
+                    $this->logger->warning('imagecreatefrombmp: Can not obtain file size of ' . $fileName . ' is not a bitmap!', ['app' => 'core']);
878
+                    return false;
879
+                }
880
+            }
881
+        }
882
+        // calculate colors
883
+        $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
884
+        // read color palette
885
+        $palette = [];
886
+        if ($meta['bits'] < 16) {
887
+            $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
888
+            // in rare cases the color value is signed
889
+            if ($palette[1] < 0) {
890
+                foreach ($palette as $i => $color) {
891
+                    $palette[$i] = $color + 16777216;
892
+                }
893
+            }
894
+        }
895
+        if (!$this->checkImageMemory($meta['width'], $meta['height'])) {
896
+            fclose($fh);
897
+            return false;
898
+        }
899
+        // create gd image
900
+        $im = imagecreatetruecolor($meta['width'], $meta['height']);
901
+        if ($im == false) {
902
+            fclose($fh);
903
+            $this->logger->warning(
904
+                'imagecreatefrombmp: imagecreatetruecolor failed for file "' . $fileName . '" with dimensions ' . $meta['width'] . 'x' . $meta['height'],
905
+                ['app' => 'core']);
906
+            return false;
907
+        }
908
+
909
+        $data = fread($fh, $meta['imagesize']);
910
+        $p = 0;
911
+        $vide = chr(0);
912
+        $y = $meta['height'] - 1;
913
+        $error = 'imagecreatefrombmp: ' . $fileName . ' has not enough data!';
914
+        // loop through the image data beginning with the lower left corner
915
+        while ($y >= 0) {
916
+            $x = 0;
917
+            while ($x < $meta['width']) {
918
+                switch ($meta['bits']) {
919
+                    case 32:
920
+                    case 24:
921
+                        if (!($part = substr($data, $p, 3))) {
922
+                            $this->logger->warning($error, ['app' => 'core']);
923
+                            return $im;
924
+                        }
925
+                        $color = @unpack('V', $part . $vide);
926
+                        break;
927
+                    case 16:
928
+                        if (!($part = substr($data, $p, 2))) {
929
+                            fclose($fh);
930
+                            $this->logger->warning($error, ['app' => 'core']);
931
+                            return $im;
932
+                        }
933
+                        $color = @unpack('v', $part);
934
+                        $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3);
935
+                        break;
936
+                    case 8:
937
+                        $color = @unpack('n', $vide . ($data[$p] ?? ''));
938
+                        $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
939
+                        break;
940
+                    case 4:
941
+                        $color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
942
+                        $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
943
+                        $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
944
+                        break;
945
+                    case 1:
946
+                        $color = @unpack('n', $vide . ($data[floor($p)] ?? ''));
947
+                        switch (($p * 8) % 8) {
948
+                            case 0:
949
+                                $color[1] = $color[1] >> 7;
950
+                                break;
951
+                            case 1:
952
+                                $color[1] = ($color[1] & 0x40) >> 6;
953
+                                break;
954
+                            case 2:
955
+                                $color[1] = ($color[1] & 0x20) >> 5;
956
+                                break;
957
+                            case 3:
958
+                                $color[1] = ($color[1] & 0x10) >> 4;
959
+                                break;
960
+                            case 4:
961
+                                $color[1] = ($color[1] & 0x8) >> 3;
962
+                                break;
963
+                            case 5:
964
+                                $color[1] = ($color[1] & 0x4) >> 2;
965
+                                break;
966
+                            case 6:
967
+                                $color[1] = ($color[1] & 0x2) >> 1;
968
+                                break;
969
+                            case 7:
970
+                                $color[1] = ($color[1] & 0x1);
971
+                                break;
972
+                        }
973
+                        $color[1] = isset($palette[$color[1] + 1]) ? $palette[$color[1] + 1] : $palette[1];
974
+                        break;
975
+                    default:
976
+                        fclose($fh);
977
+                        $this->logger->warning('imagecreatefrombmp: ' . $fileName . ' has ' . $meta['bits'] . ' bits and this is not supported!', ['app' => 'core']);
978
+                        return false;
979
+                }
980
+                imagesetpixel($im, $x, $y, $color[1]);
981
+                $x++;
982
+                $p += $meta['bytes'];
983
+            }
984
+            $y--;
985
+            $p += $meta['decal'];
986
+        }
987
+        fclose($fh);
988
+        return $im;
989
+    }
990
+
991
+    /**
992
+     * Resizes the image preserving ratio.
993
+     *
994
+     * @param integer $maxSize The maximum size of either the width or height.
995
+     * @return bool
996
+     */
997
+    public function resize($maxSize) {
998
+        if (!$this->valid()) {
999
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1000
+            return false;
1001
+        }
1002
+        $result = $this->resizeNew($maxSize);
1003
+        imagedestroy($this->resource);
1004
+        $this->resource = $result;
1005
+        return $this->valid();
1006
+    }
1007
+
1008
+    /**
1009
+     * @param $maxSize
1010
+     * @return resource|bool|\GdImage
1011
+     */
1012
+    private function resizeNew($maxSize) {
1013
+        if (!$this->valid()) {
1014
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1015
+            return false;
1016
+        }
1017
+        $widthOrig = imagesx($this->resource);
1018
+        $heightOrig = imagesy($this->resource);
1019
+        $ratioOrig = $widthOrig / $heightOrig;
1020
+
1021
+        if ($ratioOrig > 1) {
1022
+            $newHeight = round($maxSize / $ratioOrig);
1023
+            $newWidth = $maxSize;
1024
+        } else {
1025
+            $newWidth = round($maxSize * $ratioOrig);
1026
+            $newHeight = $maxSize;
1027
+        }
1028
+
1029
+        return $this->preciseResizeNew((int)round($newWidth), (int)round($newHeight));
1030
+    }
1031
+
1032
+    /**
1033
+     * @param int $width
1034
+     * @param int $height
1035
+     * @return bool
1036
+     */
1037
+    public function preciseResize(int $width, int $height): bool {
1038
+        if (!$this->valid()) {
1039
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1040
+            return false;
1041
+        }
1042
+        $result = $this->preciseResizeNew($width, $height);
1043
+        imagedestroy($this->resource);
1044
+        $this->resource = $result;
1045
+        return $this->valid();
1046
+    }
1047
+
1048
+
1049
+    /**
1050
+     * @param int $width
1051
+     * @param int $height
1052
+     * @return resource|bool|\GdImage
1053
+     */
1054
+    public function preciseResizeNew(int $width, int $height) {
1055
+        if (!($width > 0) || !($height > 0)) {
1056
+            $this->logger->info(__METHOD__ . '(): Requested image size not bigger than 0', ['app' => 'core']);
1057
+            return false;
1058
+        }
1059
+        if (!$this->valid()) {
1060
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1061
+            return false;
1062
+        }
1063
+        $widthOrig = imagesx($this->resource);
1064
+        $heightOrig = imagesy($this->resource);
1065
+        $process = imagecreatetruecolor($width, $height);
1066
+        if ($process === false) {
1067
+            $this->logger->debug(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
1068
+            return false;
1069
+        }
1070
+
1071
+        // preserve transparency
1072
+        if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1073
+            imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1074
+            imagealphablending($process, false);
1075
+            imagesavealpha($process, true);
1076
+        }
1077
+
1078
+        $res = imagecopyresampled($process, $this->resource, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig);
1079
+        if ($res === false) {
1080
+            $this->logger->debug(__METHOD__ . '(): Error re-sampling process image', ['app' => 'core']);
1081
+            imagedestroy($process);
1082
+            return false;
1083
+        }
1084
+        return $process;
1085
+    }
1086
+
1087
+    /**
1088
+     * Crops the image to the middle square. If the image is already square it just returns.
1089
+     *
1090
+     * @param int $size maximum size for the result (optional)
1091
+     * @return bool for success or failure
1092
+     */
1093
+    public function centerCrop($size = 0) {
1094
+        if (!$this->valid()) {
1095
+            $this->logger->debug('OC_Image->centerCrop, No image loaded', ['app' => 'core']);
1096
+            return false;
1097
+        }
1098
+        $widthOrig = imagesx($this->resource);
1099
+        $heightOrig = imagesy($this->resource);
1100
+        if ($widthOrig === $heightOrig and $size == 0) {
1101
+            return true;
1102
+        }
1103
+        $ratioOrig = $widthOrig / $heightOrig;
1104
+        $width = $height = min($widthOrig, $heightOrig);
1105
+
1106
+        if ($ratioOrig > 1) {
1107
+            $x = ($widthOrig / 2) - ($width / 2);
1108
+            $y = 0;
1109
+        } else {
1110
+            $y = ($heightOrig / 2) - ($height / 2);
1111
+            $x = 0;
1112
+        }
1113
+        if ($size > 0) {
1114
+            $targetWidth = $size;
1115
+            $targetHeight = $size;
1116
+        } else {
1117
+            $targetWidth = $width;
1118
+            $targetHeight = $height;
1119
+        }
1120
+        $process = imagecreatetruecolor($targetWidth, $targetHeight);
1121
+        if ($process === false) {
1122
+            $this->logger->debug('OC_Image->centerCrop, Error creating true color image', ['app' => 'core']);
1123
+            return false;
1124
+        }
1125
+
1126
+        // preserve transparency
1127
+        if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1128
+            imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1129
+            imagealphablending($process, false);
1130
+            imagesavealpha($process, true);
1131
+        }
1132
+
1133
+        imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $targetWidth, $targetHeight, $width, $height);
1134
+        if ($process === false) {
1135
+            $this->logger->debug('OC_Image->centerCrop, Error re-sampling process image ' . $width . 'x' . $height, ['app' => 'core']);
1136
+            return false;
1137
+        }
1138
+        imagedestroy($this->resource);
1139
+        $this->resource = $process;
1140
+        return true;
1141
+    }
1142
+
1143
+    /**
1144
+     * Crops the image from point $x$y with dimension $wx$h.
1145
+     *
1146
+     * @param int $x Horizontal position
1147
+     * @param int $y Vertical position
1148
+     * @param int $w Width
1149
+     * @param int $h Height
1150
+     * @return bool for success or failure
1151
+     */
1152
+    public function crop(int $x, int $y, int $w, int $h): bool {
1153
+        if (!$this->valid()) {
1154
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1155
+            return false;
1156
+        }
1157
+        $result = $this->cropNew($x, $y, $w, $h);
1158
+        imagedestroy($this->resource);
1159
+        $this->resource = $result;
1160
+        return $this->valid();
1161
+    }
1162
+
1163
+    /**
1164
+     * Crops the image from point $x$y with dimension $wx$h.
1165
+     *
1166
+     * @param int $x Horizontal position
1167
+     * @param int $y Vertical position
1168
+     * @param int $w Width
1169
+     * @param int $h Height
1170
+     * @return resource|\GdImage|false
1171
+     */
1172
+    public function cropNew(int $x, int $y, int $w, int $h) {
1173
+        if (!$this->valid()) {
1174
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1175
+            return false;
1176
+        }
1177
+        $process = imagecreatetruecolor($w, $h);
1178
+        if ($process === false) {
1179
+            $this->logger->debug(__METHOD__ . '(): Error creating true color image', ['app' => 'core']);
1180
+            return false;
1181
+        }
1182
+
1183
+        // preserve transparency
1184
+        if ($this->imageType == IMAGETYPE_GIF or $this->imageType == IMAGETYPE_PNG) {
1185
+            imagecolortransparent($process, imagecolorallocatealpha($process, 0, 0, 0, 127));
1186
+            imagealphablending($process, false);
1187
+            imagesavealpha($process, true);
1188
+        }
1189
+
1190
+        imagecopyresampled($process, $this->resource, 0, 0, $x, $y, $w, $h, $w, $h);
1191
+        if ($process === false) {
1192
+            $this->logger->debug(__METHOD__ . '(): Error re-sampling process image ' . $w . 'x' . $h, ['app' => 'core']);
1193
+            return false;
1194
+        }
1195
+        return $process;
1196
+    }
1197
+
1198
+    /**
1199
+     * Resizes the image to fit within a boundary while preserving ratio.
1200
+     *
1201
+     * Warning: Images smaller than $maxWidth x $maxHeight will end up being scaled up
1202
+     *
1203
+     * @param integer $maxWidth
1204
+     * @param integer $maxHeight
1205
+     * @return bool
1206
+     */
1207
+    public function fitIn($maxWidth, $maxHeight) {
1208
+        if (!$this->valid()) {
1209
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1210
+            return false;
1211
+        }
1212
+        $widthOrig = imagesx($this->resource);
1213
+        $heightOrig = imagesy($this->resource);
1214
+        $ratio = $widthOrig / $heightOrig;
1215
+
1216
+        $newWidth = min($maxWidth, $ratio * $maxHeight);
1217
+        $newHeight = min($maxHeight, $maxWidth / $ratio);
1218
+
1219
+        $this->preciseResize((int)round($newWidth), (int)round($newHeight));
1220
+        return true;
1221
+    }
1222
+
1223
+    /**
1224
+     * Shrinks larger images to fit within specified boundaries while preserving ratio.
1225
+     *
1226
+     * @param integer $maxWidth
1227
+     * @param integer $maxHeight
1228
+     * @return bool
1229
+     */
1230
+    public function scaleDownToFit($maxWidth, $maxHeight) {
1231
+        if (!$this->valid()) {
1232
+            $this->logger->debug(__METHOD__ . '(): No image loaded', ['app' => 'core']);
1233
+            return false;
1234
+        }
1235
+        $widthOrig = imagesx($this->resource);
1236
+        $heightOrig = imagesy($this->resource);
1237
+
1238
+        if ($widthOrig > $maxWidth || $heightOrig > $maxHeight) {
1239
+            return $this->fitIn($maxWidth, $maxHeight);
1240
+        }
1241
+
1242
+        return false;
1243
+    }
1244
+
1245
+    public function copy(): IImage {
1246
+        $image = new OC_Image(null, $this->logger, $this->config);
1247
+        $image->resource = imagecreatetruecolor($this->width(), $this->height());
1248
+        imagecopy(
1249
+            $image->resource(),
1250
+            $this->resource(),
1251
+            0,
1252
+            0,
1253
+            0,
1254
+            0,
1255
+            $this->width(),
1256
+            $this->height()
1257
+        );
1258
+
1259
+        return $image;
1260
+    }
1261
+
1262
+    public function cropCopy(int $x, int $y, int $w, int $h): IImage {
1263
+        $image = new OC_Image(null, $this->logger, $this->config);
1264
+        $image->imageType = $this->imageType;
1265
+        $image->mimeType = $this->mimeType;
1266
+        $image->bitDepth = $this->bitDepth;
1267
+        $image->resource = $this->cropNew($x, $y, $w, $h);
1268
+
1269
+        return $image;
1270
+    }
1271
+
1272
+    public function preciseResizeCopy(int $width, int $height): IImage {
1273
+        $image = new OC_Image(null, $this->logger, $this->config);
1274
+        $image->imageType = $this->imageType;
1275
+        $image->mimeType = $this->mimeType;
1276
+        $image->bitDepth = $this->bitDepth;
1277
+        $image->resource = $this->preciseResizeNew($width, $height);
1278
+
1279
+        return $image;
1280
+    }
1281
+
1282
+    public function resizeCopy(int $maxSize): IImage {
1283
+        $image = new OC_Image(null, $this->logger, $this->config);
1284
+        $image->imageType = $this->imageType;
1285
+        $image->mimeType = $this->mimeType;
1286
+        $image->bitDepth = $this->bitDepth;
1287
+        $image->resource = $this->resizeNew($maxSize);
1288
+
1289
+        return $image;
1290
+    }
1291
+
1292
+    /**
1293
+     * Destroys the current image and resets the object
1294
+     */
1295
+    public function destroy() {
1296
+        if ($this->valid()) {
1297
+            imagedestroy($this->resource);
1298
+        }
1299
+        $this->resource = false;
1300
+    }
1301
+
1302
+    public function __destruct() {
1303
+        $this->destroy();
1304
+    }
1305 1305
 }
1306 1306
 
1307 1307
 if (!function_exists('imagebmp')) {
1308
-	/**
1309
-	 * Output a BMP image to either the browser or a file
1310
-	 *
1311
-	 * @link http://www.ugia.cn/wp-data/imagebmp.php
1312
-	 * @author legend <[email protected]>
1313
-	 * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
1314
-	 * @author mgutt <[email protected]>
1315
-	 * @version 1.00
1316
-	 * @param resource|\GdImage $im
1317
-	 * @param string $fileName [optional] <p>The path to save the file to.</p>
1318
-	 * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
1319
-	 * @param int $compression [optional]
1320
-	 * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
1321
-	 */
1322
-	function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
1323
-		if (!in_array($bit, [1, 4, 8, 16, 24, 32])) {
1324
-			$bit = 24;
1325
-		} elseif ($bit == 32) {
1326
-			$bit = 24;
1327
-		}
1328
-		$bits = (int)pow(2, $bit);
1329
-		imagetruecolortopalette($im, true, $bits);
1330
-		$width = imagesx($im);
1331
-		$height = imagesy($im);
1332
-		$colorsNum = imagecolorstotal($im);
1333
-		$rgbQuad = '';
1334
-		if ($bit <= 8) {
1335
-			for ($i = 0; $i < $colorsNum; $i++) {
1336
-				$colors = imagecolorsforindex($im, $i);
1337
-				$rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
1338
-			}
1339
-			$bmpData = '';
1340
-			if ($compression == 0 || $bit < 8) {
1341
-				$compression = 0;
1342
-				$extra = '';
1343
-				$padding = 4 - ceil($width / (8 / $bit)) % 4;
1344
-				if ($padding % 4 != 0) {
1345
-					$extra = str_repeat("\0", $padding);
1346
-				}
1347
-				for ($j = $height - 1; $j >= 0; $j--) {
1348
-					$i = 0;
1349
-					while ($i < $width) {
1350
-						$bin = 0;
1351
-						$limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
1352
-						for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
1353
-							$index = imagecolorat($im, $i, $j);
1354
-							$bin |= $index << $k;
1355
-							$i++;
1356
-						}
1357
-						$bmpData .= chr($bin);
1358
-					}
1359
-					$bmpData .= $extra;
1360
-				}
1361
-			} // RLE8
1362
-			elseif ($compression == 1 && $bit == 8) {
1363
-				for ($j = $height - 1; $j >= 0; $j--) {
1364
-					$lastIndex = null;
1365
-					$sameNum = 0;
1366
-					for ($i = 0; $i <= $width; $i++) {
1367
-						$index = imagecolorat($im, $i, $j);
1368
-						if ($index !== $lastIndex || $sameNum > 255) {
1369
-							if ($sameNum != 0) {
1370
-								$bmpData .= chr($sameNum) . chr($lastIndex);
1371
-							}
1372
-							$lastIndex = $index;
1373
-							$sameNum = 1;
1374
-						} else {
1375
-							$sameNum++;
1376
-						}
1377
-					}
1378
-					$bmpData .= "\0\0";
1379
-				}
1380
-				$bmpData .= "\0\1";
1381
-			}
1382
-			$sizeQuad = strlen($rgbQuad);
1383
-			$sizeData = strlen($bmpData);
1384
-		} else {
1385
-			$extra = '';
1386
-			$padding = 4 - ($width * ($bit / 8)) % 4;
1387
-			if ($padding % 4 != 0) {
1388
-				$extra = str_repeat("\0", $padding);
1389
-			}
1390
-			$bmpData = '';
1391
-			for ($j = $height - 1; $j >= 0; $j--) {
1392
-				for ($i = 0; $i < $width; $i++) {
1393
-					$index = imagecolorat($im, $i, $j);
1394
-					$colors = imagecolorsforindex($im, $index);
1395
-					if ($bit == 16) {
1396
-						$bin = 0 << $bit;
1397
-						$bin |= ($colors['red'] >> 3) << 10;
1398
-						$bin |= ($colors['green'] >> 3) << 5;
1399
-						$bin |= $colors['blue'] >> 3;
1400
-						$bmpData .= pack("v", $bin);
1401
-					} else {
1402
-						$bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
1403
-					}
1404
-				}
1405
-				$bmpData .= $extra;
1406
-			}
1407
-			$sizeQuad = 0;
1408
-			$sizeData = strlen($bmpData);
1409
-			$colorsNum = 0;
1410
-		}
1411
-		$fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
1412
-		$infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
1413
-		if ($fileName != '') {
1414
-			$fp = fopen($fileName, 'wb');
1415
-			fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
1416
-			fclose($fp);
1417
-			return true;
1418
-		}
1419
-		echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
1420
-		return true;
1421
-	}
1308
+    /**
1309
+     * Output a BMP image to either the browser or a file
1310
+     *
1311
+     * @link http://www.ugia.cn/wp-data/imagebmp.php
1312
+     * @author legend <[email protected]>
1313
+     * @link http://www.programmierer-forum.de/imagebmp-gute-funktion-gefunden-t143716.htm
1314
+     * @author mgutt <[email protected]>
1315
+     * @version 1.00
1316
+     * @param resource|\GdImage $im
1317
+     * @param string $fileName [optional] <p>The path to save the file to.</p>
1318
+     * @param int $bit [optional] <p>Bit depth, (default is 24).</p>
1319
+     * @param int $compression [optional]
1320
+     * @return bool <b>TRUE</b> on success or <b>FALSE</b> on failure.
1321
+     */
1322
+    function imagebmp($im, $fileName = '', $bit = 24, $compression = 0) {
1323
+        if (!in_array($bit, [1, 4, 8, 16, 24, 32])) {
1324
+            $bit = 24;
1325
+        } elseif ($bit == 32) {
1326
+            $bit = 24;
1327
+        }
1328
+        $bits = (int)pow(2, $bit);
1329
+        imagetruecolortopalette($im, true, $bits);
1330
+        $width = imagesx($im);
1331
+        $height = imagesy($im);
1332
+        $colorsNum = imagecolorstotal($im);
1333
+        $rgbQuad = '';
1334
+        if ($bit <= 8) {
1335
+            for ($i = 0; $i < $colorsNum; $i++) {
1336
+                $colors = imagecolorsforindex($im, $i);
1337
+                $rgbQuad .= chr($colors['blue']) . chr($colors['green']) . chr($colors['red']) . "\0";
1338
+            }
1339
+            $bmpData = '';
1340
+            if ($compression == 0 || $bit < 8) {
1341
+                $compression = 0;
1342
+                $extra = '';
1343
+                $padding = 4 - ceil($width / (8 / $bit)) % 4;
1344
+                if ($padding % 4 != 0) {
1345
+                    $extra = str_repeat("\0", $padding);
1346
+                }
1347
+                for ($j = $height - 1; $j >= 0; $j--) {
1348
+                    $i = 0;
1349
+                    while ($i < $width) {
1350
+                        $bin = 0;
1351
+                        $limit = $width - $i < 8 / $bit ? (8 / $bit - $width + $i) * $bit : 0;
1352
+                        for ($k = 8 - $bit; $k >= $limit; $k -= $bit) {
1353
+                            $index = imagecolorat($im, $i, $j);
1354
+                            $bin |= $index << $k;
1355
+                            $i++;
1356
+                        }
1357
+                        $bmpData .= chr($bin);
1358
+                    }
1359
+                    $bmpData .= $extra;
1360
+                }
1361
+            } // RLE8
1362
+            elseif ($compression == 1 && $bit == 8) {
1363
+                for ($j = $height - 1; $j >= 0; $j--) {
1364
+                    $lastIndex = null;
1365
+                    $sameNum = 0;
1366
+                    for ($i = 0; $i <= $width; $i++) {
1367
+                        $index = imagecolorat($im, $i, $j);
1368
+                        if ($index !== $lastIndex || $sameNum > 255) {
1369
+                            if ($sameNum != 0) {
1370
+                                $bmpData .= chr($sameNum) . chr($lastIndex);
1371
+                            }
1372
+                            $lastIndex = $index;
1373
+                            $sameNum = 1;
1374
+                        } else {
1375
+                            $sameNum++;
1376
+                        }
1377
+                    }
1378
+                    $bmpData .= "\0\0";
1379
+                }
1380
+                $bmpData .= "\0\1";
1381
+            }
1382
+            $sizeQuad = strlen($rgbQuad);
1383
+            $sizeData = strlen($bmpData);
1384
+        } else {
1385
+            $extra = '';
1386
+            $padding = 4 - ($width * ($bit / 8)) % 4;
1387
+            if ($padding % 4 != 0) {
1388
+                $extra = str_repeat("\0", $padding);
1389
+            }
1390
+            $bmpData = '';
1391
+            for ($j = $height - 1; $j >= 0; $j--) {
1392
+                for ($i = 0; $i < $width; $i++) {
1393
+                    $index = imagecolorat($im, $i, $j);
1394
+                    $colors = imagecolorsforindex($im, $index);
1395
+                    if ($bit == 16) {
1396
+                        $bin = 0 << $bit;
1397
+                        $bin |= ($colors['red'] >> 3) << 10;
1398
+                        $bin |= ($colors['green'] >> 3) << 5;
1399
+                        $bin |= $colors['blue'] >> 3;
1400
+                        $bmpData .= pack("v", $bin);
1401
+                    } else {
1402
+                        $bmpData .= pack("c*", $colors['blue'], $colors['green'], $colors['red']);
1403
+                    }
1404
+                }
1405
+                $bmpData .= $extra;
1406
+            }
1407
+            $sizeQuad = 0;
1408
+            $sizeData = strlen($bmpData);
1409
+            $colorsNum = 0;
1410
+        }
1411
+        $fileHeader = 'BM' . pack('V3', 54 + $sizeQuad + $sizeData, 0, 54 + $sizeQuad);
1412
+        $infoHeader = pack('V3v2V*', 0x28, $width, $height, 1, $bit, $compression, $sizeData, 0, 0, $colorsNum, 0);
1413
+        if ($fileName != '') {
1414
+            $fp = fopen($fileName, 'wb');
1415
+            fwrite($fp, $fileHeader . $infoHeader . $rgbQuad . $bmpData);
1416
+            fclose($fp);
1417
+            return true;
1418
+        }
1419
+        echo $fileHeader . $infoHeader . $rgbQuad . $bmpData;
1420
+        return true;
1421
+    }
1422 1422
 }
1423 1423
 
1424 1424
 if (!function_exists('exif_imagetype')) {
1425
-	/**
1426
-	 * Workaround if exif_imagetype does not exist
1427
-	 *
1428
-	 * @link https://www.php.net/manual/en/function.exif-imagetype.php#80383
1429
-	 * @param string $fileName
1430
-	 * @return string|boolean
1431
-	 */
1432
-	function exif_imagetype($fileName) {
1433
-		if (($info = getimagesize($fileName)) !== false) {
1434
-			return $info[2];
1435
-		}
1436
-		return false;
1437
-	}
1425
+    /**
1426
+     * Workaround if exif_imagetype does not exist
1427
+     *
1428
+     * @link https://www.php.net/manual/en/function.exif-imagetype.php#80383
1429
+     * @param string $fileName
1430
+     * @return string|boolean
1431
+     */
1432
+    function exif_imagetype($fileName) {
1433
+        if (($info = getimagesize($fileName)) !== false) {
1434
+            return $info[2];
1435
+        }
1436
+        return false;
1437
+    }
1438 1438
 }
Please login to merge, or discard this patch.