Elgg  Version 4.x
ImageService.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
12 
19 class ImageService {
20 
21  use Loggable;
22 
23  const JPEG_QUALITY = 75;
24  const WEBP_QUALITY = 75;
25 
29  protected $imagine;
30 
34  protected $config;
35 
39  protected $mimetype;
40 
47  public function __construct(Config $config, MimeTypeService $mimetype) {
48 
49  switch ($config->image_processor) {
50  case 'imagick':
51  if (extension_loaded('imagick')) {
52  $this->imagine = new \Imagine\Imagick\Imagine();
53  break;
54  }
55  default:
56  // default use GD
57  $this->imagine = new \Imagine\Gd\Imagine();
58  break;
59  }
60 
61  $this->config = $config;
62  $this->mimetype = $mimetype;
63  }
64 
93  public function resize($source, $destination = null, array $params = []) {
94 
95  if (!isset($destination)) {
96  $destination = $source;
97  }
98 
99  try {
100  $image = $this->imagine->open($source);
101 
102  $width = $image->getSize()->getWidth();
103  $height = $image->getSize()->getHeight();
104 
105  $resize_params = $this->normalizeResizeParameters($width, $height, $params);
106 
107  $max_width = (int) elgg_extract('w', $resize_params);
108  $max_height = (int) elgg_extract('h', $resize_params);
109 
110  $x1 = (int) elgg_extract('x1', $resize_params, 0);
111  $y1 = (int) elgg_extract('y1', $resize_params, 0);
112  $x2 = (int) elgg_extract('x2', $resize_params, 0);
113  $y2 = (int) elgg_extract('y2', $resize_params, 0);
114 
115  if ($x2 > $x1 && $y2 > $y1) {
116  $crop_start = new Point($x1, $y1);
117  $crop_size = new Box($x2 - $x1, $y2 - $y1);
118  $image->crop($crop_start, $crop_size);
119  }
120 
121  $target_size = new Box($max_width, $max_height);
122  $thumbnail = $image->resize($target_size);
123 
124  if (pathinfo($destination, PATHINFO_EXTENSION) === 'webp') {
125  $options = [
126  'webp_quality' => elgg_extract('webp_quality', $params, self::WEBP_QUALITY),
127  ];
128  } else {
129  $options = [
130  'format' => $this->getFileFormat($source, $params),
131  'jpeg_quality' => elgg_extract('jpeg_quality', $params, self::JPEG_QUALITY),
132  ];
133  }
134 
135  $thumbnail->save($destination, $options);
136 
137  unset($image);
138  unset($thumbnail);
139  } catch (\Exception $ex) {
140  $this->getLogger()->error($ex);
141 
142  return false;
143  }
144 
145  return true;
146  }
147 
155  public function fixOrientation($filename) {
156  try {
157  $image = $this->imagine->open($filename);
158  $metadata = $image->metadata();
159  if (!isset($metadata['ifd0.Orientation'])) {
160  // no need to perform an orientation fix
161  return true;
162  }
163 
164  $autorotate = new Autorotate();
165  $autorotate->apply($image)->save($filename);
166 
167  $image->strip()->save($filename);
168 
169  return true;
170  } catch (\Exception $ex) {
171  $this->getLogger()->notice($ex);
172  }
173 
174  return false;
175  }
176 
192  public function normalizeResizeParameters($width, $height, array $params = []) {
193 
194  $max_width = (int) elgg_extract('w', $params, 100, false);
195  $max_height = (int) elgg_extract('h', $params, 100, false);
196  if (!$max_height || !$max_width) {
197  throw new \LogicException("Resize width and height parameters are required");
198  }
199 
200  $square = elgg_extract('square', $params, false);
201  $upscale = elgg_extract('upscale', $params, false);
202 
203  $x1 = (int) elgg_extract('x1', $params, 0);
204  $y1 = (int) elgg_extract('y1', $params, 0);
205  $x2 = (int) elgg_extract('x2', $params, 0);
206  $y2 = (int) elgg_extract('y2', $params, 0);
207 
208  $cropping_mode = $x1 || $y1 || $x2 || $y2;
209 
210  if ($cropping_mode) {
211  $crop_width = $x2 - $x1;
212  $crop_height = $y2 - $y1;
213  if ($crop_width <= 0 || $crop_height <= 0 || $crop_width > $width || $crop_height > $height) {
214  throw new \LogicException("Coordinates [$x1, $y1], [$x2, $y2] are invalid for image cropping");
215  }
216  } else {
217  // everything selected if no crop parameters
218  $crop_width = $width;
219  $crop_height = $height;
220  }
221 
222  // determine cropping offsets
223  if ($square) {
224  // asking for a square image back
225 
226  // size of the new square image
227  $max_width = $max_height = min($max_width, $max_height);
228 
229  // find largest square that fits within the selected region
230  $crop_width = $crop_height = min($crop_width, $crop_height);
231 
232  if (!$cropping_mode) {
233  // place square region in the center
234  $x1 = floor(($width - $crop_width) / 2);
235  $y1 = floor(($height - $crop_height) / 2);
236  }
237  } else {
238  // maintain aspect ratio of original image/crop
239  if ($crop_height / $max_height > $crop_width / $max_width) {
240  $max_width = floor($max_height * $crop_width / $crop_height);
241  } else {
242  $max_height = floor($max_width * $crop_height / $crop_width);
243  }
244  }
245 
246  if (!$upscale && ($crop_height < $max_height || $crop_width < $max_width)) {
247  // we cannot upscale and selected area is too small so we decrease size of returned image
248  $max_height = $crop_height;
249  $max_width = $crop_width;
250  }
251 
252  return [
253  'w' => $max_width,
254  'h' => $max_height,
255  'x1' => $x1,
256  'y1' => $y1,
257  'x2' => $x1 + $crop_width,
258  'y2' => $y1 + $crop_height,
259  'square' => $square,
260  'upscale' => $upscale,
261  ];
262  }
263 
273  protected function getFileFormat($filename, $params) {
274 
275  $accepted_formats = [
276  'image/jpeg' => 'jpeg',
277  'image/pjpeg' => 'jpeg',
278  'image/png' => 'png',
279  'image/x-png' => 'png',
280  'image/gif' => 'gif',
281  'image/vnd.wap.wbmp' => 'wbmp',
282  'image/x‑xbitmap' => 'xbm',
283  'image/x‑xbm' => 'xbm',
284  ];
285 
286  // was a valid output format supplied
287  $format = elgg_extract('format', $params);
288  if (in_array($format, $accepted_formats)) {
289  return $format;
290  }
291 
292  try {
293  return elgg_extract($this->mimetype->getMimeType($filename), $accepted_formats);
294  } catch (InvalidArgumentException $e) {
295  $this->getLogger()->warning($e);
296  }
297  }
298 
304  public function hasWebPSupport(): bool {
305  if ($this->config->webp_enabled === false) {
306  return false;
307  }
308 
309  if ($this->imagine instanceof \Imagine\Imagick\Imagine) {
310  return !empty(\Imagick::queryformats('WEBP*'));
311  } elseif ($this->imagine instanceof \Imagine\Gd\Imagine) {
312  return (bool) elgg_extract('WebP Support', gd_info(), false);
313  }
314 
315  return false;
316  }
317 }
$format
Definition: date.php:39
__construct(Config $config, MimeTypeService $mimetype)
Constructor.
$source
Base exception of invalid argument exceptions in the Elgg system.
$params
Saves global plugin settings.
Definition: save.php:13
$options
Elgg admin footer.
Definition: footer.php:6
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
fixOrientation($filename)
If needed the image will be rotated based on orientation information.
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
$image
Definition: image_block.php:25
Image manipulation service.
getFileFormat($filename, $params)
Determine the image file format, this is needed for correct resizing.
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:686
getLogger()
Returns logger.
Definition: Loggable.php:37
if($item instanceof\ElggEntity) elseif($item instanceof\ElggRiverItem) elseif($item instanceof ElggRelationship) elseif(is_callable([$item, 'getType']))
Definition: item.php:48
$metadata
Output annotation metadata.
Definition: metadata.php:9
hasWebPSupport()
Checks if imagine has WebP support.
resize($source, $destination=null, array $params=[])
Crop and resize an image.
$filename
Public service related to MIME type detection.
normalizeResizeParameters($width, $height, array $params=[])
Calculate the parameters for resizing an image.