JImage::cropResize()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 18
rs 9.9666
cc 2
nc 2
nop 3
1
<?php
2
/**
3
 * @package     Joomla.Platform
4
 * @subpackage  Image
5
 *
6
 * @copyright   Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7
 * @license     GNU General Public License version 2 or later; see LICENSE
8
 */
9
10
defined('JPATH_PLATFORM') or die;
11
12
/**
13
 * Class to manipulate an image.
14
 *
15
 * @since  11.3
16
 */
17
class JImage
18
{
19
	/**
20
	 * @const  integer
21
	 * @since  11.3
22
	 */
23
	const SCALE_FILL = 1;
24
25
	/**
26
	 * @const  integer
27
	 * @since  11.3
28
	 */
29
	const SCALE_INSIDE = 2;
30
31
	/**
32
	 * @const  integer
33
	 * @since  11.3
34
	 */
35
	const SCALE_OUTSIDE = 3;
36
37
	/**
38
	 * @const  integer
39
	 * @since  12.2
40
	 */
41
	const CROP = 4;
42
43
	/**
44
	 * @const  integer
45
	 * @since  12.3
46
	 */
47
	const CROP_RESIZE = 5;
48
49
	/**
50
	 * @const  integer
51
	 * @since  3.2
52
	 */
53
	const SCALE_FIT = 6;
54
55
	/**
56
	 * @const  string
57
	 * @since  3.4.2
58
	 */
59
	const ORIENTATION_LANDSCAPE = 'landscape';
60
61
	/**
62
	 * @const  string
63
	 * @since  3.4.2
64
	 */
65
	const ORIENTATION_PORTRAIT = 'portrait';
66
67
	/**
68
	 * @const  string
69
	 * @since  3.4.2
70
	 */
71
	const ORIENTATION_SQUARE = 'square';
72
73
	/**
74
	 * @var    resource  The image resource handle.
75
	 * @since  11.3
76
	 */
77
	protected $handle;
78
79
	/**
80
	 * @var    string  The source image path.
81
	 * @since  11.3
82
	 */
83
	protected $path = null;
84
85
	/**
86
	 * @var    array  Whether or not different image formats are supported.
87
	 * @since  11.3
88
	 */
89
	protected static $formats = array();
90
91
	/**
92
	 * @var    boolean  True for best quality. False for speed
93
	 *
94
	 * @since  3.7.0
95
	 */
96
	protected $generateBestQuality = true;
97
98
	/**
99
	 * Class constructor.
100
	 *
101
	 * @param   mixed  $source  Either a file path for a source image or a GD resource handler for an image.
102
	 *
103
	 * @since   11.3
104
	 * @throws  RuntimeException
105
	 */
106
	public function __construct($source = null)
107
	{
108
		// Verify that GD support for PHP is available.
109
		if (!extension_loaded('gd'))
110
		{
111
			// @codeCoverageIgnoreStart
112
			JLog::add('The GD extension for PHP is not available.', JLog::ERROR);
113
			throw new RuntimeException('The GD extension for PHP is not available.');
114
115
			// @codeCoverageIgnoreEnd
116
		}
117
118
		// Determine which image types are supported by GD, but only once.
119
		if (!isset(self::$formats[IMAGETYPE_JPEG]))
120
		{
121
			$info = gd_info();
122
			self::$formats[IMAGETYPE_JPEG] = ($info['JPEG Support']) ? true : false;
123
			self::$formats[IMAGETYPE_PNG]  = ($info['PNG Support']) ? true : false;
124
			self::$formats[IMAGETYPE_GIF]  = ($info['GIF Read Support']) ? true : false;
125
		}
126
127
		// If the source input is a resource, set it as the image handle.
128
		if (is_resource($source) && (get_resource_type($source) == 'gd'))
129
		{
130
			$this->handle = &$source;
131
		}
132
		elseif (!empty($source) && is_string($source))
133
		{
134
			// If the source input is not empty, assume it is a path and populate the image handle.
135
			$this->loadFile($source);
136
		}
137
	}
138
139
	/**
140
	 * Method to return a properties object for an image given a filesystem path.
141
	 * The result object has values for image width, height, type, attributes, bits, channels, mime type, file size and orientation.
142
	 *
143
	 * @param   string  $path  The filesystem path to the image for which to get properties.
144
	 *
145
	 * @return  stdClass
146
	 *
147
	 * @since   11.3
148
	 *
149
	 * @throws  InvalidArgumentException
150
	 * @throws  RuntimeException
151
	 */
152
	public static function getImageFileProperties($path)
153
	{
154
		// Make sure the file exists.
155
		if (!file_exists($path))
156
		{
157
			throw new InvalidArgumentException('The image file does not exist.');
158
		}
159
160
		// Get the image file information.
161
		$info = getimagesize($path);
162
163
		if (!$info)
0 ignored issues
show
Bug Best Practice introduced by
The expression $info of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
164
		{
165
			// @codeCoverageIgnoreStart
166
			throw new RuntimeException('Unable to get properties for the image.');
167
168
			// @codeCoverageIgnoreEnd
169
		}
170
171
		// Build the response object.
172
		$properties = (object) array(
173
			'width'       => $info[0],
174
			'height'      => $info[1],
175
			'type'        => $info[2],
176
			'attributes'  => $info[3],
177
			'bits'        => isset($info['bits']) ? $info['bits'] : null,
178
			'channels'    => isset($info['channels']) ? $info['channels'] : null,
179
			'mime'        => $info['mime'],
180
			'filesize'    => filesize($path),
181
			'orientation' => self::getOrientationString((int) $info[0], (int) $info[1]),
182
		);
183
184
		return $properties;
185
	}
186
187
	/**
188
	 * Method to detect whether an image's orientation is landscape, portrait or square.
189
	 * The orientation will be returned as a string.
190
	 *
191
	 * @return  mixed   Orientation string or null.
192
	 *
193
	 * @since   3.4.2
194
	 */
195
	public function getOrientation()
196
	{
197
		if ($this->isLoaded())
198
		{
199
			return self::getOrientationString($this->getWidth(), $this->getHeight());
200
		}
201
202
		return;
203
	}
204
205
	/**
206
	 * Compare width and height integers to determine image orientation.
207
	 *
208
	 * @param   integer  $width   The width value to use for calculation
209
	 * @param   integer  $height  The height value to use for calculation
210
	 *
211
	 * @return  string   Orientation string
212
	 *
213
	 * @since   3.4.2
214
	 */
215
	private static function getOrientationString($width, $height)
216
	{
217
		if ($width > $height)
218
		{
219
			return self::ORIENTATION_LANDSCAPE;
220
		}
221
222
		if ($width < $height)
223
		{
224
			return self::ORIENTATION_PORTRAIT;
225
		}
226
227
		return self::ORIENTATION_SQUARE;
228
	}
229
230
	/**
231
	 * Method to generate thumbnails from the current image. It allows
232
	 * creation by resizing or cropping the original image.
233
	 *
234
	 * @param   mixed    $thumbSizes      String or array of strings. Example: $thumbSizes = array('150x75','250x150');
235
	 * @param   integer  $creationMethod  1-3 resize $scaleMethod | 4 create cropping | 5 resize then crop
236
	 *
237
	 * @return  array    returns the generated thumb in the results array
238
	 *
239
	 * @since   12.2
240
	 * @throws  LogicException
241
	 * @throws  InvalidArgumentException
242
	 */
243
	public function generateThumbs($thumbSizes, $creationMethod = self::SCALE_INSIDE)
244
	{
245
		// Make sure the resource handle is valid.
246
		if (!$this->isLoaded())
247
		{
248
			throw new LogicException('No valid image was loaded.');
249
		}
250
251
		// Accept a single thumbsize string as parameter
252
		if (!is_array($thumbSizes))
253
		{
254
			$thumbSizes = array($thumbSizes);
255
		}
256
257
		// Process thumbs
258
		$generated = array();
259
260
		if (!empty($thumbSizes))
261
		{
262
			foreach ($thumbSizes as $thumbSize)
263
			{
264
				// Desired thumbnail size
265
				$size = explode('x', strtolower($thumbSize));
266
267
				if (count($size) != 2)
268
				{
269
					throw new InvalidArgumentException('Invalid thumb size received: ' . $thumbSize);
270
				}
271
272
				$thumbWidth  = $size[0];
273
				$thumbHeight = $size[1];
274
275
				switch ($creationMethod)
276
				{
277
					// Case for self::CROP
278
					case 4:
279
						$thumb = $this->crop($thumbWidth, $thumbHeight, null, null, true);
280
						break;
281
282
					// Case for self::CROP_RESIZE
283
					case 5:
284
						$thumb = $this->cropResize($thumbWidth, $thumbHeight, true);
0 ignored issues
show
Bug introduced by
$thumbWidth of type string is incompatible with the type integer expected by parameter $width of JImage::cropResize(). ( Ignorable by Annotation )

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

284
						$thumb = $this->cropResize(/** @scrutinizer ignore-type */ $thumbWidth, $thumbHeight, true);
Loading history...
Bug introduced by
$thumbHeight of type string is incompatible with the type integer expected by parameter $height of JImage::cropResize(). ( Ignorable by Annotation )

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

284
						$thumb = $this->cropResize($thumbWidth, /** @scrutinizer ignore-type */ $thumbHeight, true);
Loading history...
285
						break;
286
287
					default:
288
						$thumb = $this->resize($thumbWidth, $thumbHeight, true, $creationMethod);
289
						break;
290
				}
291
292
				// Store the thumb in the results array
293
				$generated[] = $thumb;
294
			}
295
		}
296
297
		return $generated;
298
	}
299
300
	/**
301
	 * Method to create thumbnails from the current image and save them to disk. It allows creation by resizing
302
	 * or cropping the original image.
303
	 *
304
	 * @param   mixed    $thumbSizes      string or array of strings. Example: $thumbSizes = array('150x75','250x150');
305
	 * @param   integer  $creationMethod  1-3 resize $scaleMethod | 4 create cropping
306
	 * @param   string   $thumbsFolder    destination thumbs folder. null generates a thumbs folder in the image folder
307
	 *
308
	 * @return  array    An array of JImage objects with thumb paths.
309
	 *
310
	 * @since   12.2
311
	 * @throws  LogicException
312
	 * @throws  InvalidArgumentException
313
	 */
314
	public function createThumbs($thumbSizes, $creationMethod = self::SCALE_INSIDE, $thumbsFolder = null)
315
	{
316
		// Make sure the resource handle is valid.
317
		if (!$this->isLoaded())
318
		{
319
			throw new LogicException('No valid image was loaded.');
320
		}
321
322
		// No thumbFolder set -> we will create a thumbs folder in the current image folder
323
		if (is_null($thumbsFolder))
324
		{
325
			$thumbsFolder = dirname($this->getPath()) . '/thumbs';
326
		}
327
328
		// Check destination
329
		if (!is_dir($thumbsFolder) && (!is_dir(dirname($thumbsFolder)) || !@mkdir($thumbsFolder)))
330
		{
331
			throw new InvalidArgumentException('Folder does not exist and cannot be created: ' . $thumbsFolder);
332
		}
333
334
		// Process thumbs
335
		$thumbsCreated = array();
336
337
		if ($thumbs = $this->generateThumbs($thumbSizes, $creationMethod))
338
		{
339
			// Parent image properties
340
			$imgProperties = self::getImageFileProperties($this->getPath());
341
342
			foreach ($thumbs as $thumb)
343
			{
344
				// Get thumb properties
345
				$thumbWidth  = $thumb->getWidth();
346
				$thumbHeight = $thumb->getHeight();
347
348
				// Generate thumb name
349
				$filename      = pathinfo($this->getPath(), PATHINFO_FILENAME);
350
				$fileExtension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
351
				$thumbFileName = $filename . '_' . $thumbWidth . 'x' . $thumbHeight . '.' . $fileExtension;
0 ignored issues
show
Bug introduced by
Are you sure $filename of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

351
				$thumbFileName = /** @scrutinizer ignore-type */ $filename . '_' . $thumbWidth . 'x' . $thumbHeight . '.' . $fileExtension;
Loading history...
Bug introduced by
Are you sure $fileExtension of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

351
				$thumbFileName = $filename . '_' . $thumbWidth . 'x' . $thumbHeight . '.' . /** @scrutinizer ignore-type */ $fileExtension;
Loading history...
352
353
				// Save thumb file to disk
354
				$thumbFileName = $thumbsFolder . '/' . $thumbFileName;
355
356
				if ($thumb->toFile($thumbFileName, $imgProperties->type))
357
				{
358
					// Return JImage object with thumb path to ease further manipulation
359
					$thumb->path     = $thumbFileName;
360
					$thumbsCreated[] = $thumb;
361
				}
362
			}
363
		}
364
365
		return $thumbsCreated;
366
	}
367
368
	/**
369
	 * Method to crop the current image.
370
	 *
371
	 * @param   mixed    $width      The width of the image section to crop in pixels or a percentage.
372
	 * @param   mixed    $height     The height of the image section to crop in pixels or a percentage.
373
	 * @param   integer  $left       The number of pixels from the left to start cropping.
374
	 * @param   integer  $top        The number of pixels from the top to start cropping.
375
	 * @param   boolean  $createNew  If true the current image will be cloned, cropped and returned; else
376
	 *                               the current image will be cropped and returned.
377
	 *
378
	 * @return  JImage
379
	 *
380
	 * @since   11.3
381
	 * @throws  LogicException
382
	 */
383
	public function crop($width, $height, $left = null, $top = null, $createNew = true)
384
	{
385
		// Make sure the resource handle is valid.
386
		if (!$this->isLoaded())
387
		{
388
			throw new LogicException('No valid image was loaded.');
389
		}
390
391
		// Sanitize width.
392
		$width = $this->sanitizeWidth($width, $height);
393
394
		// Sanitize height.
395
		$height = $this->sanitizeHeight($height, $width);
396
397
		// Autocrop offsets
398
		if (is_null($left))
399
		{
400
			$left = round(($this->getWidth() - $width) / 2);
401
		}
402
403
		if (is_null($top))
404
		{
405
			$top = round(($this->getHeight() - $height) / 2);
406
		}
407
408
		// Sanitize left.
409
		$left = $this->sanitizeOffset($left);
410
411
		// Sanitize top.
412
		$top = $this->sanitizeOffset($top);
413
414
		// Create the new truecolor image handle.
415
		$handle = imagecreatetruecolor($width, $height);
416
417
		// Allow transparency for the new image handle.
418
		imagealphablending($handle, false);
419
		imagesavealpha($handle, true);
420
421
		if ($this->isTransparent())
422
		{
423
			// Get the transparent color values for the current image.
424
			$rgba  = imageColorsForIndex($this->handle, imagecolortransparent($this->handle));
425
			$color = imageColorAllocateAlpha($handle, $rgba['red'], $rgba['green'], $rgba['blue'], $rgba['alpha']);
426
427
			// Set the transparent color values for the new image.
428
			imagecolortransparent($handle, $color);
429
			imagefill($handle, 0, 0, $color);
430
		}
431
432
		if (!$this->generateBestQuality)
433
		{
434
			imagecopyresized($handle, $this->handle, 0, 0, $left, $top, $width, $height, $width, $height);
435
		}
436
		else
437
		{
438
			imagecopyresampled($handle, $this->handle, 0, 0, $left, $top, $width, $height, $width, $height);
439
		}
440
441
		// If we are cropping to a new image, create a new JImage object.
442
		if ($createNew)
443
		{
444
			// @codeCoverageIgnoreStart
445
			$new = new JImage($handle);
446
447
			return $new;
448
449
			// @codeCoverageIgnoreEnd
450
		}
451
		// Swap out the current handle for the new image handle.
452
		else
453
		{
454
			// Free the memory from the current handle
455
			$this->destroy();
456
457
			$this->handle = $handle;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handle can also be of type GdImage. However, the property $handle is declared as type resource. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
458
459
			return $this;
460
		}
461
	}
462
463
	/**
464
	 * Method to apply a filter to the image by type.  Two examples are: grayscale and sketchy.
465
	 *
466
	 * @param   string  $type     The name of the image filter to apply.
467
	 * @param   array   $options  An array of options for the filter.
468
	 *
469
	 * @return  JImage
470
	 *
471
	 * @since   11.3
472
	 * @see     JImageFilter
473
	 * @throws  LogicException
474
	 */
475
	public function filter($type, array $options = array())
476
	{
477
		// Make sure the resource handle is valid.
478
		if (!$this->isLoaded())
479
		{
480
			throw new LogicException('No valid image was loaded.');
481
		}
482
483
		// Get the image filter instance.
484
		$filter = $this->getFilterInstance($type);
485
486
		// Execute the image filter.
487
		$filter->execute($options);
488
489
		return $this;
490
	}
491
492
	/**
493
	 * Method to get the height of the image in pixels.
494
	 *
495
	 * @return  integer
496
	 *
497
	 * @since   11.3
498
	 * @throws  LogicException
499
	 */
500
	public function getHeight()
501
	{
502
		// Make sure the resource handle is valid.
503
		if (!$this->isLoaded())
504
		{
505
			throw new LogicException('No valid image was loaded.');
506
		}
507
508
		return imagesy($this->handle);
509
	}
510
511
	/**
512
	 * Method to get the width of the image in pixels.
513
	 *
514
	 * @return  integer
515
	 *
516
	 * @since   11.3
517
	 * @throws  LogicException
518
	 */
519
	public function getWidth()
520
	{
521
		// Make sure the resource handle is valid.
522
		if (!$this->isLoaded())
523
		{
524
			throw new LogicException('No valid image was loaded.');
525
		}
526
527
		return imagesx($this->handle);
528
	}
529
530
	/**
531
	 * Method to return the path
532
	 *
533
	 * @return	string
534
	 *
535
	 * @since	11.3
536
	 */
537
	public function getPath()
538
	{
539
		return $this->path;
540
	}
541
542
	/**
543
	 * Method to determine whether or not an image has been loaded into the object.
544
	 *
545
	 * @return  boolean
546
	 *
547
	 * @since   11.3
548
	 */
549
	public function isLoaded()
550
	{
551
		// Make sure the resource handle is valid.
552
		if (!is_resource($this->handle) || (get_resource_type($this->handle) != 'gd'))
553
		{
554
			return false;
555
		}
556
557
		return true;
558
	}
559
560
	/**
561
	 * Method to determine whether or not the image has transparency.
562
	 *
563
	 * @return  boolean
564
	 *
565
	 * @since   11.3
566
	 * @throws  LogicException
567
	 */
568
	public function isTransparent()
569
	{
570
		// Make sure the resource handle is valid.
571
		if (!$this->isLoaded())
572
		{
573
			throw new LogicException('No valid image was loaded.');
574
		}
575
576
		return imagecolortransparent($this->handle) >= 0;
577
	}
578
579
	/**
580
	 * Method to load a file into the JImage object as the resource.
581
	 *
582
	 * @param   string  $path  The filesystem path to load as an image.
583
	 *
584
	 * @return  void
585
	 *
586
	 * @since   11.3
587
	 * @throws  InvalidArgumentException
588
	 * @throws  RuntimeException
589
	 */
590
	public function loadFile($path)
591
	{
592
		// Destroy the current image handle if it exists
593
		$this->destroy();
594
595
		// Make sure the file exists.
596
		if (!file_exists($path))
597
		{
598
			throw new InvalidArgumentException('The image file does not exist.');
599
		}
600
601
		// Get the image properties.
602
		$properties = self::getImageFileProperties($path);
603
604
		// Attempt to load the image based on the MIME-Type
605
		switch ($properties->mime)
606
		{
607
			case 'image/gif':
608
				// Make sure the image type is supported.
609
				if (empty(self::$formats[IMAGETYPE_GIF]))
610
				{
611
					// @codeCoverageIgnoreStart
612
					JLog::add('Attempting to load an image of unsupported type GIF.', JLog::ERROR);
613
					throw new RuntimeException('Attempting to load an image of unsupported type GIF.');
614
615
					// @codeCoverageIgnoreEnd
616
				}
617
618
				// Attempt to create the image handle.
619
				$handle = imagecreatefromgif($path);
620
621
				if (!is_resource($handle))
622
				{
623
					// @codeCoverageIgnoreStart
624
					throw new RuntimeException('Unable to process GIF image.');
625
626
					// @codeCoverageIgnoreEnd
627
				}
628
629
				$this->handle = $handle;
630
				break;
631
632
			case 'image/jpeg':
633
				// Make sure the image type is supported.
634
				if (empty(self::$formats[IMAGETYPE_JPEG]))
635
				{
636
					// @codeCoverageIgnoreStart
637
					JLog::add('Attempting to load an image of unsupported type JPG.', JLog::ERROR);
638
					throw new RuntimeException('Attempting to load an image of unsupported type JPG.');
639
640
					// @codeCoverageIgnoreEnd
641
				}
642
643
				// Attempt to create the image handle.
644
				$handle = imagecreatefromjpeg($path);
645
646
				if (!is_resource($handle))
647
				{
648
					// @codeCoverageIgnoreStart
649
					throw new RuntimeException('Unable to process JPG image.');
650
651
					// @codeCoverageIgnoreEnd
652
				}
653
654
				$this->handle = $handle;
655
				break;
656
657
			case 'image/png':
658
				// Make sure the image type is supported.
659
				if (empty(self::$formats[IMAGETYPE_PNG]))
660
				{
661
					// @codeCoverageIgnoreStart
662
					JLog::add('Attempting to load an image of unsupported type PNG.', JLog::ERROR);
663
					throw new RuntimeException('Attempting to load an image of unsupported type PNG.');
664
665
					// @codeCoverageIgnoreEnd
666
				}
667
668
				// Attempt to create the image handle.
669
				$handle = imagecreatefrompng($path);
670
671
				if (!is_resource($handle))
672
				{
673
					// @codeCoverageIgnoreStart
674
					throw new RuntimeException('Unable to process PNG image.');
675
676
					// @codeCoverageIgnoreEnd
677
				}
678
679
				$this->handle = $handle;
680
681
				break;
682
683
			default:
684
				JLog::add('Attempting to load an image of unsupported type: ' . $properties->mime, JLog::ERROR);
685
				throw new InvalidArgumentException('Attempting to load an image of unsupported type: ' . $properties->mime);
686
				break;
687
		}
688
689
		// Set the filesystem path to the source image.
690
		$this->path = $path;
691
	}
692
693
	/**
694
	 * Method to resize the current image.
695
	 *
696
	 * @param   mixed    $width        The width of the resized image in pixels or a percentage.
697
	 * @param   mixed    $height       The height of the resized image in pixels or a percentage.
698
	 * @param   boolean  $createNew    If true the current image will be cloned, resized and returned; else
699
	 *                                 the current image will be resized and returned.
700
	 * @param   integer  $scaleMethod  Which method to use for scaling
701
	 *
702
	 * @return  JImage
703
	 *
704
	 * @since   11.3
705
	 * @throws  LogicException
706
	 */
707
	public function resize($width, $height, $createNew = true, $scaleMethod = self::SCALE_INSIDE)
708
	{
709
		// Make sure the resource handle is valid.
710
		if (!$this->isLoaded())
711
		{
712
			throw new LogicException('No valid image was loaded.');
713
		}
714
715
		// Sanitize width.
716
		$width = $this->sanitizeWidth($width, $height);
717
718
		// Sanitize height.
719
		$height = $this->sanitizeHeight($height, $width);
720
721
		// Prepare the dimensions for the resize operation.
722
		$dimensions = $this->prepareDimensions($width, $height, $scaleMethod);
723
724
		// Instantiate offset.
725
		$offset    = new stdClass;
726
		$offset->x = $offset->y = 0;
727
728
		// Center image if needed and create the new truecolor image handle.
729
		if ($scaleMethod == self::SCALE_FIT)
730
		{
731
			// Get the offsets
732
			$offset->x = round(($width - $dimensions->width) / 2);
733
			$offset->y = round(($height - $dimensions->height) / 2);
734
735
			$handle = imagecreatetruecolor($width, $height);
736
737
			// Make image transparent, otherwise cavas outside initial image would default to black
738
			if (!$this->isTransparent())
739
			{
740
				$transparency = imagecolorAllocateAlpha($this->handle, 0, 0, 0, 127);
741
				imagecolorTransparent($this->handle, $transparency);
742
			}
743
		}
744
		else
745
		{
746
			$handle = imagecreatetruecolor($dimensions->width, $dimensions->height);
747
		}
748
749
		// Allow transparency for the new image handle.
750
		imagealphablending($handle, false);
751
		imagesavealpha($handle, true);
752
753
		if ($this->isTransparent())
754
		{
755
			// Get the transparent color values for the current image.
756
			$rgba  = imageColorsForIndex($this->handle, imagecolortransparent($this->handle));
757
			$color = imageColorAllocateAlpha($handle, $rgba['red'], $rgba['green'], $rgba['blue'], $rgba['alpha']);
758
759
			// Set the transparent color values for the new image.
760
			imagecolortransparent($handle, $color);
761
			imagefill($handle, 0, 0, $color);
762
		}
763
764
		if (!$this->generateBestQuality)
765
		{
766
			imagecopyresized(
767
				$handle,
768
				$this->handle,
769
				$offset->x,
0 ignored issues
show
Bug introduced by
It seems like $offset->x can also be of type double; however, parameter $dst_x of imagecopyresized() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

769
				/** @scrutinizer ignore-type */ $offset->x,
Loading history...
770
				$offset->y,
0 ignored issues
show
Bug introduced by
It seems like $offset->y can also be of type double; however, parameter $dst_y of imagecopyresized() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

770
				/** @scrutinizer ignore-type */ $offset->y,
Loading history...
771
				0,
772
				0,
773
				$dimensions->width,
774
				$dimensions->height,
775
				$this->getWidth(),
776
				$this->getHeight()
777
			);
778
		}
779
		else
780
		{
781
			imagecopyresampled(
782
				$handle,
783
				$this->handle,
784
				$offset->x,
0 ignored issues
show
Bug introduced by
It seems like $offset->x can also be of type double; however, parameter $dst_x of imagecopyresampled() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

784
				/** @scrutinizer ignore-type */ $offset->x,
Loading history...
785
				$offset->y,
0 ignored issues
show
Bug introduced by
It seems like $offset->y can also be of type double; however, parameter $dst_y of imagecopyresampled() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

785
				/** @scrutinizer ignore-type */ $offset->y,
Loading history...
786
				0,
787
				0,
788
				$dimensions->width,
789
				$dimensions->height,
790
				$this->getWidth(),
791
				$this->getHeight()
792
			);
793
		}
794
795
		// If we are resizing to a new image, create a new JImage object.
796
		if ($createNew)
797
		{
798
			// @codeCoverageIgnoreStart
799
			$new = new JImage($handle);
800
801
			return $new;
802
803
			// @codeCoverageIgnoreEnd
804
		}
805
		// Swap out the current handle for the new image handle.
806
		else
807
		{
808
			// Free the memory from the current handle
809
			$this->destroy();
810
811
			$this->handle = $handle;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handle can also be of type GdImage. However, the property $handle is declared as type resource. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
812
813
			return $this;
814
		}
815
	}
816
817
	/**
818
	 * Method to crop an image after resizing it to maintain
819
	 * proportions without having to do all the set up work.
820
	 *
821
	 * @param   integer  $width      The desired width of the image in pixels or a percentage.
822
	 * @param   integer  $height     The desired height of the image in pixels or a percentage.
823
	 * @param   boolean  $createNew  If true the current image will be cloned, resized, cropped and returned.
824
	 *
825
	 * @return  object  JImage Object for chaining.
826
	 *
827
	 * @since   12.3
828
	 */
829
	public function cropResize($width, $height, $createNew = true)
830
	{
831
		$width  = $this->sanitizeWidth($width, $height);
832
		$height = $this->sanitizeHeight($height, $width);
833
834
		$resizewidth  = $width;
835
		$resizeheight = $height;
836
837
		if (($this->getWidth() / $width) < ($this->getHeight() / $height))
838
		{
839
			$resizeheight = 0;
840
		}
841
		else
842
		{
843
			$resizewidth = 0;
844
		}
845
846
		return $this->resize($resizewidth, $resizeheight, $createNew)->crop($width, $height, null, null, false);
847
	}
848
849
	/**
850
	 * Method to rotate the current image.
851
	 *
852
	 * @param   mixed    $angle       The angle of rotation for the image
853
	 * @param   integer  $background  The background color to use when areas are added due to rotation
854
	 * @param   boolean  $createNew   If true the current image will be cloned, rotated and returned; else
855
	 *                                the current image will be rotated and returned.
856
	 *
857
	 * @return  JImage
858
	 *
859
	 * @since   11.3
860
	 * @throws  LogicException
861
	 */
862
	public function rotate($angle, $background = -1, $createNew = true)
863
	{
864
		// Make sure the resource handle is valid.
865
		if (!$this->isLoaded())
866
		{
867
			throw new LogicException('No valid image was loaded.');
868
		}
869
870
		// Sanitize input
871
		$angle = (float) $angle;
872
873
		// Create the new truecolor image handle.
874
		$handle = imagecreatetruecolor($this->getWidth(), $this->getHeight());
875
876
		// Make background transparent if no external background color is provided.
877
		if ($background == -1)
878
		{
879
			// Allow transparency for the new image handle.
880
			imagealphablending($handle, false);
881
			imagesavealpha($handle, true);
882
883
			$background = imagecolorallocatealpha($handle, 0, 0, 0, 127);
884
		}
885
886
		// Copy the image
887
		imagecopy($handle, $this->handle, 0, 0, 0, 0, $this->getWidth(), $this->getHeight());
888
889
		// Rotate the image
890
		$handle = imagerotate($handle, $angle, $background);
891
892
		// If we are resizing to a new image, create a new JImage object.
893
		if ($createNew)
894
		{
895
			// @codeCoverageIgnoreStart
896
			$new = new JImage($handle);
897
898
			return $new;
899
900
			// @codeCoverageIgnoreEnd
901
		}
902
		// Swap out the current handle for the new image handle.
903
		else
904
		{
905
			// Free the memory from the current handle
906
			$this->destroy();
907
908
			$this->handle = $handle;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handle can also be of type GdImage. However, the property $handle is declared as type resource. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
909
910
			return $this;
911
		}
912
	}
913
914
	/**
915
	 * Method to flip the current image.
916
	 *
917
	 * @param   integer  $mode       The flip mode for flipping the image {@link https://secure.php.net/imageflip#refsect1-function.imageflip-parameters}
918
	 * @param   boolean  $createNew  If true the current image will be cloned, flipped and returned; else the current image will be flipped and returned.
919
	 *
920
	 * @return  JImage
921
	 *
922
	 * @since   11.3
923
	 * @throws  LogicException
924
	 */
925
	public function flip($mode, $createNew = true)
926
	{
927
		// Make sure the resource handle is valid.
928
		if (!$this->isLoaded())
929
		{
930
			throw new LogicException('No valid image was loaded.');
931
		}
932
933
		// Create the new truecolor image handle.
934
		$handle = imagecreatetruecolor($this->getWidth(), $this->getHeight());
935
936
		// Copy the image
937
		imagecopy($handle, $this->handle, 0, 0, 0, 0, $this->getWidth(), $this->getHeight());
938
939
		// Flip the image
940
		if (!imageflip($handle, $mode))
941
		{
942
			throw new LogicException('Unable to flip the image.');
943
		}
944
945
		// If we are resizing to a new image, create a new JImage object.
946
		if ($createNew)
947
		{
948
			// @codeCoverageIgnoreStart
949
			$new = new JImage($handle);
950
951
			return $new;
952
953
			// @codeCoverageIgnoreEnd
954
		}
955
956
		// Free the memory from the current handle
957
		$this->destroy();
958
959
		// Swap out the current handle for the new image handle.
960
		$this->handle = $handle;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handle can also be of type GdImage. However, the property $handle is declared as type resource. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
961
962
		return $this;
963
	}
964
965
	/**
966
	 * Method to write the current image out to a file.
967
	 *
968
	 * @param   string   $path     The filesystem path to save the image.
969
	 * @param   integer  $type     The image type to save the file as.
970
	 * @param   array    $options  The image type options to use in saving the file.
971
	 *
972
	 * @return  boolean
973
	 *
974
	 * @see     https://secure.php.net/manual/image.constants.php
975
	 * @since   11.3
976
	 * @throws  LogicException
977
	 */
978
	public function toFile($path, $type = IMAGETYPE_JPEG, array $options = array())
979
	{
980
		// Make sure the resource handle is valid.
981
		if (!$this->isLoaded())
982
		{
983
			throw new LogicException('No valid image was loaded.');
984
		}
985
986
		switch ($type)
987
		{
988
			case IMAGETYPE_GIF:
989
				return imagegif($this->handle, $path);
990
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
991
992
			case IMAGETYPE_PNG:
993
				return imagepng($this->handle, $path, (array_key_exists('quality', $options)) ? $options['quality'] : 0);
994
				break;
995
996
			case IMAGETYPE_JPEG:
997
			default:
998
				return imagejpeg($this->handle, $path, (array_key_exists('quality', $options)) ? $options['quality'] : 100);
999
		}
1000
	}
1001
1002
	/**
1003
	 * Method to get an image filter instance of a specified type.
1004
	 *
1005
	 * @param   string  $type  The image filter type to get.
1006
	 *
1007
	 * @return  JImageFilter
1008
	 *
1009
	 * @since   11.3
1010
	 * @throws  RuntimeException
1011
	 */
1012
	protected function getFilterInstance($type)
1013
	{
1014
		// Sanitize the filter type.
1015
		$type = strtolower(preg_replace('#[^A-Z0-9_]#i', '', $type));
1016
1017
		// Verify that the filter type exists.
1018
		$className = 'JImageFilter' . ucfirst($type);
1019
1020
		if (!class_exists($className))
1021
		{
1022
			JLog::add('The ' . ucfirst($type) . ' image filter is not available.', JLog::ERROR);
1023
			throw new RuntimeException('The ' . ucfirst($type) . ' image filter is not available.');
1024
		}
1025
1026
		// Instantiate the filter object.
1027
		$instance = new $className($this->handle);
1028
1029
		// Verify that the filter type is valid.
1030
		if (!($instance instanceof JImageFilter))
1031
		{
1032
			// @codeCoverageIgnoreStart
1033
			JLog::add('The ' . ucfirst($type) . ' image filter is not valid.', JLog::ERROR);
1034
			throw new RuntimeException('The ' . ucfirst($type) . ' image filter is not valid.');
1035
1036
			// @codeCoverageIgnoreEnd
1037
		}
1038
1039
		return $instance;
1040
	}
1041
1042
	/**
1043
	 * Method to get the new dimensions for a resized image.
1044
	 *
1045
	 * @param   integer  $width        The width of the resized image in pixels.
1046
	 * @param   integer  $height       The height of the resized image in pixels.
1047
	 * @param   integer  $scaleMethod  The method to use for scaling
1048
	 *
1049
	 * @return  stdClass
1050
	 *
1051
	 * @since   11.3
1052
	 * @throws  InvalidArgumentException  If width, height or both given as zero
1053
	 */
1054
	protected function prepareDimensions($width, $height, $scaleMethod)
1055
	{
1056
		// Instantiate variables.
1057
		$dimensions = new stdClass;
1058
1059
		switch ($scaleMethod)
1060
		{
1061
			case self::SCALE_FILL:
1062
				$dimensions->width = (int) round($width);
1063
				$dimensions->height = (int) round($height);
1064
				break;
1065
1066
			case self::SCALE_INSIDE:
1067
			case self::SCALE_OUTSIDE:
1068
			case self::SCALE_FIT:
1069
				$rx = ($width > 0) ? ($this->getWidth() / $width) : 0;
1070
				$ry = ($height > 0) ? ($this->getHeight() / $height) : 0;
1071
1072
				if ($scaleMethod != self::SCALE_OUTSIDE)
1073
				{
1074
					$ratio = max($rx, $ry);
1075
				}
1076
				else
1077
				{
1078
					$ratio = min($rx, $ry);
1079
				}
1080
1081
				$dimensions->width  = (int) round($this->getWidth() / $ratio);
1082
				$dimensions->height = (int) round($this->getHeight() / $ratio);
1083
				break;
1084
1085
			default:
1086
				throw new InvalidArgumentException('Invalid scale method.');
1087
				break;
1088
		}
1089
1090
		return $dimensions;
1091
	}
1092
1093
	/**
1094
	 * Method to sanitize a height value.
1095
	 *
1096
	 * @param   mixed  $height  The input height value to sanitize.
1097
	 * @param   mixed  $width   The input width value for reference.
1098
	 *
1099
	 * @return  integer
1100
	 *
1101
	 * @since   11.3
1102
	 */
1103
	protected function sanitizeHeight($height, $width)
1104
	{
1105
		// If no height was given we will assume it is a square and use the width.
1106
		$height = ($height === null) ? $width : $height;
1107
1108
		// If we were given a percentage, calculate the integer value.
1109
		if (preg_match('/^[0-9]+(\.[0-9]+)?\%$/', $height))
1110
		{
1111
			$height = (int) round($this->getHeight() * (float) str_replace('%', '', $height) / 100);
1112
		}
1113
		// Else do some rounding so we come out with a sane integer value.
1114
		else
1115
		{
1116
			$height = (int) round((float) $height);
1117
		}
1118
1119
		return $height;
1120
	}
1121
1122
	/**
1123
	 * Method to sanitize an offset value like left or top.
1124
	 *
1125
	 * @param   mixed  $offset  An offset value.
1126
	 *
1127
	 * @return  integer
1128
	 *
1129
	 * @since   11.3
1130
	 */
1131
	protected function sanitizeOffset($offset)
1132
	{
1133
		return (int) round((float) $offset);
1134
	}
1135
1136
	/**
1137
	 * Method to sanitize a width value.
1138
	 *
1139
	 * @param   mixed  $width   The input width value to sanitize.
1140
	 * @param   mixed  $height  The input height value for reference.
1141
	 *
1142
	 * @return  integer
1143
	 *
1144
	 * @since   11.3
1145
	 */
1146
	protected function sanitizeWidth($width, $height)
1147
	{
1148
		// If no width was given we will assume it is a square and use the height.
1149
		$width = ($width === null) ? $height : $width;
1150
1151
		// If we were given a percentage, calculate the integer value.
1152
		if (preg_match('/^[0-9]+(\.[0-9]+)?\%$/', $width))
1153
		{
1154
			$width = (int) round($this->getWidth() * (float) str_replace('%', '', $width) / 100);
1155
		}
1156
		// Else do some rounding so we come out with a sane integer value.
1157
		else
1158
		{
1159
			$width = (int) round((float) $width);
1160
		}
1161
1162
		return $width;
1163
	}
1164
1165
	/**
1166
	 * Method to destroy an image handle and
1167
	 * free the memory associated with the handle
1168
	 *
1169
	 * @return  boolean  True on success, false on failure or if no image is loaded
1170
	 *
1171
	 * @since   12.3
1172
	 */
1173
	public function destroy()
1174
	{
1175
		if ($this->isLoaded())
1176
		{
1177
			return imagedestroy($this->handle);
1178
		}
1179
1180
		return false;
1181
	}
1182
1183
	/**
1184
	 * Method to call the destroy() method one last time
1185
	 * to free any memory when the object is unset
1186
	 *
1187
	 * @see     JImage::destroy()
1188
	 * @since   12.3
1189
	 */
1190
	public function __destruct()
1191
	{
1192
		$this->destroy();
1193
	}
1194
1195
	/**
1196
	 * Method for set option of generate thumbnail method
1197
	 *
1198
	 * @param   boolean  $quality  True for best quality. False for best speed.
1199
	 *
1200
	 * @return  void
1201
	 *
1202
	 * @since   3.7.0
1203
	 */
1204
	public function setThumbnailGenerate($quality = true)
1205
	{
1206
		$this->generateBestQuality = (boolean) $quality;
1207
	}
1208
}
1209