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

ImagickAdapter::getString()   A

Complexity

Conditions 3
Paths 3

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.568
cc 3
nc 3
nop 0
1
<?php
2
declare(strict_types = 1);
3
4
namespace Imagecow\Adapters;
5
6
use Imagick;
7
use ImagickPixel;
8
use Imagecow\ImageException;
9
10
/**
11
 * Imagick library.
12
 */
13
final class ImagickAdapter implements AdapterInterface
14
{
15
    use CommonTrait;
16
17
    private $image;
18
19
    public static function checkCompatibility(): bool
20
    {
21
        return extension_loaded('imagick');
22
    }
23
24
    public static function createFromFile(string $filename): AdapterInterface
25
    {
26
        $imagick = new Imagick();
27
28
        if ($imagick->readImage($filename) !== true) {
29
            throw new ImageException("The image file '{$filename}' cannot be loaded");
30
        }
31
32
        return new static($imagick);
33
    }
34
35
    public static function createFromString(string $string): AdapterInterface
36
    {
37
        $imagick = new Imagick();
38
39
        $imagick->readImageBlob($string);
40
41
        return new static($imagick);
42
    }
43
44
    public function __construct(Imagick $image)
45
    {
46
        $this->image = $image;
47
48
        //Convert CMYK to RGB
49
        if ($this->image->getImageColorspace() !== Imagick::COLORSPACE_CMYK) {
50
            return $this;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
51
        }
52
53
        $profiles = $this->image->getImageProfiles('*', false);
54
55
        if (array_search('icc', $profiles) === false) {
56
            $this->image->profileImage('icc', file_get_contents(__DIR__.'/icc/us_web_uncoated.icc'));
57
        }
58
59
        $this->image->profileImage('icm', file_get_contents(__DIR__.'/icc/srgb.icm'));
60
        $this->image->transformImageColorspace(Imagick::COLORSPACE_SRGB);
61
    }
62
63
    public function __destruct()
64
    {
65
        $this->image->destroy();
66
    }
67
68
    public function flip()
69
    {
70
        if ($this->image->flipImage() !== true) {
71
            throw new ImageException('There was an error on flip the image');
72
        }
73
    }
74
75
    public function flop()
76
    {
77
        if ($this->image->flopImage() !== true) {
78
            throw new ImageException('There was an error on flop the image');
79
        }
80
    }
81
82
    public function save(string $filename)
83
    {
84
        $image = $this->getCompressed();
85
86
        if ($this->animated) {
87
            if (!($fp = fopen($filename, 'w'))) {
88
                throw new ImageException("The image file '{$filename}' cannot be saved");
89
            }
90
91
            $image->writeImagesFile($fp);
92
93
            fclose($fp);
94
        } elseif (!$image->writeImage($filename)) {
95
            throw new ImageException("The image file '{$filename}' cannot be saved");
96
        }
97
    }
98
99
    public function getImage(): Imagick
100
    {
101
        return $this->image;
102
    }
103
104
    public function getString(): string
105
    {
106
        $image = $this->getCompressed();
107
108
        if (!$this->animated) {
109
            return $image->getImageBlob();
110
        }
111
112
        if (!($fp = fopen($file = tempnam(sys_get_temp_dir(), 'imagick'), 'w'))) {
113
            throw new ImageException('Cannot create a temp file to generate the string data image');
114
        }
115
116
        $image->writeImagesFile($fp);
117
118
        fclose($fp);
119
120
        $string = file_get_contents($file);
121
122
        unlink($file);
123
124
        return $string;
125
    }
126
127
    public function getMimeType(): string
128
    {
129
        $format = strtolower($this->image->getImageFormat());
130
131
        if (in_array($format, ['jpeg', 'jpg', 'gif', 'png', 'webp'], true)) {
132
            return "image/$format";
133
        }
134
    }
135
136
    public function getWidth(): int
137
    {
138
        if ($this->animated) {
139
            return $this->image->coalesceImages()->getImageWidth();
140
        } else {
141
            return $this->image->getImageWidth();
142
        }
143
    }
144
145
    public function getHeight(): int
146
    {
147
        if ($this->animated) {
148
            return $this->image->coalesceImages()->getImageHeight();
149
        } else {
150
            return $this->image->getImageHeight();
151
        }
152
    }
153
154
    public function format(string $format): string
155
    {
156
        if (preg_match('/jpe?g/i', $format)) {
157
            list($r, $g, $b) = $this->background;
158
159
            $this->image->setImageBackgroundColor("rgb($r,$g,$b)");
160
            $this->image = $this->image->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
161
        }
162
163
        if ($this->image->setImageFormat($format) !== true) {
164
            throw new ImageException("The image format '{$format}' is not valid");
165
        }
166
    }
167
168
    public function resize(int $maxWidth, int $maxHeight)
169
    {
170
        if ($this->animated) {
171
            $this->image = $this->image->coalesceImages();
172
173
            foreach ($this->image as $frame) {
174
                $frame->scaleImage($maxWidth, $maxHeight);
175
            }
176
177
            $this->image = $this->image->deconstructImages();
178
        } else {
179
            if ($this->image->scaleImage($maxWidth, $maxHeight) !== true) {
180
                throw new ImageException('There was an error resizing the image');
181
            }
182
183
            $this->image->setImagePage(0, 0, 0, 0);
184
        }
185
    }
186
187
    public function getCropOffsets(int $width, int $height, string $method): array
188
    {
189
        $class = 'Imagecow\\Crops\\'.ucfirst(strtolower($method));
190
191
        if (!class_exists($class)) {
192
            throw new ImageException("The crop method '$method' is not available for Imagick");
193
        }
194
195
        return $class::getOffsets($this->image, $width, $height);
196
    }
197
198
    public function crop(int $width, int $height, int $x, int $y)
199
    {
200
        if ($this->animated) {
201
            $this->image = $this->image->coalesceImages();
202
203
            foreach ($this->image as $frame) {
204
                $frame->cropImage($width, $height, $x, $y);
205
                $frame->setImagePage(0, 0, 0, 0);
206
            }
207
208
            $this->image = $this->image->deconstructImages();
209
        } else {
210
            if ($this->image->cropImage($width, $height, $x, $y) !== true) {
211
                throw new ImageException('There was an error cropping the image');
212
            }
213
214
            $this->image->setImagePage(0, 0, 0, 0);
215
        }
216
    }
217
218
    public function rotate(int $angle)
219
    {
220
        if ($this->image->rotateImage(new ImagickPixel('#FFFFFF'), $angle) !== true) {
221
            throw new ImageException('There was an error rotating the image');
222
        }
223
224
        $this->image->setImagePage(0, 0, 0, 0);
225
    }
226
227
    public function blur(int $loops)
228
    {
229
        $width = $this->getWidth();
230
        $height = $this->getHeight();
231
232
        $this->resize($width / 5, $height / 5);
233
234
        for ($i = 0; $i < $loops; $i++) {
235
            $this->image->blurImage(5, 100);
236
        }
237
238
        $this->resize($width, $height);
239
240
        $this->image->blurImage(10, 100);
241
    }
242
243
    /**
244
     * Returns a copy of the image compressed and ready to save or print.
245
     */
246
    private function getCompressed(): Imagick
247
    {
248
        $image = $this->image;
249
250
        if ($this->animated) {
251
            $image = $image->coalesceImages();
252
253
            foreach ($image as $frame) {
254
                $frame->stripImage();
255
                $frame->setImageUnits(1);
256
                $frame->setImageCompressionQuality($this->quality);
257
            }
258
259
            return $image->deconstructImages();
260
        }
261
262
        $format = strtolower($image->getImageFormat());
263
264
        $image->stripImage();
265
        $image->setImageUnits(1);
266
        $image->setImageCompressionQuality($this->quality);
267
268
        switch ($format) {
269
            case 'jpeg':
270
                $image->setInterlaceScheme(Imagick::INTERLACE_JPEG);
271
                $image->setImageCompression(Imagick::COMPRESSION_JPEG);
272
                break;
273
274
            case 'gif':
275
                $image->setInterlaceScheme(Imagick::INTERLACE_GIF);
276
                break;
277
278
            case 'png':
279
                $image->setInterlaceScheme(Imagick::INTERLACE_PNG);
280
                break;
281
        }
282
283
        return $image;
284
    }
285
286
    public function watermark(LibInterface $image, $x, $y)
287
    {
288
        if (!($image instanceof self)) {
289
            $image = self::createFromString($image->getString());
290
        }
291
292
        $this->image->compositeImage($image->getImage(), Imagick::COMPOSITE_DISSOLVE, $x, $y);
293
    }
294
295
    public function opacity(int $opacity)
296
    {
297
        if ($opacity >= 100 || $opacity < 0) {
298
            return;
299
        }
300
301
        if ($this->image->getImageAlphaChannel() !== Imagick::ALPHACHANNEL_ACTIVATE) {
302
            $this->image->setImageAlphaChannel(Imagick::ALPHACHANNEL_OPAQUE);
303
        }
304
305
        // NOTE: Using setImageOpacity will destroy current alpha channels!
306
        $this->image->evaluateImage(Imagick::EVALUATE_MULTIPLY, $opacity / 100, Imagick::CHANNEL_ALPHA);
307
    }
308
309
    public function setProgressive(bool $progressive)
310
    {
311
        if ($progressive) {
312
            $this->image->setInterlaceScheme(Imagick::INTERLACE_PLANE);
313
        } else {
314
            $this->image->setInterlaceScheme(Imagick::INTERLACE_NO);
315
        }
316
    }
317
}
318