Completed
Push — v3.x ( 8b6982 )
by Oscar
01:53
created

GdAdapter::createImage()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
c 0
b 0
f 0
rs 9.2568
cc 5
nc 6
nop 3
1
<?php
2
declare(strict_types = 1);
3
4
namespace Imagecow\Adapters;
5
6
use Imagecow\ImageException;
7
8
/**
9
 * GD library.
10
 */
11
final class GdAdapter implements AdapterInterface
12
{
13
    use CommonTrait;
14
15
    public static $fallbackCropMethods = [
16
        'Entropy' => ['center', 'middle'],
17
        'Balanced' => ['center', 'middle'],
18
    ];
19
20
    private $image;
21
    private $type;
22
23
    public static function checkCompatibility(): bool
24
    {
25
        return extension_loaded('gd');
26
    }
27
28
    public static function createFromFile(string $filename): AdapterInterface
29
    {
30
        $data = getImageSize($filename);
31
32
        if (empty($data) || !is_array($data)) {
33
            throw new ImageException("The image file '{$filename}' cannot be loaded");
34
        }
35
36
        $function = 'imagecreatefrom'.image_type_to_extension($data[2], false);
37
38
        if (function_exists($function)) {
39
            return new static($function($filename), $data[2]);
40
        }
41
    }
42
43
    public static function createFromString(string $string): AdapterInterface
44
    {
45
        if (($image = imagecreatefromstring($string))) {
46
            return new static($image);
47
        }
48
49
        throw new ImageException('Error creating the image from string');
50
    }
51
52
    /**
53
     * @param resource $image The Gd resource.
54
     */
55
    public function __construct($image, int $type = null)
56
    {
57
        $this->image = $image;
58
        $this->type = isset($type) ? $type : IMAGETYPE_PNG;
59
60
        imagealphablending($this->image, true);
61
        imagesavealpha($this->image, true);
62
    }
63
64
    public function __destruct()
65
    {
66
        imagedestroy($this->image);
67
    }
68
69
    public function flip()
70
    {
71
        imageflip($this->image, IMG_FLIP_VERTICAL);
72
    }
73
74
    public function flop()
75
    {
76
        imageflip($this->image, IMG_FLIP_HORIZONTAL);
77
    }
78
79
    public function save(string $filename)
80
    {
81
        $extension = image_type_to_extension($this->type, false);
82
        $function = 'image'.$extension;
83
84
        if (!function_exists($function) || ($function($this->image, $filename) === false)) {
85
            throw new ImageException("The image format '{$extension}' cannot be saved to '{$filename}'");
86
        }
87
    }
88
89
    /**
90
     * Gets the original image object.
91
     *
92
     * @return resource
93
     */
94
    public function getImage()
95
    {
96
        return $this->image;
97
    }
98
99
    public function getString(): string
100
    {
101
        $extension = image_type_to_extension($this->type, false);
102
        $function = 'image'.$extension;
103
104
        if (!function_exists($function)) {
105
            throw new ImageException("The image format '{$extension}' cannot be exported");
106
        }
107
108
        ob_start();
109
110
        if ($extension === 'jpeg') {
111
            $function($this->image, null, $this->quality);
112
        } else {
113
            $function($this->image);
114
        }
115
116
        return ob_get_clean();
117
    }
118
119
    public function getMimeType(): string
120
    {
121
        return image_type_to_mime_type($this->type);
122
    }
123
124
    public function getWidth(): int
125
    {
126
        return imagesx($this->image);
127
    }
128
129
    public function getHeight(): int
130
    {
131
        return imagesy($this->image);
132
    }
133
134
    public function format(string $format)
135
    {
136
        switch (strtolower($format)) {
137
            case 'jpg':
138
            case 'jpeg':
139
                $width = $this->getWidth();
140
                $height = $this->getHeight();
141
142
                if (($image = imagecreatetruecolor($width, $height)) === false) {
143
                    throw new ImageException('Error creating a image');
144
                }
145
146
                if (imagesavealpha($image, true) === false) {
147
                    throw new ImageException('Error saving the alpha chanel of the image');
148
                }
149
150
                if (isset($this->background[3])) {
151
                    $background = imagecolorallocatealpha($image, $this->background[0], $this->background[1], $this->background[2], $this->background[3]);
152
                } else {
153
                    $background = imagecolorallocate($image, $this->background[0], $this->background[1], $this->background[2]);
154
                }
155
156
                if (imagefill($image, 0, 0, $background) === false) {
157
                    throw new ImageException('Error filling the image');
158
                }
159
160
                imagecopy($image, $this->image, 0, 0, 0, 0, $width, $height);
161
162
                imagedestroy($this->image);
163
                $this->image = $image;
164
                $this->type = IMAGETYPE_JPEG;
165
                break;
166
167
            case 'gif':
168
                $this->type = IMAGETYPE_GIF;
169
                break;
170
171
            case 'png':
172
                $this->type = IMAGETYPE_PNG;
173
                break;
174
175
            case 'webp':
176
                $this->type = IMAGETYPE_WEBP;
177
                break;
178
179
            default:
180
                throw new ImageException("The image format '{$format}' is not valid");
181
        }
182
    }
183
184
    /**
185
     * imagescale() is not used due a weird black border:
186
     * https://bugs.php.net/bug.php?id=73281
187
     */
188
    public function resize(int $maxWidth, int $maxHeight)
189
    {
190
        $image = $this->createImage($maxWidth, $maxHeight, array(0, 0, 0, 127));
191
192
        if (imagecopyresampled($image, $this->image, 0, 0, 0, 0, $maxWidth, $maxHeight, $this->getWidth(), $this->getHeight()) === false) {
193
            throw new ImageException('Error resizing the image');
194
        }
195
196
        imagedestroy($this->image);
197
        $this->image = $image;
198
    }
199
200
    public function getCropOffsets(int $width, int $height, string $method): array
201
    {
202
        if (empty(static::$fallbackCropMethods[$method])) {
203
            throw new ImageException("The crop method '$method' is not available for Gd");
204
        }
205
206
        return static::$fallbackCropMethods[$method];
207
    }
208
209
    public function crop(int $width, int $height, int $x, int $y)
210
    {
211
        $crop = [
212
            'width' => $width,
213
            'height' => $height,
214
            'x' => $x,
215
            'y' => $y,
216
        ];
217
218
        if (($image = imagecrop($this->image, $crop)) === false) {
219
            throw new ImageException('Error cropping the image');
220
        }
221
222
        imagedestroy($this->image);
223
        $this->image = $image;
224
    }
225
226
    public function rotate(int $angle)
227
    {
228
        $background = imagecolorallocatealpha($this->image, 0, 0, 0, 127);
229
230
        if ($background === false || ($image = imagerotate($this->image, -$angle, $background)) === false) {
231
            throw new ImageException('Error rotating the image');
232
        }
233
234
        imagecolortransparent($image, imagecolorallocatealpha($image, 0, 0, 0, 127));
235
236
        imagedestroy($this->image);
237
        $this->image = $image;
238
    }
239
240
    public function blur(int $loops)
241
    {
242
        $width = $this->getWidth();
243
        $height = $this->getHeight();
244
        $loops *= 10;
245
246
        $this->resize($width / 5, $height / 5);
247
248
        for ($x = 0; $x < $loops; $x++) {
249
            if (($x % 4) === 0) {
250
                imagefilter($this->image, IMG_FILTER_SMOOTH, -4);
251
                imagefilter($this->image, IMG_FILTER_BRIGHTNESS, 2);
252
            }
253
254
            imagefilter($this->image, IMG_FILTER_GAUSSIAN_BLUR);
255
        }
256
257
        $this->resize($width, $height);
258
    }
259
260
    public function watermark(LibInterface $image, $x, $y)
261
    {
262
        if (!($image instanceof self)) {
263
            $image = self::createFromString($image->getString());
264
        }
265
266
        imagecopy($this->image, $image->getImage(), $x, $y, 0, 0, $image->getWidth(), $image->getHeight());
267
    }
268
269
    public function opacity(int $opacity)
270
    {
271
        if ($opacity >= 100 || $opacity < 0) {
272
            return;
273
        }
274
275
        $this->format('png');
276
277
        $opacity = $opacity / 100;
278
279
        $width = $this->getWidth();
280
        $height = $this->getHeight();
281
282
        imagealphablending($this->image, false);
283
284
        for ($x = 0; $x < $width; ++$x) {
285
            for ($y = 0; $y < $height; ++$y) {
286
                $color = imagecolorat($this->image, $x, $y);
287
                $alpha = 127 - (($color >> 24) & 0xFF);
288
289
                if ($alpha <= 0) {
290
                    continue;
291
                }
292
293
                $color = ($color & 0xFFFFFF) | ((int) round(127 - $alpha * $opacity) << 24);
294
295
                imagesetpixel($this->image, $x, $y, $color);
296
            }
297
        }
298
    }
299
300
    public function setProgressive(bool $progressive)
301
    {
302
        imageinterlace($this->image, $progressive);
303
    }
304
305
    /**
306
     * Creates a new truecolor image
307
     *
308
     * @return resource
309
     */
310
    private function createImage(int $width, int $height, array $background = [0, 0, 0])
311
    {
312
        if (($image = imagecreatetruecolor($width, $height)) === false) {
313
            throw new ImageException('Error creating a image');
314
        }
315
316
        if (imagesavealpha($image, true) === false) {
317
            throw new ImageException('Error saving the alpha chanel of the image');
318
        }
319
320
        if (isset($background[3])) {
321
            $background = imagecolorallocatealpha($image, $background[0], $background[1], $background[2], $background[3]);
322
        } else {
323
            $background = imagecolorallocate($image, $background[0], $background[1], $background[2]);
324
        }
325
326
        if (imagefill($image, 0, 0, $background) === false) {
327
            throw new ImageException('Error filling the image');
328
        }
329
330
        return $image;
331
    }
332
}
333