Image::resizeMulti()   F
last analyzed

Complexity

Conditions 34
Paths 722

Size

Total Lines 181

Duplication

Lines 31
Ratio 17.13 %

Importance

Changes 0
Metric Value
dl 31
loc 181
rs 0.3088
c 0
b 0
f 0
cc 34
nc 722
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace TraderInteractive\Util;
4
5
use Imagick;
6
use InvalidArgumentException;
7
use TraderInteractive\Util;
8
9
final class Image
10
{
11
    /**
12
     * @var string
13
     */
14
    const DEFAULT_COLOR = 'white';
15
16
    /**
17
     * @var bool
18
     */
19
    const DEFAULT_UPSIZE = false;
20
21
    /**
22
     * @var bool
23
     */
24
    const DEFAULT_BESTFIT = false;
25
26
    /**
27
     * @var int
28
     */
29
    const DEFAULT_MAX_WIDTH = 10000;
30
31
    /**
32
     * @var int
33
     */
34
    const DEFAULT_MAX_HEIGHT = 10000;
35
36
    /**
37
     * @var bool
38
     */
39
    const DEFAULT_BLUR_BACKGROUND = false;
40
41
    /**
42
     * @var float
43
     */
44
    const DEFAULT_BLUR_VALUE = 15.0;
45
46
    /**
47
     * @var array
48
     */
49
    const DEFAULT_OPTIONS = [
50
        'color' => self::DEFAULT_COLOR,
51
        'upsize' => self::DEFAULT_UPSIZE,
52
        'bestfit' => self::DEFAULT_BESTFIT,
53
        'maxWidth' => self::DEFAULT_MAX_WIDTH,
54
        'maxHeight' => self::DEFAULT_MAX_HEIGHT,
55
        'blurBackground' => self::DEFAULT_BLUR_BACKGROUND,
56
        'blurValue' => self::DEFAULT_BLUR_VALUE,
57
    ];
58
59
    /**
60
     * @param Imagick $source    The image magick object to resize
61
     * @param int     $boxWidth  The final width of the image.
62
     * @param int     $boxHeight The final height of the image.
63
     * @param array   $options   Options for the resize operation.
64
     *
65
     * @return Imagick
66
     *
67
     * @throws \Exception Thrown if options are invalid.
68
     */
69
    public static function resize(Imagick $source, int $boxWidth, int $boxHeight, array $options = []) : Imagick
70
    {
71
        $options += self::DEFAULT_OPTIONS;
72
73
        $color = $options['color'];
74
        Util::ensure(true, is_string($color), InvalidArgumentException::class, ['$options["color"] was not a string']);
75
76
        $upsize = $options['upsize'];
77
        Util::ensure(true, is_bool($upsize), InvalidArgumentException::class, ['$options["upsize"] was not a bool']);
78
79
        $bestfit = $options['bestfit'];
80
        Util::ensure(true, is_bool($bestfit), InvalidArgumentException::class, ['$options["bestfit"] was not a bool']);
81
82
        $blurBackground = $options['blurBackground'];
83
        Util::ensure(
84
            true,
85
            is_bool($blurBackground),
86
            InvalidArgumentException::class,
87
            ['$options["blurBackground"] was not a bool']
88
        );
89
90
        $blurValue = $options['blurValue'];
91
        Util::ensure(
92
            true,
93
            is_float($blurValue),
94
            InvalidArgumentException::class,
95
            ['$options["blurValue"] was not a float']
96
        );
97
        $maxWidth = $options['maxWidth'];
98
        Util::ensure(true, is_int($maxWidth), InvalidArgumentException::class, ['$options["maxWidth"] was not an int']);
99
100
        $maxHeight = $options['maxHeight'];
101
        Util::ensure(
102
            true,
103
            is_int($maxHeight),
104
            InvalidArgumentException::class,
105
            ['$options["maxHeight"] was not an int']
106
        );
107
108
109
        if ($boxWidth > $maxWidth || $boxWidth <= 0) {
110
            throw new InvalidArgumentException('a $boxSizes width was not between 0 and $options["maxWidth"]');
111
        }
112
113
        if ($boxHeight > $maxHeight || $boxHeight <= 0) {
114
            throw new InvalidArgumentException('a $boxSizes height was not between 0 and $options["maxHeight"]');
115
        }
116
117
        $clone = clone $source;
118
119
        self::rotateImage($clone);
120
121
        $width = $clone->getImageWidth();
122
        $height = $clone->getImageHeight();
123
124
        //ratio over 1 is horizontal, under 1 is vertical
125
        $boxRatio = $boxWidth / $boxHeight;
126
        //height should be positive since I didnt find a way you could get zero into imagick
127
        $originalRatio = $width / $height;
128
129
        $targetWidth = null;
0 ignored issues
show
Unused Code introduced by
$targetWidth is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
130
        $targetHeight = null;
0 ignored issues
show
Unused Code introduced by
$targetHeight is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
131
        $targetX = null;
0 ignored issues
show
Unused Code introduced by
$targetX is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
132
        $targetY = null;
0 ignored issues
show
Unused Code introduced by
$targetY is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
133 View Code Duplication
        if ($width < $boxWidth && $height < $boxHeight && !$upsize) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
134
            $targetWidth = $width;
135
            $targetHeight = $height;
136
            $targetX = ($boxWidth - $width) / 2;
137
            $targetY = ($boxHeight - $height) / 2;
138
        } else {
139
            //if box is more vertical than original
140
            if ($boxRatio < $originalRatio) {
141
                $targetWidth = $boxWidth;
142
                $targetHeight = (int)((double)$boxWidth / $originalRatio);
143
                $targetX = 0;
144
                $targetY = ($boxHeight - $targetHeight) / 2;
145
            } else {
146
                $targetWidth = (int)((double)$boxHeight * $originalRatio);
147
                $targetHeight = $boxHeight;
148
                $targetX = ($boxWidth - $targetWidth) / 2;
149
                $targetY = 0;
150
            }
151
        }
152
153
        $widthReduced = false;
154
        if ($width > $targetWidth) {
155
            $width = $targetWidth;
156
            $widthReduced = true;
157
        }
158
159
        $heightReduced = false;
160
        if ($height > $targetHeight) {
161
            $height = $targetHeight;
162
            $heightReduced = true;
163
        }
164
165
        if ($widthReduced || $heightReduced) {
166
            if ($clone->resizeImage($width, $height, \Imagick::FILTER_BOX, 1.0) !== true) {
167
                //cumbersome to test
168
                throw new \Exception('Imagick::resizeImage() did not return true');//@codeCoverageIgnore
169
            }
170
        }
171
172 View Code Duplication
        if ($upsize && ($width < $targetWidth || $height < $targetHeight)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173
            if ($clone->resizeImage($targetWidth, $targetHeight, \Imagick::FILTER_CUBIC, 1.0, $bestfit) !== true) {
174
                //cumbersome to test
175
                throw new \Exception('Imagick::resizeImage() did not return true');//@codeCoverageIgnore
176
            }
177
        }
178
179
        if ($clone->getImageHeight() === $boxHeight && $clone->getImageWidth() === $boxWidth) {
180
            return $clone;
181
        }
182
183
        //put image in box
184
        $canvas = self::getBackgroundCanvas($clone, $color, $blurBackground, $blurValue, $boxWidth, $boxHeight);
185
        if ($canvas->compositeImage($clone, Imagick::COMPOSITE_DEFAULT, $targetX, $targetY) !== true) {
186
            //cumbersome to test
187
            throw new \Exception('Imagick::compositeImage() did not return true');//@codeCoverageIgnore
188
        }
189
190
        //reason we are not supporting the options in self::write() here is because format, and strip headers are
191
        //only relevant once written Imagick::stripImage() doesnt even have an effect until written
192
        //also the user can just call that function with the resultant $canvas
193
        return $canvas;
194
    }
195
196
    /**
197
     * resizes images into a bounding box. Maintains aspect ratio, extra space filled with given color.
198
     *
199
     * @param \Imagick $source source image to resize. Will not modify
200
     * @param array $boxSizes resulting bounding boxes. Each value should be an array with width and height, both
201
     *                        integers
202
     * @param array $options options
203
     *     string color (default white) background color. Any supported from
204
     *         http://www.imagemagick.org/script/color.php#color_names
205
     *     bool upsize (default false) true to upsize the original image or false to upsize just the bounding box
206
     *     bool bestfit (default false) true to resize with the best fit option.
207
     *     int maxWidth (default 10000) max width allowed for $boxWidth
208
     *     int maxHeight (default 10000) max height allowed for $boxHeight
209
     *     bool blurBackground (default false) true to create a composite resized image placed over an enlarged blurred
210
     *                         image of the original.
211
     *
212
     * @return array array of \Imagick objects resized. Keys maintained from $boxSizes
213
     *
214
     * @throws InvalidArgumentException if $options["color"] was not a string
215
     * @throws InvalidArgumentException if $options["upsize"] was not a bool
216
     * @throws InvalidArgumentException if $options["bestfit"] was not a bool
217
     * @throws InvalidArgumentException if $options["maxWidth"] was not an int
218
     * @throws InvalidArgumentException if $options["maxHeight"] was not an int
219
     * @throws InvalidArgumentException if a width in a $boxSizes value was not an int
220
     * @throws InvalidArgumentException if a height in a $boxSizes value was not an int
221
     * @throws InvalidArgumentException if a $boxSizes width was not between 0 and $options["maxWidth"]
222
     * @throws InvalidArgumentException if a $boxSizes height was not between 0 and $options["maxHeight"]
223
     * @throws \Exception
224
     */
225
    public static function resizeMulti(\Imagick $source, array $boxSizes, array $options = []) : array
226
    {
227
        //algorithm inspired from http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html
228
        //use of 2x2 binning is arguably the best quality one will get downsizing and is what lots of hardware does in
229
        //the photography field, while being reasonably fast. Upsizing is more subjective but you can't get much
230
        //better than bicubic which is what is used here.
231
232
        $options = $options + self::DEFAULT_OPTIONS;
233
        $color = $options['color'];
234
        Util::ensure(true, is_string($color), InvalidArgumentException::class, ['$options["color"] was not a string']);
235
236
        $upsize = $options['upsize'];
237
        Util::ensure(true, is_bool($upsize), InvalidArgumentException::class, ['$options["upsize"] was not a bool']);
238
239
        $bestfit = $options['bestfit'];
240
        Util::ensure(true, is_bool($bestfit), InvalidArgumentException::class, ['$options["bestfit"] was not a bool']);
241
242
        $blurBackground = $options['blurBackground'];
243
        Util::ensure(
244
            true,
245
            is_bool($blurBackground),
246
            InvalidArgumentException::class,
247
            ['$options["blurBackground"] was not a bool']
248
        );
249
250
        $blurValue = $options['blurValue'];
251
        Util::ensure(
252
            true,
253
            is_float($blurValue),
254
            InvalidArgumentException::class,
255
            ['$options["blurValue"] was not a float']
256
        );
257
        $maxWidth = $options['maxWidth'];
258
        Util::ensure(true, is_int($maxWidth), InvalidArgumentException::class, ['$options["maxWidth"] was not an int']);
259
260
        $maxHeight = $options['maxHeight'];
261
        Util::ensure(
262
            true,
263
            is_int($maxHeight),
264
            InvalidArgumentException::class,
265
            ['$options["maxHeight"] was not an int']
266
        );
267
268
        foreach ($boxSizes as $boxSizeKey => $boxSize) {
269 View Code Duplication
            if (!isset($boxSize['width']) || !is_int($boxSize['width'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
270
                throw new InvalidArgumentException('a width in a $boxSizes value was not an int');
271
            }
272
273 View Code Duplication
            if (!isset($boxSize['height']) || !is_int($boxSize['height'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
274
                throw new InvalidArgumentException('a height in a $boxSizes value was not an int');
275
            }
276
277
            if ($boxSize['width'] > $maxWidth || $boxSize['width'] <= 0) {
278
                throw new InvalidArgumentException('a $boxSizes width was not between 0 and $options["maxWidth"]');
279
            }
280
281
            if ($boxSize['height'] > $maxHeight || $boxSize['height'] <= 0) {
282
                throw new InvalidArgumentException('a $boxSizes height was not between 0 and $options["maxHeight"]');
283
            }
284
        }
285
286
        $results = [];
287
        $cloneCache = [];
288
        foreach ($boxSizes as $boxSizeKey => $boxSize) {
289
            $boxWidth = $boxSize['width'];
290
            $boxHeight = $boxSize['height'];
291
292
            $clone = clone $source;
293
294
            self::rotateImage($clone);
295
296
            $width = $clone->getImageWidth();
297
            $height = $clone->getImageHeight();
298
299
            //ratio over 1 is horizontal, under 1 is vertical
300
            $boxRatio = $boxWidth / $boxHeight;
301
            //height should be positive since I didnt find a way you could get zero into imagick
302
            $originalRatio = $width / $height;
303
304
            $targetWidth = null;
0 ignored issues
show
Unused Code introduced by
$targetWidth is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
305
            $targetHeight = null;
0 ignored issues
show
Unused Code introduced by
$targetHeight is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
306
            $targetX = null;
0 ignored issues
show
Unused Code introduced by
$targetX is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
307
            $targetY = null;
0 ignored issues
show
Unused Code introduced by
$targetY is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
308 View Code Duplication
            if ($width < $boxWidth && $height < $boxHeight && !$upsize) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
309
                $targetWidth = $width;
310
                $targetHeight = $height;
311
                $targetX = ($boxWidth - $width) / 2;
312
                $targetY = ($boxHeight - $height) / 2;
313
            } else {
314
                //if box is more vertical than original
315
                if ($boxRatio < $originalRatio) {
316
                    $targetWidth = $boxWidth;
317
                    $targetHeight = (int)((double)$boxWidth / $originalRatio);
318
                    $targetX = 0;
319
                    $targetY = ($boxHeight - $targetHeight) / 2;
320
                } else {
321
                    $targetWidth = (int)((double)$boxHeight * $originalRatio);
322
                    $targetHeight = $boxHeight;
323
                    $targetX = ($boxWidth - $targetWidth) / 2;
324
                    $targetY = 0;
325
                }
326
            }
327
328
            //do iterative downsize by halfs (2x2 binning is a common name) on dimensions that are bigger than target
329
            //width and height
330
            while (true) {
331
                $widthReduced = false;
332
                $widthIsHalf = false;
333
                if ($width > $targetWidth) {
334
                    $width = (int)($width / 2);
335
                    $widthReduced = true;
336
                    $widthIsHalf = true;
337
                    if ($width < $targetWidth) {
338
                        $width = $targetWidth;
339
                        $widthIsHalf = false;
340
                    }
341
                }
342
343
                $heightReduced = false;
344
                $heightIsHalf = false;
345
                if ($height > $targetHeight) {
346
                    $height = (int)($height / 2);
347
                    $heightReduced = true;
348
                    $heightIsHalf = true;
349
                    if ($height < $targetHeight) {
350
                        $height = $targetHeight;
351
                        $heightIsHalf = false;
352
                    }
353
                }
354
355
                if (!$widthReduced && !$heightReduced) {
356
                    break;
357
                }
358
359
                $cacheKey = "{$width}x{$height}";
360
                if (isset($cloneCache[$cacheKey])) {
361
                    $clone = clone $cloneCache[$cacheKey];
362
                    continue;
363
                }
364
365
                if ($clone->resizeImage($width, $height, \Imagick::FILTER_BOX, 1.0) !== true) {
366
                    //cumbersome to test
367
                    throw new \Exception('Imagick::resizeImage() did not return true');//@codeCoverageIgnore
368
                }
369
370
                if ($widthIsHalf && $heightIsHalf) {
371
                    $cloneCache[$cacheKey] = clone $clone;
372
                }
373
            }
374
375 View Code Duplication
            if ($upsize && ($width < $targetWidth || $height < $targetHeight)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
                if ($clone->resizeImage($targetWidth, $targetHeight, \Imagick::FILTER_CUBIC, 1.0, $bestfit) !== true) {
377
                    //cumbersome to test
378
                    throw new \Exception('Imagick::resizeImage() did not return true');//@codeCoverageIgnore
379
                }
380
            }
381
382
            if ($clone->getImageHeight() === $boxHeight && $clone->getImageWidth() === $boxWidth) {
383
                $results[$boxSizeKey] = $clone;
384
                continue;
385
            }
386
387
            //put image in box
388
            $canvas = self::getBackgroundCanvas($source, $color, $blurBackground, $blurValue, $boxWidth, $boxHeight);
389
            if ($canvas->compositeImage($clone, \Imagick::COMPOSITE_ATOP, $targetX, $targetY) !== true) {
390
                //cumbersome to test
391
                throw new \Exception('Imagick::compositeImage() did not return true');//@codeCoverageIgnore
392
            }
393
394
            //reason we are not supporting the options in self::write() here is because format, and strip headers are
395
            //only relevant once written Imagick::stripImage() doesnt even have an effect until written
396
            //also the user can just call that function with the resultant $canvas
397
            $results[$boxSizeKey] = $canvas;
398
        }
399
400
        foreach ($cloneCache as $clone) {
401
            $clone->destroy();
402
        }
403
404
        return $results;
405
    }
406
407
    private static function getBackgroundCanvas(
408
        \Imagick $source,
409
        string $color,
410
        bool $blurBackground,
411
        float $blurValue,
412
        int $boxWidth,
413
        int $boxHeight
414
    ) : \Imagick {
415
        if ($blurBackground || $color === 'blur') {
416
            return self::getBlurredBackgroundCanvas($source, $blurValue, $boxWidth, $boxHeight);
417
        }
418
419
        return self::getColoredBackgroundCanvas($color, $boxWidth, $boxHeight);
420
    }
421
422
    private static function getColoredBackgroundCanvas(string $color, int $boxWidth, int $boxHeight)
423
    {
424
        $canvas = new \Imagick();
425
        $imageCreated = $canvas->newImage($boxWidth, $boxHeight, $color);
426
        Util::ensure(true, $imageCreated, 'Imagick::newImage() did not return true');
427
        return $canvas;
428
    }
429
430
    private static function getBlurredBackgroundCanvas(
431
        \Imagick $source,
432
        float $blurValue,
433
        int $boxWidth,
434
        int $boxHeight
435
    ) : \Imagick {
436
        $canvas = clone $source;
437
        self::rotateImage($canvas);
438
        $canvas->resizeImage($boxWidth, $boxHeight, \Imagick::FILTER_BOX, $blurValue, false);
439
        return $canvas;
440
    }
441
442
    /**
443
     * write $source to $destPath with $options applied
444
     *
445
     * @param \Imagick $source source image. Will not modify
446
     * @param string $destPath destination image path
447
     * @param array $options options
448
     *     string format        (default jpeg) Any from http://www.imagemagick.org/script/formats.php#supported
449
     *     int    directoryMode (default 0777) chmod mode for any parent directories created
450
     *     int    fileMode      (default 0777) chmod mode for the resized image file
451
     *     bool   stripHeaders  (default true) whether to strip headers (exif, etc). Is only reflected in $destPath,
452
     *                                         not returned clone
453
     *
454
     * @return void
455
     *
456
     * @throws InvalidArgumentException if $destPath was not a string
457
     * @throws InvalidArgumentException if $options["format"] was not a string
458
     * @throws InvalidArgumentException if $options["directoryMode"] was not an int
459
     * @throws InvalidArgumentException if $options["fileMode"] was not an int
460
     * @throws InvalidArgumentException if $options["stripHeaders"] was not a bool
461
     * @throws \Exception
462
     */
463
    public static function write(\Imagick $source, string $destPath, array $options = [])
464
    {
465
        $format = 'jpeg';
466
        if (array_key_exists('format', $options)) {
467
            $format = $options['format'];
468
            if (!is_string($format)) {
469
                throw new InvalidArgumentException('$options["format"] was not a string');
470
            }
471
        }
472
473
        $directoryMode = 0777;
474
        if (array_key_exists('directoryMode', $options)) {
475
            $directoryMode = $options['directoryMode'];
476
            if (!is_int($directoryMode)) {
477
                throw new InvalidArgumentException('$options["directoryMode"] was not an int');
478
            }
479
        }
480
481
        $fileMode = 0777;
482
        if (array_key_exists('fileMode', $options)) {
483
            $fileMode = $options['fileMode'];
484
            if (!is_int($fileMode)) {
485
                throw new InvalidArgumentException('$options["fileMode"] was not an int');
486
            }
487
        }
488
489
        $stripHeaders = true;
490
        if (array_key_exists('stripHeaders', $options)) {
491
            $stripHeaders = $options['stripHeaders'];
492
            if ($stripHeaders !== false && $stripHeaders !== true) {
493
                throw new InvalidArgumentException('$options["stripHeaders"] was not a bool');
494
            }
495
        }
496
497
        $destDir = dirname($destPath);
498
        if (!is_dir($destDir)) {
499
            $oldUmask = umask(0);
500
            if (!mkdir($destDir, $directoryMode, true)) {
501
                //cumbersome to test
502
                throw new \Exception('mkdir() returned false');//@codeCoverageIgnore
503
            }
504
505
            umask($oldUmask);
506
        }
507
508
        $clone = clone $source;
509
510
        if ($clone->setImageFormat($format) !== true) {
511
            //cumbersome to test
512
            throw new \Exception('Imagick::setImageFormat() did not return true');//@codeCoverageIgnore
513
        }
514
515
        if ($stripHeaders && $clone->stripImage() !== true) {
516
            //cumbersome to test
517
            throw new \Exception('Imagick::stripImage() did not return true');//@codeCoverageIgnore
518
        }
519
520
        if ($clone->writeImage($destPath) !== true) {
521
            //cumbersome to test
522
            throw new \Exception('Imagick::writeImage() did not return true');//@codeCoverageIgnore
523
        }
524
525
        if (!chmod($destPath, $fileMode)) {
526
            //cumbersome to test
527
            throw new \Exception('chmod() returned false');//@codeCoverageIgnore
528
        }
529
    }
530
531
    /**
532
     * Strips the headers (exif, etc) from an image at the given path.
533
     *
534
     * @param string $path The image path.
535
     * @return void
536
     * @throws InvalidArgumentException if $path is not a string
537
     * @throws \Exception if there is a failure stripping the headers
538
     * @throws \Exception if there is a failure writing the image back to path
539
     */
540
    public static function stripHeaders(string $path)
541
    {
542
        $imagick = new \Imagick($path);
543
        if ($imagick->stripImage() !== true) {
544
            //cumbersome to test
545
            throw new \Exception('Imagick::stripImage() did not return true');//@codeCoverageIgnore
546
        }
547
548
        if ($imagick->writeImage($path) !== true) {
549
            //cumbersome to test
550
            throw new \Exception('Imagick::writeImage() did not return true');//@codeCoverageIgnore
551
        }
552
    }
553
554
    /**
555
     * @param \Imagick $imagick
556
     */
557
    private static function rotateImage(\Imagick $imagick)
558
    {
559
        $orientation = $imagick->getImageOrientation();
560
        switch ($orientation) {
561
            case \Imagick::ORIENTATION_BOTTOMRIGHT:
562
                $imagick->rotateimage('#fff', 180);
563
                $imagick->stripImage();
564
                break;
565
            case \Imagick::ORIENTATION_RIGHTTOP:
566
                $imagick->rotateimage('#fff', 90);
567
                $imagick->stripImage();
568
                break;
569
            case \Imagick::ORIENTATION_LEFTBOTTOM:
570
                $imagick->rotateimage('#fff', -90);
571
                $imagick->stripImage();
572
                break;
573
        }
574
    }
575
}
576