ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
ImageConversionTest.php
Go to the documentation of this file.
1 <?php
2 
19 namespace ILIAS\Filesystem\Util;
20 
29 
33 class ImageConversionTest extends TestCase
34 {
36 
37  protected const BY_WIDTH_FINAL = 256;
38  protected const BY_HEIGHT_FINAL = 756;
39  protected const W = 'width';
40  protected const H = 'height';
41  protected const IMAGE_JPEG = 'image/jpeg';
42  protected const IMAGE_PNG = 'image/png';
43  protected const IMAGE_WEBP = 'image/webp';
44  protected Images $images;
45 
46  protected function setUp(): void
47  {
48  $this->checkImagick();
49  $this->images = new Images(
50  true,
51  );
52  }
53 
54  public function testImageThumbnailActualImage(): void
55  {
56  $img = __DIR__ . '/img/robot.jpg';
57  $this->assertFileExists($img);
58  $getimagesize = getimagesize($img);
59  $original_width = $getimagesize[0]; // should be 600
60  $original_height = $getimagesize[1]; // should be 800
61  $this->assertEquals(600, $original_width);
62  $this->assertEquals(800, $original_height);
63 
64  // make tumbnail
65  $original_stream = Streams::ofResource(fopen($img, 'rb'));
66 
67  $thumbnail_converter = $this->images->thumbnail(
68  $original_stream,
69  100
70  );
71  $this->assertTrue($thumbnail_converter->isOK());
72  $this->assertNull($thumbnail_converter->getThrowableIfAny());
73  $converted_stream = $thumbnail_converter->getStream();
74 
75  $getimagesizefromstring = getimagesizefromstring((string) $converted_stream);
76 
77  $this->assertEquals(75, $getimagesizefromstring[0]); // width
78  $this->assertEquals(100, $getimagesizefromstring[1]); // height
79  }
80 
81  public function testImageSquareActualImage(): void
82  {
83  $img = __DIR__ . '/img/robot.jpg';
84  $this->assertFileExists($img);
85  $getimagesize = getimagesize($img);
86  $original_width = $getimagesize[0]; // should be 600
87  $original_height = $getimagesize[1]; // should be 800
88  $this->assertEquals(600, $original_width);
89  $this->assertEquals(800, $original_height);
90 
91  // make tumbnail
92  $original_stream = Streams::ofResource(fopen($img, 'rb'));
93 
94  $thumbnail_converter = $this->images->croppedSquare(
95  $original_stream,
96  200
97  );
98  $this->assertTrue($thumbnail_converter->isOK());
99  $this->assertNull($thumbnail_converter->getThrowableIfAny());
100 
101  $getimagesizefromstring = $this->getImageSizeFromStream($thumbnail_converter->getStream());
102 
103  $this->assertEquals(200, $getimagesizefromstring[self::W]);
104  $this->assertEquals(200, $getimagesizefromstring[self::H]);
105  }
106 
107  public static function getImageSizesByWidth(): array
108  {
109  return [
110  [400, 300, self::BY_WIDTH_FINAL, 192],
111  [300, 400, self::BY_WIDTH_FINAL, 341],
112  [543, 431, self::BY_WIDTH_FINAL, 203],
113  [200, 200, self::BY_WIDTH_FINAL, 256],
114  ];
115  }
116 
120  public function testResizeToFitWidth(
121  int $width,
122  int $height,
123  int $final_width,
124  int $final_height
125  ): void {
126  $stream = $this->createTestImageStream($width, $height);
127  $dimensions = $this->getImageSizeFromStream($stream);
128  $this->assertEquals($width, $dimensions[self::W]);
129  $this->assertEquals($height, $dimensions[self::H]);
130 
131  // resize to fit width
132  $resized = $this->images->resizeByWidth($stream, self::BY_WIDTH_FINAL);
133  $this->assertTrue($resized->isOK());
134  $new_dimensions = $this->getImageSizeFromStream($resized->getStream());
135 
136  $this->assertEquals($final_width, $new_dimensions[self::W]);
137  $this->assertEquals($final_height, $new_dimensions[self::H]);
138 
139  // check aspect ratio
140  $this->assertEquals(
141  round($width > $height),
142  round($final_width > $final_height)
143  );
144  $this->assertEquals(
145  round($width / $height),
146  round($new_dimensions[self::W] / $new_dimensions[self::H])
147  );
148  $this->assertEquals(
149  $width > $height,
150  $width > $height
151  );
152  $this->assertEquals(
153  $width > $height,
154  $new_dimensions[self::W] > $new_dimensions[self::H]
155  );
156  }
157 
158  public static function getImageSizesByHeight(): array
159  {
160  return [
161  [400, 300, self::BY_HEIGHT_FINAL, 1008],
162  [300, 400, self::BY_HEIGHT_FINAL, 567],
163  [200, 200, self::BY_HEIGHT_FINAL, 756],
164  [248, 845, self::BY_HEIGHT_FINAL, 221],
165  ];
166  }
167 
168 
172  public function testResizeToFitHeight(
173  int $width,
174  int $height,
175  int $final_height,
176  int $final_width
177  ): void {
178  $stream = $this->createTestImageStream($width, $height);
179  $dimensions = $this->getImageSizeFromStream($stream);
180  $this->assertEquals($width, $dimensions[self::W]);
181  $this->assertEquals($height, $dimensions[self::H]);
182 
183  // resize to fit
184  $resized = $this->images->resizeByHeight($stream, self::BY_HEIGHT_FINAL);
185  $this->assertTrue($resized->isOK());
186  $new_dimensions = $this->getImageSizeFromStream($resized->getStream());
187 
188  $this->assertEquals($final_width, $new_dimensions[self::W]);
189  $this->assertEquals($final_height, $new_dimensions[self::H]);
190 
191  // check aspect ratio
192  $this->assertEquals(
193  round($width > $height),
194  round($final_width > $final_height)
195  );
196  $this->assertEquals(
197  round($width / $height),
198  round($new_dimensions[self::W] / $new_dimensions[self::H])
199  );
200  $this->assertEquals(
201  $width > $height,
202  $width > $height
203  );
204  $this->assertEquals(
205  $width > $height,
206  $new_dimensions[self::W] > $new_dimensions[self::H]
207  );
208  }
209 
210  public static function getImageSizesByFixed(): array
211  {
212  return [
213  [1024, 768, 300, 100, true],
214  [1024, 768, 300, 100, false],
215  [1024, 768, 100, 300, true],
216  [1024, 768, 100, 300, false],
217  [400, 300, 500, 400, true],
218  [400, 300, 500, 400, false],
219  ];
220  }
221 
225  public function testResizeByFixedSize(
226  int $width,
227  int $height,
228  int $final_width,
229  int $final_height,
230  bool $crop
231  ): void {
232  $stream = $this->createTestImageStream($width, $height);
233  $dimensions = $this->getImageSizeFromStream($stream);
234  $this->assertEquals($width, $dimensions[self::W]);
235  $this->assertEquals($height, $dimensions[self::H]);
236 
237  $by_fixed = $this->images->resizeToFixedSize($stream, $final_width, $final_height, $crop);
238  $this->assertTrue($by_fixed->isOK());
239  $new_dimensions = $this->getImageSizeFromStream($by_fixed->getStream());
240 
241  $this->assertEquals($final_width, $new_dimensions[self::W]);
242  $this->assertEquals($final_height, $new_dimensions[self::H]);
243  }
244 
245  public static function getImageOptions(): array
246  {
247  $options = new ImageOutputOptions();
248  return [
249  [$options, self::IMAGE_JPEG, 75],
250  [$options->withPngOutput()->withQuality(22), self::IMAGE_PNG, 0],
251  [$options->withJpgOutput()->withQuality(100), self::IMAGE_JPEG, 100],
252  [$options->withFormat('png')->withQuality(50), self::IMAGE_PNG, 0],
253  [$options->withFormat('jpg')->withQuality(87), self::IMAGE_JPEG, 87],
254  [$options->withQuality(5)->withJpgOutput(), self::IMAGE_JPEG, 5],
255  [$options->withQuality(10)->withJpgOutput(), self::IMAGE_JPEG, 10],
256  [$options->withQuality(35)->withJpgOutput(), self::IMAGE_JPEG, 35],
257  [$options->withQuality(0)->withWebPOutput(), self::IMAGE_WEBP, 0],
258  [$options->withQuality(100)->withWebPOutput(), self::IMAGE_WEBP, 100],
259  ];
260  }
261 
262 
266  public function testImageOutputOptions(
267  ImageOutputOptions $options,
268  string $expected_mime_type,
269  int $expected_quality
270  ): void {
271  $resized = $this->images->resizeToFixedSize(
272  $this->createTestImageStream(10, 10),
273  5,
274  5,
275  true,
276  $options
277  );
278 
279  $this->assertEquals($expected_mime_type, $this->getImageTypeFromStream($resized->getStream()));
280  $this->assertEquals($expected_quality, $this->getImageQualityFromStream($resized->getStream()));
281  }
282 
283  public function testImageOutputOptionSanity(): void
284  {
285  $options = new ImageOutputOptions();
286 
287  // Defaults
288  $this->assertEquals('jpg', $options->getFormat());
289  $this->assertEquals(75, $options->getQuality());
290 
291  $png = $options->withPngOutput();
292  $this->assertEquals('png', $png->getFormat());
293  $this->assertEquals('jpg', $options->getFormat()); // original options should not change
294  $png_explicit = $options->withFormat('png');
295  $this->assertEquals('png', $png_explicit->getFormat());
296 
297  $jpg = $options->withJpgOutput();
298  $this->assertEquals('jpg', $jpg->getFormat());
299  $jpg_explicit = $options->withFormat('jpg');
300  $this->assertEquals('jpg', $jpg_explicit->getFormat());
301  $jpeg = $options->withFormat('jpeg');
302  $this->assertEquals('jpg', $jpeg->getFormat());
303 
304  // Quality
305  $low = $options->withQuality(5);
306  $this->assertEquals(5, $low->getQuality());
307  $this->assertEquals(75, $options->getQuality()); // original options should not change
308  }
309 
310  public static function getWrongFormats(): array
311  {
312  return [
313  ['gif'],
314  ['bmp'],
315  ['jpg2000'],
316  ];
317  }
318 
322  public function testWrongFormats(string $format): void
323  {
324  $options = new ImageOutputOptions();
325  $this->expectException(\InvalidArgumentException::class);
326  $wrong = $options->withFormat($format);
327  }
328 
329  public static function getWrongQualites(): array
330  {
331  return [
332  [-1],
333  [101],
334  [102],
335  ];
336  }
337 
341  public function testWrongQualities(int $quality): void
342  {
343  $options = new ImageOutputOptions();
344  $this->expectException(\InvalidArgumentException::class);
345  $wrong = $options->withQuality($quality);
346  }
347 
348  public function testFormatConvert(): void
349  {
350  $jpg = $this->createTestImageStream(10, 10);
351  $png = $this->images->convertToFormat(
352  $jpg,
353  'png'
354  );
355 
356  $this->assertEquals(self::IMAGE_PNG, $this->getImageTypeFromStream($png->getStream()));
357  $size = $this->getImageSizeFromStream($png->getStream());
358  $this->assertEquals(10, $size[self::W]);
359  $this->assertEquals(10, $size[self::H]);
360 
361  // With Dimensions
362  $jpg = $this->createTestImageStream(10, 10);
363  $png = $this->images->convertToFormat(
364  $jpg,
365  'png',
366  20,
367  20
368  );
369 
370  $this->assertEquals(self::IMAGE_PNG, $this->getImageTypeFromStream($png->getStream()));
371  $size = $this->getImageSizeFromStream($png->getStream());
372  $this->assertEquals(20, $size[self::W]);
373  $this->assertEquals(20, $size[self::H]);
374  }
375 
376  public function testFailed(): void
377  {
378  $false_stream = Streams::ofString('false');
379  $images = new Images(
380  false,
381  false
382  );
383 
384  $resized = $images->resizeToFixedSize(
385  $false_stream,
386  5,
387  5
388  );
389  $this->assertFalse($resized->isOK());
390  $this->assertInstanceOf(\Throwable::class, $resized->getThrowableIfAny());
391  }
392 
393  public static function getColors(): array
394  {
395  return [
396  [null],
397  ['#000000'],
398  ['#ff0000'],
399  ['#00ff00'],
400  ['#0000ff'],
401  ['#ffffff'],
402  ['#A3BF5A'],
403  ['#E9745A'],
404  ['#5A5AE9'],
405  ['#5AE9E9'],
406  ['#E95AE9'],
407  ['#E9E95A'],
408  ];
409  }
410 
411  private function colorDiff(string $hex_color_one, string $hex_color_two): int
412  {
413  preg_match('/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i', $hex_color_one, $rgb_one);
414  preg_match('/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i', $hex_color_two, $rgb_two);
415 
416  return abs(hexdec($rgb_one[1]) - hexdec($rgb_two[1]))
417  + abs(hexdec($rgb_one[2]) - hexdec($rgb_two[2]))
418  + abs(hexdec($rgb_one[3]) - hexdec($rgb_two[3]));
419  }
420 
424  public function testBackgroundColor(?string $color): void
425  {
426  $transparent_png = __DIR__ . '/img/transparent.png';
427  $this->assertFileExists($transparent_png);
428  $png = Streams::ofResource(fopen($transparent_png, 'rb'));
429 
430  $converter_options = (new ImageConversionOptions())
431  ->withThrowOnError(true)
432  ->withFixedDimensions(100, 100);
433 
434  if ($color !== null) {
435  $converter_options = $converter_options->withBackgroundColor($color);
436  } else {
437  $color = '#ffffff';
438  }
439 
440  $output_options = (new ImageOutputOptions())
441  ->withQuality(100)
442  ->withJpgOutput();
443 
444  $converter = new ImageConverter($converter_options, $output_options, $png);
445  $this->assertTrue($converter->isOK());
446  $converted_stream = $converter->getStream();
447  $gd_image = imagecreatefromstring((string) $converted_stream);
448  $colors = imagecolorsforindex($gd_image, imagecolorat($gd_image, 1, 1));
449 
450  $color_in_converted_picture = sprintf("#%02x%02x%02x", $colors['red'], $colors['green'], $colors['blue']);
451  $color_diff = $this->colorDiff($color, $color_in_converted_picture);
452 
453  $this->assertLessThan(3, $color_diff);
454  }
455 
456  public function testWriteImage(): void
457  {
458  $img = $this->createTestImageStream(10, 10);
459 
460  $output_path = __DIR__ . '/img/output.jpg';
461  $converter_options = (new ImageConversionOptions())
462  ->withThrowOnError(true)
463  ->withMakeTemporaryFiles(false)
464  ->withFixedDimensions(100, 100)
465  ->withOutputPath($output_path);
466 
467  $output_options = (new ImageOutputOptions())
468  ->withQuality(10)
469  ->withJpgOutput();
470 
471  $converter = new ImageConverter($converter_options, $output_options, $img);
472  $this->assertTrue($converter->isOK());
473 
474  $this->assertFileExists($output_path);
475  $stream = $converter->getStream();
476  $this->assertEquals($output_path, $stream->getMetadata('uri'));
477 
478  unlink($output_path);
479  }
480 
481  public function testHighDensityPixel(): void
482  {
483  $file = 'https://upload.wikimedia.org/wikipedia/commons/5/5e/Jan_Vermeer_-_The_Art_of_Painting_-_Google_Art_Project.jpg';
484  $curl = curl_init();
485  curl_setopt($curl, CURLOPT_URL, $file);
486  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
487  curl_setopt($curl, CURLOPT_USERAGENT, 'PHPUnit/1.0');
488  $string = curl_exec($curl);
489  curl_close($curl);
490 
491  $img = Streams::ofString($string);
492  $this->assertInstanceOf(FileStream::class, $img);
493 
494  $converter_options = (new ImageConversionOptions())
495  ->withWidth(80)
496  ->withHeight(80)
497  ->withKeepAspectRatio(true)
498  ->withCrop(true)
499  ->withThrowOnError(true);
500 
501  $output_options = (new ImageOutputOptions())
502  ->withQuality(60)
503  ->withFormat(ImageOutputOptions::FORMAT_PNG);
504 
505  $converter = new ImageConverter($converter_options, $output_options, $img);
506  $this->assertTrue($converter->isOK());
507  }
508 
509 
510  protected function checkImagick(): void
511  {
512  if (!class_exists('Imagick')) {
513  $this->markTestSkipped('Imagick not installed');
514  }
515  }
516 
517  protected function getImageSizeFromStream(FileStream $stream): array
518  {
519  $getimagesizefromstring = getimagesizefromstring((string) $stream);
520  return [
521  self::W => (int) round($getimagesizefromstring[0]),
522  self::H => (int) round($getimagesizefromstring[1])
523  ];
524  }
525 
526  protected function getImageTypeFromStream(FileStream $stream): string
527  {
528  return finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $stream->read(255));
529  }
530 
531  protected function getImageQualityFromStream(FileStream $stream): int
532  {
533  $stream->rewind();
534  $img = new \Imagick();
535  $img->readImageBlob((string) $stream);
536 
537  return $img->getImageCompressionQuality();
538  }
539 
540  protected function createTestImageStream(int $width, int $height): FileStream
541  {
542  $img = new \Imagick();
543  $img->newImage($width, $height, new \ImagickPixel('black'));
544  $img->setImageFormat('jpg');
545 
546  $stream = Streams::ofString($img->getImageBlob());
547  $stream->rewind();
548  return $stream;
549  }
550 }
testResizeToFitWidth(int $width, int $height, int $final_width, int $final_height)
getImageSizesByWidth
testWrongFormats(string $format)
getWrongFormats
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Factory.php:21
testResizeByFixedSize(int $width, int $height, int $final_width, int $final_height, bool $crop)
getImageSizesByFixed
testImageOutputOptions(ImageOutputOptions $options, string $expected_mime_type, int $expected_quality)
getImageOptions
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
colorDiff(string $hex_color_one, string $hex_color_two)
testResizeToFitHeight(int $width, int $height, int $final_height, int $final_width)
getImageSizesByHeight
testWrongQualities(int $quality)
getWrongQualites
The base interface for all filesystem streams.
Definition: FileStream.php:31