Passed
Pull Request — master (#19)
by Arthur
05:00
created

ImageUtils::imageDestroy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 10
1
<?php
2
3
namespace WebThumbnailer\Utils;
4
5
use WebThumbnailer\Exception\ImageConvertException;
6
use WebThumbnailer\Exception\NotAnImageException;
7
8
/**
9
 * Class ImageUtils
10
 *
11
 * Util class to manipulate GD images.
12
 *
13
 * @package WebThumbnailer\Utils
14
 */
15
class ImageUtils
16
{
17
    /**
18
     * Generate a clean PNG thumbnail from given image resource.
19
     *
20
     * It makes sure the downloaded image is really an image,
21
     * doesn't contain funny stuff, and it resize it to a standard size.
22
     * Resizing conserves proportions.
23
     *
24
     * @param string   $imageStr  Source image.
25
     * @param string   $target    Path where the generated thumb will be saved.
26
     * @param int      $maxWidth  Max width for the generated thumb.
27
     * @param int      $maxHeight Max height for the generated thumb.
28
     * @param bool     $crop      Will crop the image to a fixed size if true. Height AND width must be provided.
29
     *
30
     * @throws NotAnImageException   The given resource isn't an image.
31
     * @throws ImageConvertException Another error occured.
32
     */
33
    public static function generateThumbnail($imageStr, $target, $maxWidth, $maxHeight, $crop = false)
34
    {
35
        if (! touch($target)) {
36
            throw new ImageConvertException('Target file is not writable.');
37
        }
38
39
        if ($crop && ($maxWidth == 0  || $maxHeight == 0)) {
40
            throw new ImageConvertException('Both width and height must be provided for cropping');
41
        }
42
43
        $sourceImg = static::imageCreateFromString($imageStr);
44
        if ($sourceImg === false) {
45
            throw new NotAnImageException();
46
        }
47
48
        $originalWidth = imagesx($sourceImg);
49
        $originalHeight = imagesy($sourceImg);
50
        if ($maxWidth > $originalWidth) {
51
            $maxWidth = $originalWidth;
52
        }
53
        if ($maxHeight > $originalHeight) {
54
            $maxHeight = $originalHeight;
55
        }
56
57
        list($finalWidth, $finalHeight) = self::calcNewSize(
58
            $originalWidth,
59
            $originalHeight,
60
            $maxWidth,
61
            $maxHeight,
62
            $crop
63
        );
64
65
        $targetImg = imagecreatetruecolor($finalWidth, $finalHeight);
66
        if ($targetImg === false) {
67
            throw new ImageConvertException('Could not generate the thumbnail from source image.');
68
        }
69
70
        if (! imagecopyresized(
71
            $targetImg,
72
            $sourceImg,
73
            0,
74
            0,
75
            0,
76
            0,
77
            $finalWidth,
78
            $finalHeight,
79
            $originalWidth,
80
            $originalHeight
81
        )
82
        ) {
83
            static::imageDestroy($sourceImg);
84
            static::imageDestroy($targetImg);
85
            throw new ImageConvertException('Could not generate the thumbnail from source image.');
86
        }
87
88
        if ($crop) {
89
            $targetImg = imagecrop($targetImg, [
90
                'x' => $finalWidth >= $finalHeight ? ($finalWidth - $maxWidth) / 2 : 0,
91
                'y' => $finalHeight <= $finalWidth ? ($finalHeight - $maxHeight) / 2 : 0,
92
                'width' => $maxWidth,
93
                'height' => $maxHeight
94
            ]);
95
        }
96
97
        imagedestroy($sourceImg);
98
        imagejpeg($targetImg, $target);
0 ignored issues
show
Bug introduced by
It seems like $targetImg can also be of type boolean; however, parameter $image of imagejpeg() 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

98
        imagejpeg(/** @scrutinizer ignore-type */ $targetImg, $target);
Loading history...
99
        imagedestroy($targetImg);
0 ignored issues
show
Bug introduced by
It seems like $targetImg can also be of type boolean; however, parameter $image of imagedestroy() 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

99
        imagedestroy(/** @scrutinizer ignore-type */ $targetImg);
Loading history...
100
    }
101
102
    /**
103
     * Calculate image new size to keep proportions depending on actual image size
104
     * and max width/height settings.
105
     *
106
     * @param int  $originalWidth  Image original width
107
     * @param int  $originalHeight Image original height
108
     * @param int  $maxWidth       Target image maximum width
109
     * @param int  $maxHeight      Target image maximum height
110
     * @param bool $crop           Is cropping enabled
111
     *
112
     * @return array [final width, final height]
113
     *
114
     * @throws ImageConvertException At least maxwidth or maxheight needs to be defined
115
     */
116
    public static function calcNewSize($originalWidth, $originalHeight, $maxWidth, $maxHeight, $crop)
117
    {
118
        if (empty($maxHeight) && empty($maxWidth)) {
119
            throw new ImageConvertException('At least maxwidth or maxheight needs to be defined.');
120
        }
121
        $diffWidth = !empty($maxWidth) ? $originalWidth - $maxWidth : false;
122
        $diffHeight = !empty($maxHeight) ? $originalHeight - $maxHeight : false;
123
124
        if (($diffHeight === false && $diffWidth !== false)
125
            || ($diffWidth > $diffHeight && ! $crop)
126
            || ($diffWidth < $diffHeight && $crop)
127
        ) {
128
            $finalWidth = $maxWidth;
129
            $finalHeight = $originalHeight * ($finalWidth / $originalWidth);
130
        } else {
131
            $finalHeight = $maxHeight;
132
            $finalWidth = $originalWidth * ($finalHeight / $originalHeight);
133
        }
134
135
        return [$finalWidth, $finalHeight];
136
    }
137
138
    /**
139
     * Check if a file extension is an image.
140
     *
141
     * @param string $ext file extension.
142
     *
143
     * @return bool true if it's an image extension, false otherwise.
144
     */
145
    public static function isImageExtension($ext)
146
    {
147
        $supportedImageFormats = ['png', 'jpg', 'jpeg', 'svg'];
148
        return in_array($ext, $supportedImageFormats);
149
    }
150
151
    /**
152
     * Check if a string is an image.
153
     *
154
     * @param string $content String to check.
155
     *
156
     * @return bool True if the content is image, false otherwise.
157
     */
158
    public static function isImageString($content)
159
    {
160
        return static::imageCreateFromString($content) !== false;
161
    }
162
163
    /**
164
     * With custom error handlers, @ does not stop the warning to being thrown.
165
     *
166
     * @param string $content
167
     *
168
     * @return resource|false
169
     */
170
    protected static function imageCreateFromString($content)
171
    {
172
        try {
173
            return @imagecreatefromstring($content);
174
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
175
        }
176
177
        return false;
178
    }
179
180
    /**
181
     * With custom error handlers, @ does not stop the warning to being thrown.
182
     *
183
     * @param resource $image
184
     *
185
     * @return bool
186
     */
187
    protected static function imageDestroy($image)
188
    {
189
        try {
190
            return @imagedestroy($image);
191
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
192
        }
193
194
        return false;
195
    }
196
}
197