Completed
Push — master ( aae6d4...77a0b7 )
by Derek Stephen
02:32
created

Image::getOffsetY()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 10
c 1
b 0
f 0
cc 3
nc 3
nop 3
crap 3
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 29
    public function __construct(string $filename = null)
36
    {
37 29
        if ($filename !== null) {
38 3
            $this->fileName = $filename;
39 3
            $this->load($filename);
40
        }
41 29
    }
42
43
    /**
44
     * @param $path
45
     * @throws NotFoundException
46
     */
47 28
    private function checkFileExists(string $path): void
48
    {
49 28
        if (!file_exists($path)) {
50 1
            throw new NotFoundException("$path does not exist");
51
        }
52 27
    }
53
54
55
    /**
56
     * @param string $filename
57
     * @throws NotFoundException
58
     */
59 28
    public function load(string $filename): void
60
    {
61 28
        $this->checkFileExists($filename);
62 27
        $index = getimagesize($filename)[2];
63 27
        $this->strategy = new $this->strategies[$index]();
64 27
        $this->image = $this->strategy->create($filename);
65 27
    }
66
67
    /**
68
     * @param ImageTypeStrategyInterface $imageTypeStrategy
69
     */
70 2
    public function setImageStrategy(ImageTypeStrategyInterface $imageTypeStrategy): void
71
    {
72 2
        $this->strategy = $imageTypeStrategy;
73 2
    }
74
75
76
    /**
77
     *  @param string $filename
78
     *  @param int $compression
79
     *  @param string $permissions
80
     */
81 5
    public function save(string $filename = null, int $permissions = null, int $compression = 100): void
82
    {
83 5
        $filename = ($filename) ?: $this->fileName;
84 5
        $this->strategy->save($this->image, $filename, $compression);
85 5
        $this->setPermissions($filename, $permissions);
86 5
    }
87
88
    /**
89
     * @param string $filename
90
     * @param int|null $permissions
91
     */
92 5
    private function setPermissions(string $filename, ?int $permissions = null): void
93
    {
94 5
        if ($permissions !== null) {
95 2
            chmod($filename, $permissions);
96
        }
97 5
    }
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 false; 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());
0 ignored issues
show
Bug introduced by
It seems like $newImage can also be of type false; however, parameter $dst_image of imagecopyresampled() 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

217
        imagecopyresampled(/** @scrutinizer ignore-type */ $newImage, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight());
Loading history...
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 false. 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);
0 ignored issues
show
Bug introduced by
It seems like $newImage can also be of type false; however, parameter $dst_image of imagecopyresampled() 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

244
        imagecopyresampled(/** @scrutinizer ignore-type */ $newImage, $this->image, 0, 0, $offsetX, $offsetY, $width, $height, $width, $height);
Loading history...
245 4
        $this->image = $newImage;
0 ignored issues
show
Documentation Bug introduced by
It seems like $newImage can also be of type false. 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 4
        if ($currentWidth > $width) {
258 3
            $diff = $currentWidth - $width;
259 3
            $offsetX = ($trim == 'center') ? $diff / 2 : $diff; //full diff for trim right
260
        }
261
        
262 4
        return (int) $offsetX;
263
    }
264
265
    /**
266
     * @param int $currentHeight
267
     * @param int $height
268
     * @param string $trim
269
     * @return int
270
     */
271 4
    private function getOffsetY(int $currentHeight, int $height, string $trim): int 
272
    {
273 4
        $offsetY = 0;
274 4
        if ($currentHeight > $height) {
275 2
            $diff = $currentHeight - $height;
276 2
            $offsetY = ($trim == 'center') ? $diff / 2 : $diff;
277
        }
278
        
279 4
        return (int) $offsetY;
280
    }
281
282
    /**
283
     * @return string
284
     * @throws NothingLoadedException
285
     */
286 6
    public function getHeader(): string
287
    {
288 6
        if (!$this->strategy) {
289 1
            throw new NothingLoadedException();
290
        }
291
        
292 5
        return $this->strategy->getContentType();
293
    }
294
295
    /**
296
     *  Frees up memory
297
     */
298 8
    public function destroy(): void
299
    {
300 8
        imagedestroy($this->image);
301 8
    }
302
}
303