Passed
Push — master ( 05757a...dfbf27 )
by Derek Stephen
09:55
created

Image::outputBase64Src()   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 0
Metric Value
eloc 1
c 0
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 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
    private function renderImage(): void
122
    {
123 19
        $this->strategy->render($this->image);
124 19
    }
125
126
    /**
127
     * @return int
128
     */
129 11
    public function getWidth(): int
130
    {
131 11
        return imagesx($this->image);
132
    }
133
134
    /**
135
     * @return int
136
     */
137 11
    public function getHeight(): int
138
    {
139 11
        return imagesy($this->image);
140
    }
141
142
    /**
143
     * @param int $height
144
     */
145 3
    public function resizeToHeight(int $height): void
146
    {
147 3
        $ratio = $height / $this->getHeight();
148 3
        $width = $this->getWidth() * $ratio;
149 3
        $this->resize($width, $height);
150 3
    }
151
152
    /**
153
     * @param int $width
154
     */
155 4
    public function resizeToWidth(int $width): void
156
    {
157 4
        $ratio = $width / $this->getWidth();
158 4
        $height = $this->getHeight() * $ratio;
159 4
        $this->resize($width, $height);
160 4
    }
161
162
    /**
163
     * @param int $scale %
164
     */
165 1
    public function scale(int $scale): void
166
    {
167 1
        $width = $this->getWidth() * $scale / 100;
168 1
        $height = $this->getHeight() * $scale / 100;
169 1
        $this->resize($width, $height);
170 1
    }
171
172
    /**
173
     * @param int $width
174
     * @param int $height
175
     */
176 4
    public function resizeAndCrop(int $width, int $height): void 
177
    {
178 4
        $targetRatio = $width / $height;
179 4
        $actualRatio = $this->getWidth() / $this->getHeight();
180
181 4
        if ($targetRatio == $actualRatio) {
182
            // Scale to size
183 1
            $this->resize($width, $height);
184 3
        } elseif ($targetRatio > $actualRatio) {
185
            // Resize to width, crop extra height
186 1
            $this->resizeToWidth($width);
187 1
            $this->crop($width, $height);
188
        } else {
189
            // Resize to height, crop additional width
190 2
            $this->resizeToHeight($height);
191 2
            $this->crop($width, $height);
192
        }
193 4
    }
194
195
196
    /**
197
     *  Now with added Transparency resizing feature
198
     *  @param int $width
199
     *  @param int $height
200
     */
201 9
    public function resize(int $width, int $height): void 
202
    {
203 9
        $newImage = imagecreatetruecolor($width, $height);
204
205 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

205
        $this->strategy->handleTransparency(/** @scrutinizer ignore-type */ $newImage, $this->image);
Loading history...
206
207
        // Now resample the image
208 9
        imagecopyresampled($newImage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
209
210
        // And allocate to $this
211 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...
212 9
    }
213
214
215
216
217
    /**
218
     * @param int $width
219
     * @param int $height
220
     * @param string $trim from either left or center
221
     */
222 4
    public function crop(int $width, int $height, string $trim = 'center'): void
223
    {
224 4
        $offsetX = 0;
225 4
        $offsetY = 0;
226 4
        $currentWidth = $this->getWidth();
227 4
        $currentHeight = $this->getHeight();
228
229 4
        if ($trim != 'left') {
230 4
            $offsetX = $this->getOffsetX($currentWidth, $width, $trim);
231 4
            $offsetY = $this->getOffsetY($currentHeight, $height, $trim);
232
        }
233
234 4
        $newImage = imagecreatetruecolor($width, $height);
235 4
        imagecopyresampled($newImage, $this->image, 0, 0, $offsetX, $offsetY, $width, $height, $width, $height);
236 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...
237 4
    }
238
239
    /**
240
     * @param int $currentWidth
241
     * @param int $width
242
     * @param string $trim
243
     * @return int
244
     */
245 4
    private function getOffsetX(int $currentWidth, int $width, string $trim): int
246
    {
247 4
        $offsetX = 0;
248 4
        if ($currentWidth > $width) {
249 3
            $diff = $currentWidth - $width;
250 3
            $offsetX = ($trim == 'center') ? $diff / 2 : $diff; //full diff for trim right
251
        }
252
        
253 4
        return (int) $offsetX;
254
    }
255
256
    /**
257
     * @param int $currentHeight
258
     * @param int $height
259
     * @param string $trim
260
     * @return int
261
     */
262 4
    private function getOffsetY(int $currentHeight, int $height, string $trim): int 
263
    {
264 4
        $offsetY = 0;
265 4
        if ($currentHeight > $height) {
266 2
            $diff = $currentHeight - $height;
267 2
            $offsetY = ($trim == 'center') ? $diff / 2 : $diff;
268
        }
269
        
270 4
        return (int) $offsetY;
271
    }
272
273
    /**
274
     * @return string
275
     * @throws NothingLoadedException
276
     */
277 5
    public function getHeader(): string
278
    {
279 5
        if (!$this->strategy) {
280 1
            throw new NothingLoadedException();
281
        }
282
        
283 4
        return $this->strategy->getContentType();
284
    }
285
286
    /**
287
     *  Frees up memory
288
     */
289 8
    public function destroy(): void
290
    {
291 8
        imagedestroy($this->image);
292 8
    }
293
}
294