Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
image.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Image
5  *
6  * @copyright Copyright (C) 2005 - 2013 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  * @package Joomla.Platform
16  * @subpackage Image
17  * @since 11.3
18  */
19 class JImage
20 {
21  /**
22  * @const integer
23  * @since 11.3
24  */
25  const SCALE_FILL = 1;
26 
27  /**
28  * @const integer
29  * @since 11.3
30  */
31  const SCALE_INSIDE = 2;
32 
33  /**
34  * @const integer
35  * @since 11.3
36  */
37  const SCALE_OUTSIDE = 3;
38 
39  /**
40  * @const integer
41  * @since 12.2
42  */
43  const CROP = 4;
44 
45  /**
46  * @const integer
47  * @since 12.3
48  */
49  const CROP_RESIZE = 5;
50 
51  /**
52  * @const integer
53  * @since 3.2
54  */
55  const SCALE_FIT = 6;
56 
57  /**
58  * @var resource The image resource handle.
59  * @since 11.3
60  */
61  protected $handle;
62 
63  /**
64  * @var string The source image path.
65  * @since 11.3
66  */
67  protected $path = null;
68 
69  /**
70  * @var array Whether or not different image formats are supported.
71  * @since 11.3
72  */
73  protected static $formats = array();
74 
75  /**
76  * Class constructor.
77  *
78  * @param mixed $source Either a file path for a source image or a GD resource handler for an image.
79  *
80  * @since 11.3
81  * @throws RuntimeException
82  */
83  public function __construct($source = null)
84  {
85  // Verify that GD support for PHP is available.
86  if (!extension_loaded('gd'))
87  {
88  // @codeCoverageIgnoreStart
89  JLog::add('The GD extension for PHP is not available.', JLog::ERROR);
90  throw new RuntimeException('The GD extension for PHP is not available.');
91 
92  // @codeCoverageIgnoreEnd
93  }
94 
95  // Determine which image types are supported by GD, but only once.
96  if (!isset(self::$formats[IMAGETYPE_JPEG]))
97  {
98  $info = gd_info();
99  self::$formats[IMAGETYPE_JPEG] = ($info['JPEG Support']) ? true : false;
100  self::$formats[IMAGETYPE_PNG] = ($info['PNG Support']) ? true : false;
101  self::$formats[IMAGETYPE_GIF] = ($info['GIF Read Support']) ? true : false;
102  }
103 
104  // If the source input is a resource, set it as the image handle.
105  if (is_resource($source) && (get_resource_type($source) == 'gd'))
106  {
107  $this->handle = &$source;
108  }
109  elseif (!empty($source) && is_string($source))
110  {
111  // If the source input is not empty, assume it is a path and populate the image handle.
112  $this->loadFile($source);
113  }
114  }
115 
116  /**
117  * Method to return a properties object for an image given a filesystem path. The
118  * result object has values for image width, height, type, attributes, mime type, bits,
119  * and channels.
120  *
121  * @param string $path The filesystem path to the image for which to get properties.
122  *
123  * @return stdClass
124  *
125  * @since 11.3
126  * @throws InvalidArgumentException
127  * @throws RuntimeException
128  */
129  public static function getImageFileProperties($path)
130  {
131  // Make sure the file exists.
132  if (!file_exists($path))
133  {
134  throw new InvalidArgumentException('The image file does not exist.');
135  }
136 
137  // Get the image file information.
138  $info = getimagesize($path);
139 
140  if (!$info)
141  {
142  // @codeCoverageIgnoreStart
143  throw new RuntimeException('Unable to get properties for the image.');
144 
145  // @codeCoverageIgnoreEnd
146  }
147 
148  // Build the response object.
149  $properties = (object) array(
150  'width' => $info[0],
151  'height' => $info[1],
152  'type' => $info[2],
153  'attributes' => $info[3],
154  'bits' => isset($info['bits']) ? $info['bits'] : null,
155  'channels' => isset($info['channels']) ? $info['channels'] : null,
156  'mime' => $info['mime']
157  );
158 
159  return $properties;
160  }
161 
162  /**
163  * Method to generate thumbnails from the current image. It allows
164  * creation by resizing or cropping the original image.
165  *
166  * @param mixed $thumbSizes String or array of strings. Example: $thumbSizes = array('150x75','250x150');
167  * @param integer $creationMethod 1-3 resize $scaleMethod | 4 create croppping | 5 resize then crop
168  *
169  * @return array
170  *
171  * @since 12.2
172  * @throws LogicException
173  * @throws InvalidArgumentException
174  */
175  public function generateThumbs($thumbSizes, $creationMethod = self::SCALE_INSIDE)
176  {
177  // Make sure the resource handle is valid.
178  if (!$this->isLoaded())
179  {
180  throw new LogicException('No valid image was loaded.');
181  }
182 
183  // Accept a single thumbsize string as parameter
184  if (!is_array($thumbSizes))
185  {
186  $thumbSizes = array($thumbSizes);
187  }
188 
189  // Process thumbs
190  $generated = array();
191 
192  if (!empty($thumbSizes))
193  {
194  foreach ($thumbSizes as $thumbSize)
195  {
196  // Desired thumbnail size
197  $size = explode('x', strtolower($thumbSize));
198 
199  if (count($size) != 2)
200  {
201  throw new InvalidArgumentException('Invalid thumb size received: ' . $thumbSize);
202  }
203 
204  $thumbWidth = $size[0];
205  $thumbHeight = $size[1];
206 
207  switch ($creationMethod)
208  {
209  // Case for self::CROP
210  case 4:
211  $thumb = $this->crop($thumbWidth, $thumbHeight, null, null, true);
212  break;
213 
214  // Case for self::CROP_RESIZE
215  case 5:
216  $thumb = $this->cropResize($thumbWidth, $thumbHeight, true);
217  break;
218 
219  default:
220  $thumb = $this->resize($thumbWidth, $thumbHeight, true, $creationMethod);
221  break;
222  }
223 
224  // Store the thumb in the results array
225  $generated[] = $thumb;
226  }
227  }
228 
229  return $generated;
230  }
231 
232  /**
233  * Method to create thumbnails from the current image and save them to disk. It allows creation by resizing
234  * or croppping the original image.
235  *
236  * @param mixed $thumbSizes string or array of strings. Example: $thumbSizes = array('150x75','250x150');
237  * @param integer $creationMethod 1-3 resize $scaleMethod | 4 create croppping
238  * @param string $thumbsFolder destination thumbs folder. null generates a thumbs folder in the image folder
239  *
240  * @return array
241  *
242  * @since 12.2
243  * @throws LogicException
244  * @throws InvalidArgumentException
245  */
246  public function createThumbs($thumbSizes, $creationMethod = self::SCALE_INSIDE, $thumbsFolder = null)
247  {
248  // Make sure the resource handle is valid.
249  if (!$this->isLoaded())
250  {
251  throw new LogicException('No valid image was loaded.');
252  }
253 
254  // No thumbFolder set -> we will create a thumbs folder in the current image folder
255  if (is_null($thumbsFolder))
256  {
257  $thumbsFolder = dirname($this->getPath()) . '/thumbs';
258  }
259 
260  // Check destination
261  if (!is_dir($thumbsFolder) && (!is_dir(dirname($thumbsFolder)) || !@mkdir($thumbsFolder)))
262  {
263  throw new InvalidArgumentException('Folder does not exist and cannot be created: ' . $thumbsFolder);
264  }
265 
266  // Process thumbs
267  $thumbsCreated = array();
268 
269  if ($thumbs = $this->generateThumbs($thumbSizes, $creationMethod))
270  {
271  // Parent image properties
272  $imgProperties = self::getImageFileProperties($this->getPath());
273 
274  foreach ($thumbs as $thumb)
275  {
276  // Get thumb properties
277  $thumbWidth = $thumb->getWidth();
278  $thumbHeight = $thumb->getHeight();
279 
280  // Generate thumb name
281  $filename = pathinfo($this->getPath(), PATHINFO_FILENAME);
282  $fileExtension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
283  $thumbFileName = $filename . '_' . $thumbWidth . 'x' . $thumbHeight . '.' . $fileExtension;
284 
285  // Save thumb file to disk
286  $thumbFileName = $thumbsFolder . '/' . $thumbFileName;
287 
288  if ($thumb->toFile($thumbFileName, $imgProperties->type))
289  {
290  // Return JImage object with thumb path to ease further manipulation
291  $thumb->path = $thumbFileName;
292  $thumbsCreated[] = $thumb;
293  }
294  }
295  }
296 
297  return $thumbsCreated;
298  }
299 
300  /**
301  * Method to crop the current image.
302  *
303  * @param mixed $width The width of the image section to crop in pixels or a percentage.
304  * @param mixed $height The height of the image section to crop in pixels or a percentage.
305  * @param integer $left The number of pixels from the left to start cropping.
306  * @param integer $top The number of pixels from the top to start cropping.
307  * @param boolean $createNew If true the current image will be cloned, cropped and returned; else
308  * the current image will be cropped and returned.
309  *
310  * @return JImage
311  *
312  * @since 11.3
313  * @throws LogicException
314  */
315  public function crop($width, $height, $left = null, $top = null, $createNew = true)
316  {
317  // Make sure the resource handle is valid.
318  if (!$this->isLoaded())
319  {
320  throw new LogicException('No valid image was loaded.');
321  }
322 
323  // Sanitize width.
324  $width = $this->sanitizeWidth($width, $height);
325 
326  // Sanitize height.
327  $height = $this->sanitizeHeight($height, $width);
328 
329  // Autocrop offsets
330  if (is_null($left))
331  {
332  $left = round(($this->getWidth() - $width) / 2);
333  }
334 
335  if (is_null($top))
336  {
337  $top = round(($this->getHeight() - $height) / 2);
338  }
339 
340  // Sanitize left.
341  $left = $this->sanitizeOffset($left);
342 
343  // Sanitize top.
344  $top = $this->sanitizeOffset($top);
345 
346  // Create the new truecolor image handle.
347  $handle = imagecreatetruecolor($width, $height);
348 
349  // Allow transparency for the new image handle.
350  imagealphablending($handle, false);
351  imagesavealpha($handle, true);
352 
353  if ($this->isTransparent())
354  {
355  // Get the transparent color values for the current image.
356  $rgba = imageColorsForIndex($this->handle, imagecolortransparent($this->handle));
357  $color = imageColorAllocate($this->handle, $rgba['red'], $rgba['green'], $rgba['blue']);
358 
359  // Set the transparent color values for the new image.
360  imagecolortransparent($handle, $color);
361  imagefill($handle, 0, 0, $color);
362 
363  imagecopyresized($handle, $this->handle, 0, 0, $left, $top, $width, $height, $width, $height);
364  }
365  else
366  {
367  imagecopyresampled($handle, $this->handle, 0, 0, $left, $top, $width, $height, $width, $height);
368  }
369 
370  // If we are cropping to a new image, create a new JImage object.
371  if ($createNew)
372  {
373  // @codeCoverageIgnoreStart
374  $new = new JImage($handle);
375 
376  return $new;
377 
378  // @codeCoverageIgnoreEnd
379  }
380  // Swap out the current handle for the new image handle.
381  else
382  {
383  // Free the memory from the current handle
384  $this->destroy();
385 
386  $this->handle = $handle;
387 
388  return $this;
389  }
390  }
391 
392  /**
393  * Method to apply a filter to the image by type. Two examples are: grayscale and sketchy.
394  *
395  * @param string $type The name of the image filter to apply.
396  * @param array $options An array of options for the filter.
397  *
398  * @return JImage
399  *
400  * @since 11.3
401  * @see JImageFilter
402  * @throws LogicException
403  */
404  public function filter($type, array $options = array())
405  {
406  // Make sure the resource handle is valid.
407  if (!$this->isLoaded())
408  {
409  throw new LogicException('No valid image was loaded.');
410  }
411 
412  // Get the image filter instance.
413  $filter = $this->getFilterInstance($type);
414 
415  // Execute the image filter.
416  $filter->execute($options);
417 
418  return $this;
419  }
420 
421  /**
422  * Method to get the height of the image in pixels.
423  *
424  * @return integer
425  *
426  * @since 11.3
427  * @throws LogicException
428  */
429  public function getHeight()
430  {
431  // Make sure the resource handle is valid.
432  if (!$this->isLoaded())
433  {
434  throw new LogicException('No valid image was loaded.');
435  }
436 
437  return imagesy($this->handle);
438  }
439 
440  /**
441  * Method to get the width of the image in pixels.
442  *
443  * @return integer
444  *
445  * @since 11.3
446  * @throws LogicException
447  */
448  public function getWidth()
449  {
450  // Make sure the resource handle is valid.
451  if (!$this->isLoaded())
452  {
453  throw new LogicException('No valid image was loaded.');
454  }
455 
456  return imagesx($this->handle);
457  }
458 
459  /**
460  * Method to return the path
461  *
462  * @return string
463  *
464  * @since 11.3
465  */
466  public function getPath()
467  {
468  return $this->path;
469  }
470 
471  /**
472  * Method to determine whether or not an image has been loaded into the object.
473  *
474  * @return boolean
475  *
476  * @since 11.3
477  */
478  public function isLoaded()
479  {
480  // Make sure the resource handle is valid.
481  if (!is_resource($this->handle) || (get_resource_type($this->handle) != 'gd'))
482  {
483  return false;
484  }
485 
486  return true;
487  }
488 
489  /**
490  * Method to determine whether or not the image has transparency.
491  *
492  * @return bool
493  *
494  * @since 11.3
495  * @throws LogicException
496  */
497  public function isTransparent()
498  {
499  // Make sure the resource handle is valid.
500  if (!$this->isLoaded())
501  {
502  throw new LogicException('No valid image was loaded.');
503  }
504 
505  return (imagecolortransparent($this->handle) >= 0);
506  }
507 
508  /**
509  * Method to load a file into the JImage object as the resource.
510  *
511  * @param string $path The filesystem path to load as an image.
512  *
513  * @return void
514  *
515  * @since 11.3
516  * @throws InvalidArgumentException
517  * @throws RuntimeException
518  */
519  public function loadFile($path)
520  {
521  // Destroy the current image handle if it exists
522  $this->destroy();
523 
524  // Make sure the file exists.
525  if (!file_exists($path))
526  {
527  throw new InvalidArgumentException('The image file does not exist.');
528  }
529 
530  // Get the image properties.
531  $properties = self::getImageFileProperties($path);
532 
533  // Attempt to load the image based on the MIME-Type
534  switch ($properties->mime)
535  {
536  case 'image/gif':
537  // Make sure the image type is supported.
538  if (empty(self::$formats[IMAGETYPE_GIF]))
539  {
540  // @codeCoverageIgnoreStart
541  JLog::add('Attempting to load an image of unsupported type GIF.', JLog::ERROR);
542  throw new RuntimeException('Attempting to load an image of unsupported type GIF.');
543 
544  // @codeCoverageIgnoreEnd
545  }
546 
547  // Attempt to create the image handle.
548  $handle = imagecreatefromgif($path);
549 
550  if (!is_resource($handle))
551  {
552  // @codeCoverageIgnoreStart
553  throw new RuntimeException('Unable to process GIF image.');
554 
555  // @codeCoverageIgnoreEnd
556  }
557 
558  $this->handle = $handle;
559  break;
560 
561  case 'image/jpeg':
562  // Make sure the image type is supported.
563  if (empty(self::$formats[IMAGETYPE_JPEG]))
564  {
565  // @codeCoverageIgnoreStart
566  JLog::add('Attempting to load an image of unsupported type JPG.', JLog::ERROR);
567  throw new RuntimeException('Attempting to load an image of unsupported type JPG.');
568 
569  // @codeCoverageIgnoreEnd
570  }
571 
572  // Attempt to create the image handle.
573  $handle = imagecreatefromjpeg($path);
574 
575  if (!is_resource($handle))
576  {
577  // @codeCoverageIgnoreStart
578  throw new RuntimeException('Unable to process JPG image.');
579 
580  // @codeCoverageIgnoreEnd
581  }
582 
583  $this->handle = $handle;
584  break;
585 
586  case 'image/png':
587  // Make sure the image type is supported.
588  if (empty(self::$formats[IMAGETYPE_PNG]))
589  {
590  // @codeCoverageIgnoreStart
591  JLog::add('Attempting to load an image of unsupported type PNG.', JLog::ERROR);
592  throw new RuntimeException('Attempting to load an image of unsupported type PNG.');
593 
594  // @codeCoverageIgnoreEnd
595  }
596 
597  // Attempt to create the image handle.
598  $handle = imagecreatefrompng($path);
599 
600  if (!is_resource($handle))
601  {
602  // @codeCoverageIgnoreStart
603  throw new RuntimeException('Unable to process PNG image.');
604 
605  // @codeCoverageIgnoreEnd
606  }
607 
608  $this->handle = $handle;
609 
610  // Set transparency for non-transparent PNGs.
611  if (!$this->isTransparent())
612  {
613  // Assign to black which is default for transparent PNGs
614  $transparency = imagecolorallocatealpha($handle, 0, 0, 0, 127);
615 
616  imagecolortransparent($handle, $transparency);
617  }
618 
619  break;
620 
621  default:
622  JLog::add('Attempting to load an image of unsupported type: ' . $properties->mime, JLog::ERROR);
623  throw new InvalidArgumentException('Attempting to load an image of unsupported type: ' . $properties->mime);
624  break;
625  }
626 
627  // Set the filesystem path to the source image.
628  $this->path = $path;
629  }
630 
631  /**
632  * Method to resize the current image.
633  *
634  * @param mixed $width The width of the resized image in pixels or a percentage.
635  * @param mixed $height The height of the resized image in pixels or a percentage.
636  * @param boolean $createNew If true the current image will be cloned, resized and returned; else
637  * the current image will be resized and returned.
638  * @param integer $scaleMethod Which method to use for scaling
639  *
640  * @return JImage
641  *
642  * @since 11.3
643  * @throws LogicException
644  */
645  public function resize($width, $height, $createNew = true, $scaleMethod = self::SCALE_INSIDE)
646  {
647  // Make sure the resource handle is valid.
648  if (!$this->isLoaded())
649  {
650  throw new LogicException('No valid image was loaded.');
651  }
652 
653  // Sanitize width.
654  $width = $this->sanitizeWidth($width, $height);
655 
656  // Sanitize height.
657  $height = $this->sanitizeHeight($height, $width);
658 
659  // Prepare the dimensions for the resize operation.
660  $dimensions = $this->prepareDimensions($width, $height, $scaleMethod);
661 
662  // Instantiate offset.
663  $offset = new stdClass;
664  $offset->x = $offset->y = 0;
665 
666  // Center image if needed and create the new truecolor image handle.
667  if ($scaleMethod == self::SCALE_FIT)
668  {
669  // Get the offsets
670  $offset->x = round(($width - $dimensions->width) / 2);
671  $offset->y = round(($height - $dimensions->height) / 2);
672 
673  $handle = imagecreatetruecolor($width, $height);
674 
675  // Make image transparent, otherwise cavas outside initial image would default to black
676  if (!$this->isTransparent())
677  {
678  $transparency = imagecolorAllocateAlpha($this->handle, 0, 0, 0, 127);
679  imagecolorTransparent($this->handle, $transparency);
680  }
681  }
682  else
683  {
684  $handle = imagecreatetruecolor($dimensions->width, $dimensions->height);
685  }
686 
687  // Allow transparency for the new image handle.
688  imagealphablending($handle, false);
689  imagesavealpha($handle, true);
690 
691  if ($this->isTransparent())
692  {
693  // Get the transparent color values for the current image.
694  $rgba = imageColorsForIndex($this->handle, imagecolortransparent($this->handle));
695  $color = imageColorAllocateAlpha($this->handle, $rgba['red'], $rgba['green'], $rgba['blue'], $rgba['alpha']);
696 
697  // Set the transparent color values for the new image.
698  imagecolortransparent($handle, $color);
699  imagefill($handle, 0, 0, $color);
700 
701  imagecopyresized($handle, $this->handle, $offset->x, $offset->y, 0, 0, $dimensions->width, $dimensions->height, $this->getWidth(), $this->getHeight());
702  }
703  else
704  {
705  imagecopyresampled($handle, $this->handle, $offset->x, $offset->y, 0, 0, $dimensions->width, $dimensions->height, $this->getWidth(), $this->getHeight());
706  }
707 
708  // If we are resizing to a new image, create a new JImage object.
709  if ($createNew)
710  {
711  // @codeCoverageIgnoreStart
712  $new = new JImage($handle);
713 
714  return $new;
715 
716  // @codeCoverageIgnoreEnd
717  }
718  // Swap out the current handle for the new image handle.
719  else
720  {
721  // Free the memory from the current handle
722  $this->destroy();
723 
724  $this->handle = $handle;
725 
726  return $this;
727  }
728  }
729 
730  /**
731  * Method to crop an image after resizing it to maintain
732  * proportions without having to do all the set up work.
733  *
734  * @param integer $width The desired width of the image in pixels or a percentage.
735  * @param integer $height The desired height of the image in pixels or a percentage.
736  * @param integer $createNew If true the current image will be cloned, resized, cropped and returned.
737  *
738  * @return object JImage Object for chaining.
739  *
740  * @since 12.3
741  */
742  public function cropResize($width, $height, $createNew = true)
743  {
744  $width = $this->sanitizeWidth($width, $height);
745  $height = $this->sanitizeHeight($height, $width);
746 
747  if (($this->getWidth() / $width) < ($this->getHeight() / $height))
748  {
749  $this->resize($width, 0, false);
750  }
751  else
752  {
753  $this->resize(0, $height, false);
754  }
755 
756  return $this->crop($width, $height, null, null, $createNew);
757  }
758 
759  /**
760  * Method to rotate the current image.
761  *
762  * @param mixed $angle The angle of rotation for the image
763  * @param integer $background The background color to use when areas are added due to rotation
764  * @param boolean $createNew If true the current image will be cloned, rotated and returned; else
765  * the current image will be rotated and returned.
766  *
767  * @return JImage
768  *
769  * @since 11.3
770  * @throws LogicException
771  */
772  public function rotate($angle, $background = -1, $createNew = true)
773  {
774  // Make sure the resource handle is valid.
775  if (!$this->isLoaded())
776  {
777  throw new LogicException('No valid image was loaded.');
778  }
779 
780  // Sanitize input
781  $angle = (float) $angle;
782 
783  // Create the new truecolor image handle.
784  $handle = imagecreatetruecolor($this->getWidth(), $this->getHeight());
785 
786  // Allow transparency for the new image handle.
787  imagealphablending($handle, false);
788  imagesavealpha($handle, true);
789 
790  // Copy the image
791  imagecopy($handle, $this->handle, 0, 0, 0, 0, $this->getWidth(), $this->getHeight());
792 
793  // Rotate the image
794  $handle = imagerotate($handle, $angle, $background);
795 
796  // If we are resizing to a new image, create a new JImage object.
797  if ($createNew)
798  {
799  // @codeCoverageIgnoreStart
800  $new = new JImage($handle);
801 
802  return $new;
803 
804  // @codeCoverageIgnoreEnd
805  }
806  // Swap out the current handle for the new image handle.
807  else
808  {
809  // Free the memory from the current handle
810  $this->destroy();
811 
812  $this->handle = $handle;
813 
814  return $this;
815  }
816  }
817 
818  /**
819  * Method to write the current image out to a file.
820  *
821  * @param string $path The filesystem path to save the image.
822  * @param integer $type The image type to save the file as.
823  * @param array $options The image type options to use in saving the file.
824  *
825  * @return boolean
826  *
827  * @see http://www.php.net/manual/image.constants.php
828  * @since 11.3
829  * @throws LogicException
830  */
831  public function toFile($path, $type = IMAGETYPE_JPEG, array $options = array())
832  {
833  // Make sure the resource handle is valid.
834  if (!$this->isLoaded())
835  {
836  throw new LogicException('No valid image was loaded.');
837  }
838 
839  switch ($type)
840  {
841  case IMAGETYPE_GIF:
842  return imagegif($this->handle, $path);
843  break;
844 
845  case IMAGETYPE_PNG:
846  return imagepng($this->handle, $path, (array_key_exists('quality', $options)) ? $options['quality'] : 0);
847  break;
848 
849  case IMAGETYPE_JPEG:
850  default:
851  return imagejpeg($this->handle, $path, (array_key_exists('quality', $options)) ? $options['quality'] : 100);
852  }
853  }
854 
855  /**
856  * Method to get an image filter instance of a specified type.
857  *
858  * @param string $type The image filter type to get.
859  *
860  * @return JImageFilter
861  *
862  * @since 11.3
863  * @throws RuntimeException
864  */
865  protected function getFilterInstance($type)
866  {
867  // Sanitize the filter type.
868  $type = strtolower(preg_replace('#[^A-Z0-9_]#i', '', $type));
869 
870  // Verify that the filter type exists.
871  $className = 'JImageFilter' . ucfirst($type);
872 
873  if (!class_exists($className))
874  {
875  JLog::add('The ' . ucfirst($type) . ' image filter is not available.', JLog::ERROR);
876  throw new RuntimeException('The ' . ucfirst($type) . ' image filter is not available.');
877  }
878 
879  // Instantiate the filter object.
880  $instance = new $className($this->handle);
881 
882  // Verify that the filter type is valid.
883  if (!($instance instanceof JImageFilter))
884  {
885  // @codeCoverageIgnoreStart
886  JLog::add('The ' . ucfirst($type) . ' image filter is not valid.', JLog::ERROR);
887  throw new RuntimeException('The ' . ucfirst($type) . ' image filter is not valid.');
888 
889  // @codeCoverageIgnoreEnd
890  }
891 
892  return $instance;
893  }
894 
895  /**
896  * Method to get the new dimensions for a resized image.
897  *
898  * @param integer $width The width of the resized image in pixels.
899  * @param integer $height The height of the resized image in pixels.
900  * @param integer $scaleMethod The method to use for scaling
901  *
902  * @return stdClass
903  *
904  * @since 11.3
905  * @throws InvalidArgumentException If width, height or both given as zero
906  */
907  protected function prepareDimensions($width, $height, $scaleMethod)
908  {
909  // Instantiate variables.
910  $dimensions = new stdClass;
911 
912  switch ($scaleMethod)
913  {
914  case self::SCALE_FILL:
915  $dimensions->width = (int) round($width);
916  $dimensions->height = (int) round($height);
917  break;
918 
919  case self::SCALE_INSIDE:
920  case self::SCALE_OUTSIDE:
921  case self::SCALE_FIT:
922  $rx = ($width > 0) ? ($this->getWidth() / $width) : 0;
923  $ry = ($height > 0) ? ($this->getHeight() / $height) : 0;
924 
925  if ($scaleMethod != self::SCALE_OUTSIDE)
926  {
927  $ratio = max($rx, $ry);
928  }
929  else
930  {
931  $ratio = min($rx, $ry);
932  }
933 
934  $dimensions->width = (int) round($this->getWidth() / $ratio);
935  $dimensions->height = (int) round($this->getHeight() / $ratio);
936  break;
937 
938  default:
939  throw new InvalidArgumentException('Invalid scale method.');
940  break;
941  }
942 
943  return $dimensions;
944  }
945 
946  /**
947  * Method to sanitize a height value.
948  *
949  * @param mixed $height The input height value to sanitize.
950  * @param mixed $width The input width value for reference.
951  *
952  * @return integer
953  *
954  * @since 11.3
955  */
956  protected function sanitizeHeight($height, $width)
957  {
958  // If no height was given we will assume it is a square and use the width.
959  $height = ($height === null) ? $width : $height;
960 
961  // If we were given a percentage, calculate the integer value.
962  if (preg_match('/^[0-9]+(\.[0-9]+)?\%$/', $height))
963  {
964  $height = (int) round($this->getHeight() * (float) str_replace('%', '', $height) / 100);
965  }
966  // Else do some rounding so we come out with a sane integer value.
967  else
968  {
969  $height = (int) round((float) $height);
970  }
971 
972  return $height;
973  }
974 
975  /**
976  * Method to sanitize an offset value like left or top.
977  *
978  * @param mixed $offset An offset value.
979  *
980  * @return integer
981  *
982  * @since 11.3
983  */
984  protected function sanitizeOffset($offset)
985  {
986  return (int) round((float) $offset);
987  }
988 
989  /**
990  * Method to sanitize a width value.
991  *
992  * @param mixed $width The input width value to sanitize.
993  * @param mixed $height The input height value for reference.
994  *
995  * @return integer
996  *
997  * @since 11.3
998  */
999  protected function sanitizeWidth($width, $height)
1000  {
1001  // If no width was given we will assume it is a square and use the height.
1002  $width = ($width === null) ? $height : $width;
1003 
1004  // If we were given a percentage, calculate the integer value.
1005  if (preg_match('/^[0-9]+(\.[0-9]+)?\%$/', $width))
1006  {
1007  $width = (int) round($this->getWidth() * (float) str_replace('%', '', $width) / 100);
1008  }
1009  // Else do some rounding so we come out with a sane integer value.
1010  else
1011  {
1012  $width = (int) round((float) $width);
1013  }
1014 
1015  return $width;
1016  }
1017 
1018  /**
1019  * Method to destroy an image handle and
1020  * free the memory associated with the handle
1021  *
1022  * @return boolean True on success, false on failure or if no image is loaded
1023  *
1024  * @since 12.3
1025  */
1026  public function destroy()
1027  {
1028  if ($this->isLoaded())
1029  {
1030  return imagedestroy($this->handle);
1031  }
1032 
1033  return false;
1034  }
1035 
1036  /**
1037  * Method to call the destroy() method one last time
1038  * to free any memory when the object is unset
1039  *
1040  * @see JImage::destroy()
1041  * @since 12.3
1042  */
1043  public function __destruct()
1044  {
1045  $this->destroy();
1046  }
1047 }