Passed
Push — master ( b0a28a...0b74dd )
by Derek Stephen
02:05
created

Image::destroy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Del;
4
5
use Del\Image\Exception\NotFoundException;
6
use Del\Image\Exception\NothingLoadedException;
7
use Del\Image\Strategy\GifStrategy;
8
use Del\Image\Strategy\ImageTypeStrategyInterface;
9
use Del\Image\Strategy\JpegStrategy;
10
use Del\Image\Strategy\PngStrategy;
11
use Del\Image\Strategy\WebPStrategy;
12
13
class Image
14
{
15
    /** @var resource $image */
16
    private $image;
17
18
    /** @var string $fileName */
19
    private $fileName;
20
21
    /** @var ImageTypeStrategyInterface $strategy */
22
    private $strategy;
23
24
    /** @var array $strategies */
25
    private $strategies = [
26
        IMAGETYPE_JPEG => JpegStrategy::class,
27
        IMAGETYPE_GIF => GifStrategy::class,
28
        IMAGETYPE_PNG => PngStrategy::class,
29
        IMAGETYPE_WEBP => WebPStrategy::class,
30
    ];
31
32
    /**
33
     * @param string $filename
34
     */
35 28
    public function __construct(string $filename = null)
36
    {
37 28
        if ($filename !== null) {
38 3
            $this->fileName = $filename;
39 3
            $this->load($filename);
40
        }
41 28
    }
42
43
    /**
44
     * @param $path
45
     * @throws NotFoundException
46
     */
47 27
    private function checkFileExists(string $path): void
48
    {
49 27
        if (!\file_exists($path)) {
50 1
            throw new NotFoundException("$path does not exist");
51
        }
52 26
    }
53
54
55
    /**
56
     * @param string $filename
57
     * @throws NotFoundException
58
     */
59 27
    public function load(string $filename): void
60
    {
61 27
        $this->checkFileExists($filename);
62 26
        $index = \getimagesize($filename)[2];
63 26
        $this->strategy = new $this->strategies[$index]();
64 26
        $this->image = $this->strategy->create($filename);
65 26
    }
66
67
    /**
68
     * @param ImageTypeStrategyInterface $imageTypeStrategy
69
     */
70 1
    public function setImageStrategy(ImageTypeStrategyInterface $imageTypeStrategy): void
71
    {
72 1
        $this->strategy = $imageTypeStrategy;
73 1
    }
74
75
76
    /**
77
     *  @param string $filename
78
     *  @param int $compression
79
     *  @param string $permissions
80
     */
81 4
    public function save(string $filename = null, int $permissions = null, int $compression = 100): void
82
    {
83 4
        $filename = ($filename) ?: $this->fileName;
84 4
        $this->strategy->save($this->image, $filename, $compression);
85 4
        $this->setPermissions($filename, $permissions);
86 4
    }
87
88
    /**
89
     * @param string $filename
90
     * @param int|null $permissions
91
     */
92 4
    private function setPermissions(string $filename, ?int $permissions = null): void
93
    {
94 4
        if ($permissions !== null) {
95 2
            \chmod($filename, $permissions);
96
        }
97 4
    }
98
99
100
    /**
101
     * @param bool $return either output directly
102
     * @return null|string image contents
103
     */
104 20
    public function output(bool $return = false): ?string
105
    {
106 20
        $contents = null;
107
108 20
        if ($return) {
109 20
            \ob_start();
110
        }
111
112 20
        $this->renderImage();
113
114 20
        if ($return) {
115 20
            $contents = \ob_get_clean();
116
        }
117
118 20
        return $contents;
119
    }
120
121
    /**
122
     * @return string
123
     * @throws NothingLoadedException
124
     */
125 1
    public function outputBase64Src(): string
126
    {
127 1
        return 'data:' . $this->getHeader() . ';base64,' . \base64_encode( $this->output(true) );
128
    }
129
130 20
    private function renderImage(): void
131
    {
132 20
        $this->strategy->render($this->image);
133 20
    }
134
135
    /**
136
     * @return int
137
     */
138 11
    public function getWidth(): int
139
    {
140 11
        return \imagesx($this->image);
141
    }
142
143
    /**
144
     * @return int
145
     */
146 11
    public function getHeight(): int
147
    {
148 11
        return \imagesy($this->image);
149
    }
150
151
    /**
152
     * @param int $height
153
     */
154 3
    public function resizeToHeight(int $height): void
155
    {
156 3
        $ratio = $height / $this->getHeight();
157 3
        $width = $this->getWidth() * $ratio;
158 3
        $this->resize($width, $height);
159 3
    }
160
161
    /**
162
     * @param int $width
163
     */
164 4
    public function resizeToWidth(int $width): void
165
    {
166 4
        $ratio = $width / $this->getWidth();
167 4
        $height = $this->getHeight() * $ratio;
168 4
        $this->resize($width, $height);
169 4
    }
170
171
    /**
172
     * @param int $scale %
173
     */
174 1
    public function scale(int $scale): void
175
    {
176 1
        $width = $this->getWidth() * $scale / 100;
177 1
        $height = $this->getHeight() * $scale / 100;
178 1
        $this->resize($width, $height);
179 1
    }
180
181
    /**
182
     * @param int $width
183
     * @param int $height
184
     */
185 4
    public function resizeAndCrop(int $width, int $height): void
186
    {
187 4
        $targetRatio = $width / $height;
188 4
        $actualRatio = $this->getWidth() / $this->getHeight();
189
190 4
        if ($targetRatio == $actualRatio) {
191
            // Scale to size
192 1
            $this->resize($width, $height);
193 3
        } elseif ($targetRatio > $actualRatio) {
194
            // Resize to width, crop extra height
195 1
            $this->resizeToWidth($width);
196 1
            $this->crop($width, $height);
197
        } else {
198
            // Resize to height, crop additional width
199 2
            $this->resizeToHeight($height);
200 2
            $this->crop($width, $height);
201
        }
202 4
    }
203
204
205
    /**
206
     *  Now with added Transparency resizing feature
207
     *  @param int $width
208
     *  @param int $height
209
     */
210 9
    public function resize(int $width, int $height): void
211
    {
212 9
        $newImage = \imagecreatetruecolor($width, $height);
213
214 9
        $this->strategy->handleTransparency($newImage, $this->image);
0 ignored issues
show
Bug introduced by
It seems like $newImage can also be of type GdImage; however, parameter $newImage of Del\Image\Strategy\Image...e::handleTransparency() does only seem to accept resource, 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

214
        $this->strategy->handleTransparency(/** @scrutinizer ignore-type */ $newImage, $this->image);
Loading history...
215
216
        // Now resample the image
217 9
        \imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
218
219
        // And allocate to $this
220 9
        $this->image = $newImage;
0 ignored issues
show
Documentation Bug introduced by
It seems like $newImage can also be of type GdImage. However, the property $image is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
221 9
    }
222
223
224
225
226
    /**
227
     * @param int $width
228
     * @param int $height
229
     * @param string $trim from either left or center
230
     */
231 4
    public function crop(int $width, int $height, string $trim = 'center'): void
232
    {
233 4
        $offsetX = 0;
234 4
        $offsetY = 0;
235 4
        $currentWidth = $this->getWidth();
236 4
        $currentHeight = $this->getHeight();
237
238 4
        if ($trim !== 'left') {
239 4
            $offsetX = $this->getOffsetX($currentWidth, $width, $trim);
240 4
            $offsetY = $this->getOffsetY($currentHeight, $height, $trim);
241
        }
242
243 4
        $newImage = \imagecreatetruecolor($width, $height);
244 4
        \imagecopyresampled($newImage, $this->image, 0, 0, $offsetX, $offsetY, $width, $height, $width, $height);
245 4
        $this->image = $newImage;
0 ignored issues
show
Documentation Bug introduced by
It seems like $newImage can also be of type GdImage. However, the property $image is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
246 4
    }
247
248
    /**
249
     * @param int $currentWidth
250
     * @param int $width
251
     * @param string $trim
252
     * @return int
253
     */
254 4
    private function getOffsetX(int $currentWidth, int $width, string $trim): int
255
    {
256 4
        $offsetX = 0;
257
258 4
        if ($currentWidth > $width) {
259 3
            $diff = $currentWidth - $width;
260 3
            $offsetX = ($trim === 'center') ? $diff / 2 : $diff; //full diff for trim right
261
        }
262
263 4
        return (int) $offsetX;
264
    }
265
266
    /**
267
     * @param int $currentHeight
268
     * @param int $height
269
     * @param string $trim
270
     * @return int
271
     */
272 4
    private function getOffsetY(int $currentHeight, int $height, string $trim): int
273
    {
274 4
        $offsetY = 0;
275
276 4
        if ($currentHeight > $height) {
277 2
            $diff = $currentHeight - $height;
278 2
            $offsetY = ($trim === 'center') ? $diff / 2 : $diff;
279
        }
280
281 4
        return (int) $offsetY;
282
    }
283
284
    /**
285
     * @return string
286
     * @throws NothingLoadedException
287
     */
288 6
    public function getHeader(): string
289
    {
290 6
        if (!$this->strategy) {
291 1
            throw new NothingLoadedException();
292
        }
293
294 5
        return $this->strategy->getContentType();
295
    }
296
297
    /**
298
     *  Frees up memory
299
     */
300 8
    public function destroy(): void
301
    {
302 8
        \imagedestroy($this->image);
303 8
    }
304
}
305