Test Failed
Push — develop ( 82dcf2...2060e9 )
by nguereza
13:58
created

Image::getImage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
/**
4
 * Platine Framework
5
 *
6
 * Platine Framework is a lightweight, high-performance, simple and elegant
7
 * PHP Web framework
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Framework
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
declare(strict_types=1);
33
34
namespace Platine\Framework\Helper;
35
36
use GdImage;
37
use RuntimeException;
38
39
/**
40
 * @class Image
41
 * @package Platine\Framework\Helper
42
 */
43
class Image
44
{
45
    /**
46
     * The current width of the image
47
     * @var int
48
     */
49
    protected int $width = 0;
50
51
    /**
52
     * The current height of the image
53
     * @var int
54
     */
55
    protected int $height = 0;
56
57
    /**
58
     * The image bits information
59
     * @var int|null
60
     */
61
    protected ?int $bits = null;
62
63
    /**
64
     * The image mime type
65
     * @var string
66
     */
67
    protected string $mimetype = '';
68
69
    /**
70
     * The GD image resource instance
71
     * @var GdImage
72
     */
73
    protected GdImage $image;
74
75
    /**
76
     * Create new instance
77
     * @param string $filePath
78
     */
79
    public function __construct(protected string $filePath)
80
    {
81
        if (extension_loaded('gd') === false) {
82
            throw new RuntimeException('PHP GD extension is not installed or enabled');
83
        }
84
85
        // Now load image
86
        $this->loadImage($filePath);
87
    }
88
89
    /**
90
     * Resize the current image
91
     * @param int $width
92
     * @param int $height
93
     * @param bool|null $useWidth if "true" will use the width as scale factor, if "false"
94
     * will use the height if "null" will use the minimum scale between width and height.
95
     * @return void
96
     */
97
    public function resize(int $width = 0, int $height = 0, ?bool $useWidth = null): void
98
    {
99
        if ($width <= 0) {
100
            $width = $this->width;
101
        }
102
103
        if ($height <= 0) {
104
            $height = $this->height;
105
        }
106
107
        // imagecreatetruecolor need minimum width and height to be >= 1
108
        if ($width < 1 || $height < 1) {
109
            return;
110
        }
111
112
        $scale = 1;
113
        $scaleWidth = (int)($width / $this->width);
114
        $scaleHeight = (int)($height / $this->height);
115
        if ($useWidth) {
116
            $scale = $scaleWidth;
117
        } elseif ($useWidth === false) {
118
            $scale = $scaleHeight;
119
        } else {
120
            $scale = (int) min($scaleWidth, $scaleHeight);
121
        }
122
123
        if (
124
            $scale === 1 &&
125
            $scaleWidth === $scaleHeight &&
126
            $this->mimetype !== 'image/png'
127
        ) {
128
            return;
129
        }
130
131
        $newWidth = (int)($this->width * $scale);
132
        $newHeight = (int)($this->height * $scale);
133
        $xpos = (int)(($width - $newWidth) / 2);
134
        $ypos = (int)(($height - $newHeight) / 2);
135
        $oldImage = $this->image;
136
        $this->image = imagecreatetruecolor($width, $height);
0 ignored issues
show
Documentation Bug introduced by
It seems like imagecreatetruecolor($width, $height) can also be of type resource. However, the property $image is declared as type GdImage. 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...
137
        if ($this->mimetype === 'image/png') {
138
            imagealphablending($this->image, false);
139
            imagesavealpha($this->image, false);
140
            $background = imagecolorallocatealpha($this->image, 255, 255, 255, 127);
141
            if ($background === false) {
142
                throw new RuntimeException('Can not allocate color alpha for PNG image');
143
            }
144
            imagecolortransparent($this->image, $background);
145
        } else {
146
            $background = imagecolorallocate($this->image, 255, 255, 255);
147
            if ($background === false) {
148
                throw new RuntimeException('Can not allocate color for image resize');
149
            }
150
        }
151
        imagefilledrectangle($this->image, 0, 0, $width, $height, $background);
152
        imagecopyresampled(
153
            $this->image,
154
            $oldImage,
155
            $xpos,
156
            $ypos,
157
            0,
158
            0,
159
            $newWidth,
160
            $newHeight,
161
            $this->width,
162
            $this->height
163
        );
164
        imagedestroy($oldImage);
165
        $this->width = $width;
166
        $this->height = $height;
167
    }
168
169
    /**
170
     * Write the watermark in the given image
171
     * @param Image $watermark
172
     * @param string $position
173
     * @return void
174
     */
175
    public function watermark(Image $watermark, string $position = 'bottomright'): void
176
    {
177
        $watermarkPosX = 0;
178
        $watermarkPosY = 0;
179
180
        $positionMaps = [
181
            'topleft' => [
182
                0,
183
                0
184
            ],
185
            'topcenter' => [
186
                intval(($this->width - $watermark->getWidth()) / 2),
187
                0
188
            ],
189
            'topright' => [
190
                $this->width - $watermark->getWidth(),
191
                0
192
            ],
193
            'middleleft' => [
194
                0,
195
                intval(($this->height - $watermark->getHeight()) / 2)
196
            ],
197
            'middlecenter' => [
198
                intval(($this->width - $watermark->getWidth()) / 2),
199
                intval(($this->height - $watermark->getHeight()) / 2)
200
            ],
201
            'middleright' => [
202
                $this->width - $watermark->getWidth(),
203
                intval(($this->height - $watermark->getHeight()) / 2)
204
            ],
205
            'bottomleft' => [
206
                0,
207
                $this->height - $watermark->getHeight()
208
            ],
209
            'bottomcenter' => [
210
                intval(($this->width - $watermark->getWidth()) / 2),
211
                $this->height - $watermark->getHeight()
212
            ],
213
            'bottomright' => [
214
                $this->width - $watermark->getWidth(),
215
                $this->height - $watermark->getHeight()
216
            ],
217
        ];
218
219
        if (isset($positionMaps[$position])) {
220
            $watermarkPosX = $positionMaps[$position][0];
221
            $watermarkPosY = $positionMaps[$position][1];
222
        }
223
        imagealphablending($this->image, true);
224
        imagesavealpha($this->image, true);
225
        imagecopy(
226
            $this->image,
227
            $watermark->getImage(),
228
            $watermarkPosX,
229
            $watermarkPosY,
230
            0,
231
            0,
232
            $watermark->getWidth(),
233
            $watermark->getHeight()
234
        );
235
        imagedestroy($watermark->getImage());
236
    }
237
238
    /**
239
     * Crop the current image
240
     * @param int $topX
241
     * @param int $topY
242
     * @param int $bottomX
243
     * @param int $bottomY
244
     * @return void
245
     */
246
    public function crop(int $topX, int $topY, int $bottomX, int $bottomY): void
0 ignored issues
show
Unused Code introduced by
The parameter $bottomX is not used and could be removed. ( Ignorable by Annotation )

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

246
    public function crop(int $topX, int $topY, /** @scrutinizer ignore-unused */ int $bottomX, int $bottomY): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
247
    {
248
        $oldImage = $this->image;
249
        $width = $bottomY - $topX;
250
        $height = $bottomY - $topY;
251
252
        // imagecreatetruecolor need minimum width and height to be >= 1
253
        if ($width < 1 || $height < 1) {
254
            return;
255
        }
256
257
        $this->image = imagecreatetruecolor($width, $height);
0 ignored issues
show
Documentation Bug introduced by
It seems like imagecreatetruecolor($width, $height) can also be of type resource. However, the property $image is declared as type GdImage. 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...
258
        imagecopy(
259
            $this->image,
260
            $oldImage,
261
            0,
262
            0,
263
            $topX,
264
            $topY,
265
            $this->width,
266
            $this->height
267
        );
268
        imagedestroy($oldImage);
269
        $this->width = $width;
270
        $this->height = $height;
271
    }
272
273
    /**
274
     * Rotate the current image
275
     * @param float $degree
276
     * @param string $color
277
     * @return void
278
     */
279
    public function rotate(float $degree, string $color = 'ffffff'): void
280
    {
281
        [$red, $green, $blue] = self::htmlToRGBColor($color);
282
        $background = imagecolorallocate($this->image, $red, $green, $blue);
283
        if ($background === false) {
284
            throw new RuntimeException('Can not allocate color for image rotation');
285
        }
286
        $image = imagerotate($this->image, $degree, $background);
287
        if ($image === false) {
288
            throw new RuntimeException('Can not rotate the current image');
289
        }
290
        $this->image = $image;
0 ignored issues
show
Documentation Bug introduced by
It seems like $image can also be of type resource. However, the property $image is declared as type GdImage. 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...
291
        $this->width = imagesx($this->image);
292
        $this->height = imagesy($this->image);
293
    }
294
295
    /**
296
     * Write text in the current image
297
     * @param string $text
298
     * @param int $x
299
     * @param int $y
300
     * @param int $size
301
     * @param string $color
302
     * @return void
303
     */
304
    public function text(
305
        string $text,
306
        int $x = 0,
307
        int $y = 0,
308
        int $size = 5,
309
        string $color = '000000'
310
    ): void {
311
        [$red, $green, $blue] = self::htmlToRGBColor($color);
312
        $background = imagecolorallocate($this->image, $red, $green, $blue);
313
        if ($background === false) {
314
            throw new RuntimeException('Can not allocate color for image text');
315
        }
316
        imagestring($this->image, $size, $x, $y, $text, $background);
317
    }
318
319
    public function merge(
320
        Image $merge,
321
        int $x = 0,
322
        int $y = 0,
323
        int $opacity = 100
324
    ): void {
325
        imagecopymerge(
326
            $this->image,
327
            $merge->getImage(),
328
            $x,
329
            $y,
330
            0,
331
            0,
332
            $merge->getWidth(),
333
            $merge->getHeight(),
334
            $opacity
335
        );
336
    }
337
338
    /**
339
     * Apply filter to the current image
340
     * @param int $filter
341
     * @param array<mixed>|int|float|bool ...$args
342
     * @return void
343
     */
344
    public function filter(int $filter, array|int|float|bool ...$args): void
345
    {
346
        imagefilter($this->image, $filter, $args);
347
    }
348
349
    /**
350
     * Save the current image into the given path
351
     * @param string $filePath
352
     * @param int $quality only used for JPEG image
353
     * @return void
354
     */
355
    public function save(string $filePath, int $quality = 90): void
356
    {
357
        $extension = pathinfo($filePath, PATHINFO_EXTENSION);
358
        if (in_array($extension, ['jpeg', 'jpg'])) {
359
            imagejpeg($this->image, $filePath, $quality);
360
        } elseif ($extension === 'png') {
361
            imagepng($this->image, $filePath);
362
        } elseif ($extension === 'gif') {
363
            imagegif($this->image, $filePath);
364
        }
365
366
        imagedestroy($this->image);
367
    }
368
369
    /**
370
     * Return the current width
371
     * @return int
372
     */
373
    public function getWidth(): int
374
    {
375
        return $this->width;
376
    }
377
378
    /**
379
     * Return the current height
380
     * @return int
381
     */
382
    public function getHeight(): int
383
    {
384
        return $this->height;
385
    }
386
387
    /**
388
     * Return the image mime type
389
     * @return string
390
     */
391
    public function getMimetype(): string
392
    {
393
        return $this->mimetype;
394
    }
395
396
397
    /**
398
     * Return the current image instance
399
     * @return GdImage
400
     */
401
    public function getImage(): GdImage
402
    {
403
        return $this->image;
404
    }
405
406
    /**
407
     * Convert the HTML color to RGB
408
     * @param string $color
409
     * @return array{0: int<0, 255>, 1:int<0, 255>, 2:int<0, 255>} the color with each index R, G, B
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{0: int<0, 255>, 1:...0, 255>, 2:int<0, 255>} at position 4 could not be parsed: Expected '}' at position 4, but found 'int'.
Loading history...
410
     */
411
    public static function htmlToRGBColor(string $color): array
412
    {
413
        if (isset($color[0]) && $color[0] === '#') {
414
            $color = substr($color, 1);
415
        }
416
417
        if (strlen($color) === 6) { // like ff34dq
418
            [$r, $g, $b] = [
419
                $color[0] . $color[1],
420
                $color[2] . $color[3],
421
                $color[4] . $color[5],
422
            ];
423
        } elseif (strlen($color) === 3) { // like f4c => ff44cc
424
            [$r, $g, $b] = [
425
                $color[0] . $color[0],
426
                $color[1] . $color[1],
427
                $color[2] . $color[2],
428
            ];
429
        } else {
430
            throw new RuntimeException(sprintf('Invalid HTML color [%s] provided', $color));
431
        }
432
433
        $r = (int) hexdec($r);
434
        if ($r < 0 || $r > 255) {
435
            $r = 0;
436
        }
437
438
        $g = (int) hexdec($g);
439
        if ($g < 0 || $g > 255) {
440
            $g = 0;
441
        }
442
443
        $b = (int) hexdec($b);
444
        if ($b < 0 || $b > 255) {
445
            $b = 0;
446
        }
447
448
        return [$r, $g, $b];
449
    }
450
451
    /**
452
     * Load the image into GD instance
453
     * @param string $filePath
454
     * @return void
455
     */
456
    protected function loadImage(string $filePath): void
457
    {
458
        if (file_exists($filePath) === false) {
459
            throw new RuntimeException(sprintf(
460
                'The image file [%s] to load does not exist',
461
                $filePath
462
            ));
463
        }
464
        $info = getimagesize($filePath);
465
        if ($info === false) {
466
            throw new RuntimeException(sprintf(
467
                'Can not load image file [%s]',
468
                $filePath
469
            ));
470
        }
471
        $this->width = $info[0];
472
        $this->height = $info[1];
473
        $this->bits = $info['bits'] ?? null;
474
        $this->mimetype = $info['mime'];
475
476
        if ($this->mimetype === 'image/gif') {
477
            $image = imagecreatefromgif($filePath);
478
        } elseif ($this->mimetype === 'image/png') {
479
            $image = imagecreatefrompng($filePath);
480
        } elseif ($this->mimetype === 'image/jpeg' || $this->mimetype === 'image/jpg') {
481
            $image = imagecreatefromjpeg($filePath);
482
        } else {
483
            throw new RuntimeException('The image MIME type cannot be determined');
484
        }
485
486
        if ($image === false) {
487
            throw new RuntimeException(sprintf('Can not create image using file [%s]', $filePath));
488
        }
489
        $this->image = $image;
0 ignored issues
show
Documentation Bug introduced by
It seems like $image can also be of type resource. However, the property $image is declared as type GdImage. 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...
490
    }
491
}
492