]> BookStack Code Mirror - bookstack/blob - app/Uploads/ImageResizer.php
Images: Started refactor of image service
[bookstack] / app / Uploads / ImageResizer.php
1 <?php
2
3 namespace BookStack\Uploads;
4
5 use BookStack\Exceptions\ImageUploadException;
6 use GuzzleHttp\Psr7\Utils;
7 use Intervention\Image\Exception\NotSupportedException;
8 use Intervention\Image\Image as InterventionImage;
9 use Intervention\Image\ImageManager;
10
11 class ImageResizer
12 {
13     public function __construct(
14         protected ImageManager $intervention
15     ) {
16     }
17
18     /**
19      * Resize the image of given data to the specified size, and return the new image data.
20      *
21      * @throws ImageUploadException
22      */
23     protected function resizeImageData(string $imageData, ?int $width, ?int $height, bool $keepRatio): string
24     {
25         try {
26             $thumb = $this->intervention->make($imageData);
27         } catch (NotSupportedException $e) {
28             throw new ImageUploadException(trans('errors.cannot_create_thumbs'));
29         }
30
31         $this->orientImageToOriginalExif($thumb, $imageData);
32
33         if ($keepRatio) {
34             $thumb->resize($width, $height, function ($constraint) {
35                 $constraint->aspectRatio();
36                 $constraint->upsize();
37             });
38         } else {
39             $thumb->fit($width, $height);
40         }
41
42         $thumbData = (string) $thumb->encode();
43
44         // Use original image data if we're keeping the ratio
45         // and the resizing does not save any space.
46         if ($keepRatio && strlen($thumbData) > strlen($imageData)) {
47             return $imageData;
48         }
49
50         return $thumbData;
51     }
52
53     /**
54      * Orientate the given intervention image based upon the given original image data.
55      * Intervention does have an `orientate` method but the exif data it needs is lost before it
56      * can be used (At least when created using binary string data) so we need to do some
57      * implementation on our side to use the original image data.
58      * Bulk of logic taken from: https://github.com/Intervention/image/blob/b734a4988b2148e7d10364b0609978a88d277536/src/Intervention/Image/Commands/OrientateCommand.php
59      * Copyright (c) Oliver Vogel, MIT License.
60      */
61     protected function orientImageToOriginalExif(InterventionImage $image, string $originalData): void
62     {
63         if (!extension_loaded('exif')) {
64             return;
65         }
66
67         $stream = Utils::streamFor($originalData)->detach();
68         $exif = @exif_read_data($stream);
69         $orientation = $exif ? ($exif['Orientation'] ?? null) : null;
70
71         switch ($orientation) {
72             case 2:
73                 $image->flip();
74                 break;
75             case 3:
76                 $image->rotate(180);
77                 break;
78             case 4:
79                 $image->rotate(180)->flip();
80                 break;
81             case 5:
82                 $image->rotate(270)->flip();
83                 break;
84             case 6:
85                 $image->rotate(270);
86                 break;
87             case 7:
88                 $image->rotate(90)->flip();
89                 break;
90             case 8:
91                 $image->rotate(90);
92                 break;
93         }
94     }
95 }