Issues (9)

src/Image.php (4 issues)

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Del;
6
7
use Del\Image\Exception\NotFoundException;
8
use Del\Image\Exception\NothingLoadedException;
9
use Del\Image\Strategy\GifStrategy;
10
use Del\Image\Strategy\ImageTypeStrategyInterface;
11
use Del\Image\Strategy\JpegStrategy;
12
use Del\Image\Strategy\PngStrategy;
13
use Del\Image\Strategy\WebPStrategy;
14
use GdImage;
15
16
class Image
17
{
18
    private GdImage $image;
19
    private ?string $fileName = null;
20
    private ?ImageTypeStrategyInterface $strategy = null;
21
22
    private array $strategies = [
23
        IMAGETYPE_JPEG => JpegStrategy::class,
24
        IMAGETYPE_GIF => GifStrategy::class,
25
        IMAGETYPE_PNG => PngStrategy::class,
26
        IMAGETYPE_WEBP => WebPStrategy::class,
27
    ];
28
29 28
    public function __construct(string $filename = null)
30
    {
31 28
        if ($filename !== null) {
32 3
            $this->fileName = $filename;
33 3
            $this->load($filename);
34
        }
35
    }
36
37
    /** @throws NotFoundException  */
38 27
    private function checkFileExists(string $path): void
39
    {
40 27
        if (!\file_exists($path)) {
41 1
            throw new NotFoundException("$path does not exist");
42
        }
43
    }
44
45
    /** @throws NotFoundException  */
46 27
    public function load(string $filename): void
47
    {
48 27
        $this->checkFileExists($filename);
49 26
        $index = \getimagesize($filename)[2];
50 26
        $this->strategy = new $this->strategies[$index]();
51 26
        $this->image = $this->strategy->create($filename);
0 ignored issues
show
The method create() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

51
        /** @scrutinizer ignore-call */ 
52
        $this->image = $this->strategy->create($filename);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
52
    }
53
54 1
    public function setImageStrategy(ImageTypeStrategyInterface $imageTypeStrategy): void
55
    {
56 1
        $this->strategy = $imageTypeStrategy;
57
    }
58
59 4
    public function save(string $filename = null, int $permissions = null, int $compression = 100): void
60
    {
61 4
        $filename = ($filename) ?: $this->fileName;
62 4
        $this->strategy->save($this->image, $filename, $compression);
0 ignored issues
show
It seems like $filename can also be of type null; however, parameter $filename of Del\Image\Strategy\Image...rategyInterface::save() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
        $this->strategy->save($this->image, /** @scrutinizer ignore-type */ $filename, $compression);
Loading history...
63 4
        $this->setPermissions($filename, $permissions);
0 ignored issues
show
It seems like $filename can also be of type null; however, parameter $filename of Del\Image::setPermissions() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

63
        $this->setPermissions(/** @scrutinizer ignore-type */ $filename, $permissions);
Loading history...
64
    }
65
66 4
    private function setPermissions(string $filename, ?int $permissions = null): void
67
    {
68 4
        if ($permissions !== null) {
69 2
            \chmod($filename, $permissions);
70
        }
71
    }
72
73 20
    public function output(bool $return = false): ?string
74
    {
75 20
        $contents = null;
76
77 20
        if ($return) {
78 20
            \ob_start();
79
        }
80
81 20
        $this->renderImage();
82
83 20
        if ($return) {
84 20
            $contents = \ob_get_clean();
85
        }
86
87 20
        return $contents;
88
    }
89
90
    /** @throws NothingLoadedException */
91 1
    public function outputBase64Src(): string
92
    {
93 1
        return 'data:' . $this->getHeader() . ';base64,' . \base64_encode( $this->output(true) );
94
    }
95
96 20
    private function renderImage(): void
97
    {
98 20
        $this->strategy->render($this->image);
99
    }
100
101 11
    public function getWidth(): int
102
    {
103 11
        return \imagesx($this->image);
104
    }
105
106 11
    public function getHeight(): int
107
    {
108 11
        return \imagesy($this->image);
109
    }
110
111 3
    public function resizeToHeight(int $height): void
112
    {
113 3
        $ratio = $height / $this->getHeight();
114 3
        $width = (int) ($this->getWidth() * $ratio);
115 3
        $this->resize($width, $height);
116
    }
117
118 4
    public function resizeToWidth(int $width): void
119
    {
120 4
        $ratio = $width / $this->getWidth();
121 4
        $height = (int) ($this->getHeight() * $ratio);
122 4
        $this->resize($width, $height);
123
    }
124
125 1
    public function scale(int $scale): void
126
    {
127 1
        $width = (int) ($this->getWidth() * $scale / 100);
128 1
        $height = (int) ($this->getHeight() * $scale / 100);
129 1
        $this->resize($width, $height);
130
    }
131
132 4
    public function resizeAndCrop(int $width, int $height): void
133
    {
134 4
        $targetRatio = $width / $height;
135 4
        $actualRatio = $this->getWidth() / $this->getHeight();
136
137 4
        if ($targetRatio === $actualRatio) {
138
            // Scale to size
139 1
            $this->resize($width, $height);
140 3
        } elseif ($targetRatio > $actualRatio) {
141
            // Resize to width, crop extra height
142 1
            $this->resizeToWidth($width);
143 1
            $this->crop($width, $height);
144
        } else {
145
            // Resize to height, crop additional width
146 2
            $this->resizeToHeight($height);
147 2
            $this->crop($width, $height);
148
        }
149
    }
150
151 9
    public function resize(int $width, int $height): void
152
    {
153 9
        $newImage = \imagecreatetruecolor($width, $height);
154
155 9
        $this->strategy->handleTransparency($newImage, $this->image);
0 ignored issues
show
It seems like $newImage can also be of type resource; however, parameter $newImage of Del\Image\Strategy\Image...e::handleTransparency() does only seem to accept GdImage, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
        $this->strategy->handleTransparency(/** @scrutinizer ignore-type */ $newImage, $this->image);
Loading history...
156
157
        // Now resample the image
158 9
        \imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
159
160
        // And allocate to $this
161 9
        $this->image = $newImage;
162
    }
163
164
    /** $trim can be either left or center */
165 4
    public function crop(int $width, int $height, string $trim = 'center'): void
166
    {
167 4
        $offsetX = 0;
168 4
        $offsetY = 0;
169 4
        $currentWidth = $this->getWidth();
170 4
        $currentHeight = $this->getHeight();
171
172 4
        if ($trim !== 'left') {
173 4
            $offsetX = $this->getOffsetX($currentWidth, $width, $trim);
174 4
            $offsetY = $this->getOffsetY($currentHeight, $height, $trim);
175
        }
176
177 4
        $newImage = \imagecreatetruecolor($width, $height);
178 4
        \imagecopyresampled($newImage, $this->image, 0, 0, $offsetX, $offsetY, $width, $height, $width, $height);
179 4
        $this->image = $newImage;
180
    }
181
182 4
    private function getOffsetX(int $currentWidth, int $width, string $trim): int
183
    {
184 4
        $offsetX = 0;
185
186 4
        if ($currentWidth > $width) {
187 3
            $diff = $currentWidth - $width;
188 3
            $offsetX = ($trim === 'center') ? $diff / 2 : $diff; //full diff for trim right
189
        }
190
191 4
        return (int) $offsetX;
192
    }
193
194 4
    private function getOffsetY(int $currentHeight, int $height, string $trim): int
195
    {
196 4
        $offsetY = 0;
197
198 4
        if ($currentHeight > $height) {
199 2
            $diff = $currentHeight - $height;
200 2
            $offsetY = ($trim === 'center') ? $diff / 2 : $diff;
201
        }
202
203 4
        return (int) $offsetY;
204
    }
205
206
    /** @throws NothingLoadedException */
207 6
    public function getHeader(): string
208
    {
209 6
        if (!$this->strategy) {
210 1
            throw new NothingLoadedException();
211
        }
212
213 5
        return $this->strategy->getContentType();
214
    }
215
216 8
    public function destroy(): void
217
    {
218 8
        \imagedestroy($this->image);
219
    }
220
}
221