Elgg  Version 6.3
ImageService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
8 use Elgg\Traits\Loggable;
9 use Imagine\Filter\Basic\Autorotate;
10 use Imagine\Image\Box;
11 use Imagine\Image\ImagineInterface;
12 use Imagine\Image\Point;
13 
20 class ImageService {
21 
22  use Loggable;
23 
24  const JPEG_QUALITY = 75;
25  const WEBP_QUALITY = 75;
26 
30  protected $imagine;
31 
38  public function __construct(protected Config $config, protected MimeTypeService $mimetype) {
39 
40  switch ($config->image_processor) {
41  case 'imagick':
42  if (extension_loaded('imagick')) {
43  $this->imagine = new \Imagine\Imagick\Imagine();
44  break;
45  }
46 
47  // fallback to GD if Imagick is not loaded
48  default:
49  // default use GD
50  $this->imagine = new \Imagine\Gd\Imagine();
51  break;
52  }
53  }
54 
83  public function resize(string $source, ?string $destination = null, array $params = []): bool {
84 
85  $destination = $destination ?? $source;
86 
87  try {
88  $resize_params = $this->normalizeResizeParameters($source, $params);
89 
90  $image = $this->imagine->open($source);
91 
92  $max_width = (int) elgg_extract('w', $resize_params);
93  $max_height = (int) elgg_extract('h', $resize_params);
94 
95  $x1 = (int) elgg_extract('x1', $resize_params, 0);
96  $y1 = (int) elgg_extract('y1', $resize_params, 0);
97  $x2 = (int) elgg_extract('x2', $resize_params, 0);
98  $y2 = (int) elgg_extract('y2', $resize_params, 0);
99 
100  if ($x2 > $x1 && $y2 > $y1) {
101  $crop_start = new Point($x1, $y1);
102  $crop_size = new Box($x2 - $x1, $y2 - $y1);
103  $image->crop($crop_start, $crop_size);
104  }
105 
106  $target_size = new Box($max_width, $max_height);
107  $image->resize($target_size);
108 
109  // create new canvas with a background (default: white)
110  $background_color = elgg_extract('background_color', $params, 'ffffff');
111  $thumbnail = $this->imagine->create($image->getSize(), $image->palette()->color($background_color));
112  $thumbnail->paste($image, new Point(0, 0));
113 
114  if (pathinfo($destination, PATHINFO_EXTENSION) === 'webp') {
115  $options = [
116  'webp_quality' => elgg_extract('webp_quality', $params, self::WEBP_QUALITY),
117  ];
118  } else {
119  $options = [
120  'format' => $this->getFileFormat($source, $params),
121  'jpeg_quality' => elgg_extract('jpeg_quality', $params, self::JPEG_QUALITY),
122  ];
123  }
124 
125  $thumbnail->save($destination, $options);
126 
127  unset($image);
128  unset($thumbnail);
129  } catch (\Exception $ex) {
130  $this->getLogger()->error($ex);
131 
132  return false;
133  }
134 
135  return true;
136  }
137 
145  public function fixOrientation($filename) {
146  try {
147  $image = $this->imagine->open($filename);
148  $metadata = $image->metadata();
149  if (!isset($metadata['ifd0.Orientation'])) {
150  // no need to perform an orientation fix
151  return true;
152  }
153 
154  $autorotate = new Autorotate();
155  $autorotate->apply($image)->save($filename);
156 
157  $image->strip()->save($filename);
158 
159  return true;
160  } catch (\Exception $ex) {
161  $this->getLogger()->notice($ex);
162  }
163 
164  return false;
165  }
166 
182  public function normalizeResizeParameters(string $source, array $params = []): array {
183 
184  $image = $this->imagine->open($source);
185 
186  $width = $image->getSize()->getWidth();
187  $height = $image->getSize()->getHeight();
188 
189  $max_width = (int) elgg_extract('w', $params, 100, false);
190  $max_height = (int) elgg_extract('h', $params, 100, false);
191  if (!$max_height || !$max_width) {
192  throw new InvalidArgumentException('Resize width and height parameters are required');
193  }
194 
195  $square = elgg_extract('square', $params, false);
196  $upscale = elgg_extract('upscale', $params, false);
197 
198  $x1 = (int) elgg_extract('x1', $params, 0);
199  $y1 = (int) elgg_extract('y1', $params, 0);
200  $x2 = (int) elgg_extract('x2', $params, 0);
201  $y2 = (int) elgg_extract('y2', $params, 0);
202 
203  $cropping_mode = $x1 || $y1 || $x2 || $y2;
204 
205  if ($cropping_mode) {
206  $crop_width = $x2 - $x1;
207  $crop_height = $y2 - $y1;
208  if ($crop_width <= 0 || $crop_height <= 0 || $crop_width > $width || $crop_height > $height) {
209  throw new RangeException("Coordinates [$x1, $y1], [$x2, $y2] are invalid for image cropping");
210  }
211  } else {
212  // everything selected if no crop parameters
213  $crop_width = $width;
214  $crop_height = $height;
215  }
216 
217  // determine cropping offsets
218  if ($square) {
219  // asking for a square image back
220 
221  // size of the new square image
222  $max_width = min($max_width, $max_height);
223  $max_height = $max_width;
224 
225  // find the largest square that fits within the selected region
226  $crop_width = min($crop_width, $crop_height);
227  $crop_height = $crop_width;
228 
229  if (!$cropping_mode) {
230  // place square region in the center
231  $x1 = floor(($width - $crop_width) / 2);
232  $y1 = floor(($height - $crop_height) / 2);
233  }
234  } else {
235  // maintain aspect ratio of original image/crop
236  if ($crop_height / $max_height > $crop_width / $max_width) {
237  $max_width = floor($max_height * $crop_width / $crop_height);
238  } else {
239  $max_height = floor($max_width * $crop_height / $crop_width);
240  }
241  }
242 
243  if (!$upscale && ($crop_height < $max_height || $crop_width < $max_width)) {
244  // we cannot upscale and selected area is too small so we decrease size of returned image
245  $max_height = $crop_height;
246  $max_width = $crop_width;
247  }
248 
249  return [
250  'w' => $max_width,
251  'h' => $max_height,
252  'x1' => $x1,
253  'y1' => $y1,
254  'x2' => $x1 + $crop_width,
255  'y2' => $y1 + $crop_height,
256  'square' => $square,
257  'upscale' => $upscale,
258  ];
259  }
260 
270  protected function getFileFormat($filename, $params) {
271 
272  $accepted_formats = [
273  'image/jpeg' => 'jpeg',
274  'image/pjpeg' => 'jpeg',
275  'image/png' => 'png',
276  'image/x-png' => 'png',
277  'image/gif' => 'gif',
278  'image/vnd.wap.wbmp' => 'wbmp',
279  'image/x‑xbitmap' => 'xbm',
280  'image/x‑xbm' => 'xbm',
281  ];
282 
283  // was a valid output format supplied
284  $format = elgg_extract('format', $params);
285  if (in_array($format, $accepted_formats)) {
286  return $format;
287  }
288 
289  try {
290  return elgg_extract($this->mimetype->getMimeType($filename), $accepted_formats);
291  } catch (InvalidArgumentException $e) {
292  $this->getLogger()->warning($e);
293  }
294  }
295 
301  public function hasWebPSupport(): bool {
302  if ($this->config->webp_enabled === false) {
303  return false;
304  }
305 
306  if ($this->imagine instanceof \Imagine\Imagick\Imagine) {
307  return !empty(\Imagick::queryformats('WEBP*'));
308  } elseif ($this->imagine instanceof \Imagine\Gd\Imagine) {
309  return (bool) elgg_extract('WebP Support', gd_info(), false);
310  }
311 
312  return false;
313  }
314 }
$params
Saves global plugin settings.
Definition: save.php:13
$source
Exception thrown if an argument is not of the expected type.
Exception thrown to indicate range errors during program execution.
Public service related to MIME type detection.
Image manipulation service.
normalizeResizeParameters(string $source, array $params=[])
Calculate the parameters for resizing an image.
__construct(protected Config $config, protected MimeTypeService $mimetype)
Constructor.
getFileFormat($filename, $params)
Determine the image file format, this is needed for correct resizing.
hasWebPSupport()
Checks if imagine has WebP support.
fixOrientation($filename)
If needed the image will be rotated based on orientation information.
resize(string $source, ?string $destination=null, array $params=[])
Crop and resize an image.
if($who_can_change_language==='nobody') elseif($who_can_change_language==='admin_only' &&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
$image
Definition: image_block.php:26
if($item instanceof \ElggEntity) elseif($item instanceof \ElggRiverItem) elseif($item instanceof \ElggRelationship) elseif(is_callable([ $item, 'getType']))
Definition: item.php:48
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:240
$format
Definition: date.php:37
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
$metadata
Output annotation metadata.
Definition: metadata.php:9