ILIAS  trunk Revision v11.0_alpha-1731-gff9cd7e2bd3
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
ImageConverter.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
26 
32 {
34 
35  protected const STATUS_OK = 1;
36  protected const STATUS_FAILED = 2;
37  protected const STATUS_UNKNOWN = 4;
38 
39  protected const RESOLUTION = 72;
40  protected const RESOLUTION_FACTOR = self::RESOLUTION / 72;
41 
42  protected int $status = self::STATUS_UNKNOWN;
44  protected ?\Throwable $throwable = null;
45  protected ?string $requested_background = null;
46  protected \Imagick $image;
47 
48  public function __construct(
49  protected ImageConversionOptions $conversion_options,
50  protected ImageOutputOptions $output_options,
51  protected FileStream $stream
52  ) {
53  $this->image = new \Imagick();
54  $this->convert();
55  }
56 
57  private function convert(): void
58  {
59  try {
60  $this->handleBackgroundColor();
61  $this->readInputStream();
62  $this->handleFormatAndQuality();
63  $this->handleImageDimension();
64  $this->buildOutputStream();
65  } catch (\Throwable $t) {
66  $this->status = self::STATUS_FAILED;
67  $this->throwable = $t;
68  if ($this->conversion_options->throwOnError()) {
69  throw $t;
70  }
71  }
72  }
73 
74 
75  protected function handleImageDimension(): void
76  {
77  $requested_width = $this->conversion_options->getWidth();
78  $requested_height = $this->conversion_options->getHeight();
79  $original_image_width = $this->image->getImageWidth();
80  $original_image_height = $this->image->getImageHeight();
81 
82  switch ($this->conversion_options->getDimensionMode()) {
83  default:
85  // no resizing
86  return;
88  if ($this->conversion_options->hasCrop()) {
89  $final_height = $requested_height;
90  $final_width = $requested_width;
91  } else {
92  // this is a special case, where we want to fit the image into the given dimensions and
93  // Imagick knows the thumbnail method for that
94  $this->doThumbnail(
95  $requested_width,
96  $requested_height
97  );
98  return;
99  }
100  break;
102  // by width and height
103  if ($requested_width > 0 && $requested_height > 0) {
104  $final_width = $requested_width;
105  $final_height = $requested_height;
106  } else {
107  throw new \InvalidArgumentException('Dimension Mode does not match the given width/height');
108  }
109  break;
111  // by height
112  if ($requested_width === null && $requested_height > 0) {
113  $ratio = $original_image_height / $requested_height;
114  $final_width = intval($original_image_width / $ratio);
115  $final_height = $requested_height;
116  $l = 1;
117  } else {
118  throw new \InvalidArgumentException('Dimension Mode does not match the given width/height');
119  }
120  break;
122  // by width
123  if ($requested_width > 0 && $requested_height === null) {
124  $ratio = $original_image_width / $requested_width;
125  $final_width = $requested_width;
126  $final_height = intval($original_image_height / $ratio);
127  } else {
128  throw new \InvalidArgumentException('Dimension Mode does not match the given width/height');
129  }
130  break;
132  // by none of them
133  if ($requested_width === null && $requested_height === null) {
134  $final_width = $original_image_width;
135  $final_height = $original_image_height;
136  } else {
137  throw new \InvalidArgumentException('Dimension Mode does not match the given width/height');
138  }
139  break;
140  }
141 
142  if ($this->conversion_options->hasCrop()) {
143  $this->doCrop(
144  $final_width,
145  $final_height
146  );
147  } else {
148  $this->doResize(
149  $final_width,
150  $final_height
151  );
152  }
153  }
154 
155 
156  protected function buildOutputStream(): void
157  {
158  if ($this->conversion_options->getOutputPath() === null) {
159  $this->output_stream = Streams::ofString($this->image->getImageBlob());
160  } else {
161  $this->image->writeImage($this->conversion_options->getOutputPath());
162  $this->output_stream = Streams::ofResource(fopen($this->conversion_options->getOutputPath(), 'rb'));
163  }
164 
165  $this->output_stream->rewind();
166 
167  $this->status = self::STATUS_OK;
168  }
169 
170  protected function handleFormatAndQuality(): void
171  {
172  $this->image->setImageResolution(
173  self::RESOLUTION,
174  self::RESOLUTION
175  );
176  // High density images do not support Resampling, cache to small. we deactivate this
177  /*$this->image->resampleImage(
178  self::RESOLUTION,
179  self::RESOLUTION,
180  \Imagick::FILTER_LANCZOS,
181  1
182  );*/
183  $quality = $this->output_options->getQuality();
184 
185  // if $this->output_options->getFormat() is 'keep', we map it to the original format
186  if ($this->output_options->getFormat() === ImageOutputOptions::FORMAT_KEEP) {
187  try {
188  $this->output_options = $this->output_options->withFormat(strtolower($this->image->getImageFormat()));
189  } catch (\InvalidArgumentException) {
190  }
191  }
192 
193  switch ($this->output_options->getFormat()) {
195  $this->image->setImageFormat('webp');
196  $this->image->setImageAlphaChannel(\Imagick::ALPHACHANNEL_REMOVE);
197  $this->image = $this->image->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
198  if ($quality === 0) {
199  $this->image->setOption('webp:lossless', 'false');
200  }
201  if ($quality === 100) {
202  $this->image->setOption('webp:lossless', 'true');
203  }
204  break;
206  $this->image->setImageFormat('jpeg');
207  $this->image->setImageAlphaChannel(\Imagick::ALPHACHANNEL_REMOVE);
208  $this->image = $this->image->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
209  $this->image->setImageCompression(\Imagick::COMPRESSION_JPEG);
210  $this->image->setImageCompressionQuality($quality);
211  break;
213  $png_compression_level = round($quality / 100 * 9, 0);
214  if ($this->requested_background !== null && $this->requested_background !== 'transparent') {
215  $this->image->setImageAlphaChannel(\Imagick::ALPHACHANNEL_REMOVE);
216  } else {
217  $this->image->setImageAlphaChannel(\Imagick::ALPHACHANNEL_ACTIVATE);
218  }
219  $this->image->setImageFormat('png');
220  $this->image->setOption('png:compression-level', (string) $png_compression_level);
221  break;
222  }
223  $this->image->stripImage();
224  }
225 
226  protected function handleBackgroundColor(): void
227  {
228  $this->requested_background = $this->conversion_options->getBackgroundColor();
229  if ($this->output_options->getFormat(
230  ) === ImageOutputOptions::FORMAT_JPG && $this->requested_background === null) {
231  $this->requested_background = '#FFFFFF';
232  }
233  if ($this->output_options->getFormat(
234  ) === ImageOutputOptions::FORMAT_PNG && $this->requested_background === null) {
235  $this->requested_background = 'transparent';
236  }
237  if ($this->requested_background !== null) {
238  $this->image->setBackgroundColor(new \ImagickPixel($this->requested_background));
239  }
240  }
241 
242  protected function readInputStream(): void
243  {
244  if ($this->conversion_options->makeTemporaryFiles()) {
245  $this->stream = $this->maybeSafeToTempStream($this->stream);
246  }
247  $this->stream->rewind();
248  $this->image->readImageFile($this->stream->detach());
249  }
250 
251 
252  public function isOK(): bool
253  {
254  return $this->status === self::STATUS_OK;
255  }
256 
257  public function getThrowableIfAny(): ?\Throwable
258  {
259  return $this->throwable;
260  }
261 
262 
263  public function getStream(): FileStream
264  {
265  return $this->output_stream;
266  }
267 
268  private function factoredResolution(int $initial): int
269  {
270  return intval(round($initial * self::RESOLUTION_FACTOR, 0));
271  }
272 
273  protected function doCrop(int $width, int $height): void
274  {
275  $this->image->setGravity(\Imagick::GRAVITY_CENTER);
276  $this->image->cropThumbnailImage(
277  $this->factoredResolution($width),
278  $this->factoredResolution($height)
279  );
280  }
281 
282  protected function doResize(int $width, int $height): void
283  {
284  $this->image->resizeImage(
285  $this->factoredResolution($width),
286  $this->factoredResolution($height),
287  \Imagick::FILTER_LANCZOS,
288  1
289  );
290  }
291 
292  protected function doThumbnail(int $width, int $height): void
293  {
294  $this->image->thumbnailImage(
295  $this->factoredResolution($width),
296  $this->factoredResolution($height),
297  true,
298  !$this->conversion_options->keepAspectRatio(),
299  );
300  }
301 }
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
__construct(protected ImageConversionOptions $conversion_options, protected ImageOutputOptions $output_options, protected FileStream $stream)
The base interface for all filesystem streams.
Definition: FileStream.php:31