Passed
Push — master ( d60929...b0a28a )
by Derek Stephen
09:59
created

Image   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 13
Bugs 1 Features 0
Metric Value
wmc 35
eloc 82
c 13
b 1
f 0
dl 0
loc 288
ccs 105
cts 105
cp 1
rs 9.6

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A save() 0 5 2
A checkFileExists() 0 4 2
A output() 0 15 3
A load() 0 6 1
A setPermissions() 0 4 2
A setImageStrategy() 0 3 1
A getHeight() 0 3 1
A crop() 0 15 2
A resizeToHeight() 0 5 1
A getOffsetX() 0 9 3
A resizeToWidth() 0 5 1
A resizeAndCrop() 0 16 3
A getWidth() 0 3 1
A destroy() 0 3 1
A scale() 0 5 1
A getOffsetY() 0 9 3
A renderImage() 0 3 1
A resize() 0 11 1
A outputBase64Src() 0 3 1
A getHeader() 0 7 2
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 27
    public function __construct(string $filename = null)
36
    {
37 27
        if ($filename !== null) {
38 3
            $this->fileName = $filename;
39 3
            $this->load($filename);
40
        }
41 27
    }
42
43
    /**
44
     * @param $path
45
     * @throws NotFoundException
46
     */
47 26
    private function checkFileExists(string $path): void
48
    {
49 26
        if (!file_exists($path)) {
50 1
            throw new NotFoundException("$path does not exist");
51
        }
52 25
    }
53
54
55
    /**
56
     * @param string $filename
57
     * @throws NotFoundException
58
     */
59 26
    public function load(string $filename): void
60
    {
61 26
        $this->checkFileExists($filename);
62 25
        $index = getimagesize($filename)[2];
63 25
        $this->strategy = new $this->strategies[$index]();
64 25
        $this->image = $this->strategy->create($filename);
65 25
    }
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 19
    public function output(bool $return = false): ?string
105
    {
106 19
        $contents = null;
107
108 19
        if ($return) {
109 19
            ob_start();
110
        }
111
112 19
        $this->renderImage();
113
114 19
        if ($return) {
115 19
            $contents = ob_get_clean();
116
        }
117
118 19
        return $contents;
119
    }
120
121 19
    /**
122
     * @return string
123 19
     * @throws NothingLoadedException
124 19
     */
125
    public function outputBase64Src(): string
126
    {
127
        return 'data:' . $this->getHeader() . ';base64,' . \base64_encode( $this->output(true) );
128
    }
129 11
130
    private function renderImage(): void
131 11
    {
132
        $this->strategy->render($this->image);
133
    }
134
135
    /**
136
     * @return int
137 11
     */
138
    public function getWidth(): int
139 11
    {
140
        return imagesx($this->image);
141
    }
142
143
    /**
144
     * @return int
145 3
     */
146
    public function getHeight(): int
147 3
    {
148 3
        return imagesy($this->image);
149 3
    }
150 3
151
    /**
152
     * @param int $height
153
     */
154
    public function resizeToHeight(int $height): void
155 4
    {
156
        $ratio = $height / $this->getHeight();
157 4
        $width = $this->getWidth() * $ratio;
158 4
        $this->resize($width, $height);
159 4
    }
160 4
161
    /**
162
     * @param int $width
163
     */
164
    public function resizeToWidth(int $width): void
165 1
    {
166
        $ratio = $width / $this->getWidth();
167 1
        $height = $this->getHeight() * $ratio;
168 1
        $this->resize($width, $height);
169 1
    }
170 1
171
    /**
172
     * @param int $scale %
173
     */
174
    public function scale(int $scale): void
175
    {
176 4
        $width = $this->getWidth() * $scale / 100;
177
        $height = $this->getHeight() * $scale / 100;
178 4
        $this->resize($width, $height);
179 4
    }
180
181 4
    /**
182
     * @param int $width
183 1
     * @param int $height
184 3
     */
185
    public function resizeAndCrop(int $width, int $height): void 
186 1
    {
187 1
        $targetRatio = $width / $height;
188
        $actualRatio = $this->getWidth() / $this->getHeight();
189
190 2
        if ($targetRatio == $actualRatio) {
191 2
            // Scale to size
192
            $this->resize($width, $height);
193 4
        } elseif ($targetRatio > $actualRatio) {
194
            // Resize to width, crop extra height
195
            $this->resizeToWidth($width);
196
            $this->crop($width, $height);
197
        } else {
198
            // Resize to height, crop additional width
199
            $this->resizeToHeight($height);
200
            $this->crop($width, $height);
201 9
        }
202
    }
203 9
204
205 9
    /**
206
     *  Now with added Transparency resizing feature
207
     *  @param int $width
208 9
     *  @param int $height
209
     */
210
    public function resize(int $width, int $height): void 
211 9
    {
212 9
        $newImage = imagecreatetruecolor($width, $height);
213
214
        $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
        imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
218
219
        // And allocate to $this
220
        $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
    }
222 4
223
224 4
225 4
226 4
    /**
227 4
     * @param int $width
228
     * @param int $height
229 4
     * @param string $trim from either left or center
230 4
     */
231 4
    public function crop(int $width, int $height, string $trim = 'center'): void
232
    {
233
        $offsetX = 0;
234 4
        $offsetY = 0;
235 4
        $currentWidth = $this->getWidth();
236 4
        $currentHeight = $this->getHeight();
237 4
238
        if ($trim != 'left') {
239
            $offsetX = $this->getOffsetX($currentWidth, $width, $trim);
240
            $offsetY = $this->getOffsetY($currentHeight, $height, $trim);
241
        }
242
243
        $newImage = imagecreatetruecolor($width, $height);
244
        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
    }
247 4
248 4
    /**
249 3
     * @param int $currentWidth
250 3
     * @param int $width
251
     * @param string $trim
252
     * @return int
253 4
     */
254
    private function getOffsetX(int $currentWidth, int $width, string $trim): int
255
    {
256
        $offsetX = 0;
257
        if ($currentWidth > $width) {
258
            $diff = $currentWidth - $width;
259
            $offsetX = ($trim == 'center') ? $diff / 2 : $diff; //full diff for trim right
260
        }
261
        
262 4
        return (int) $offsetX;
263
    }
264 4
265 4
    /**
266 2
     * @param int $currentHeight
267 2
     * @param int $height
268
     * @param string $trim
269
     * @return int
270 4
     */
271
    private function getOffsetY(int $currentHeight, int $height, string $trim): int 
272
    {
273
        $offsetY = 0;
274
        if ($currentHeight > $height) {
275
            $diff = $currentHeight - $height;
276
            $offsetY = ($trim == 'center') ? $diff / 2 : $diff;
277 5
        }
278
        
279 5
        return (int) $offsetY;
280 1
    }
281
282
    /**
283 4
     * @return string
284
     * @throws NothingLoadedException
285
     */
286
    public function getHeader(): string
287
    {
288
        if (!$this->strategy) {
289 8
            throw new NothingLoadedException();
290
        }
291 8
        
292 8
        return $this->strategy->getContentType();
293
    }
294
295
    /**
296
     *  Frees up memory
297
     */
298
    public function destroy(): void
299
    {
300
        imagedestroy($this->image);
301
    }
302
}
303