Image::resize()   F
last analyzed

Complexity

Conditions 30
Paths 4730

Size

Total Lines 118
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 30
eloc 69
nc 4730
nop 3
dl 0
loc 118
rs 0
c 1
b 0
f 0

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 image\components\Kohana;
4
5
use yii\base\ErrorException;
6
use image\components\drivers\Driver;
7
8
/**
9
 * Image manipulation support. Allows images to be resized, cropped, etc.
10
 *
11
 * @package    image\components\Kohana
12
 * @author     Kohana Team
13
 * @copyright  (c) 2008-2009,2018 Kohana Team
14
 * @license    http://kohanaphp.com/license.html
15
 */
16
class Image extends Driver implements ImageInterface
17
{
18
19
    /**
20
     * @var  string  default driver: GD, Imagick, etc
21
     */
22
    public static $default_driver = 'GD';
23
24
    /**
25
     * Loads an image and prepares it for manipulation.
26
     *
27
     *     $image = Image::factory('upload/test.jpg');
28
     *
29
     * @param   string $file image file path
30
     * @param   string $driver driver type: GD, ImageMagick, etc
31
     * @return  Image
32
     * @uses    Image::$default_driver
33
     */
34
    public static function factory($file, $driver = null)
35
    {
36
        if ($driver === null) {
37
38
            // Use the default driver
39
            $driver = Image::$default_driver;
40
        }
41
42
        // Set the class name
43
        $class = 'image\components\drivers\Image_' . $driver;
44
45
        return new $class($file);
46
    }
47
48
    /**
49
     * Render the current image.
50
     *
51
     *     echo $image;
52
     *
53
     * [!!] The output of this function is binary and must be rendered with the
54
     * appropriate Content-Type header or it will not be displayed correctly!
55
     *
56
     * @return  string
57
     */
58
    public function __toString()
59
    {
60
        try {
61
            // Render the current image
62
            return $this->render();
63
        } catch (ErrorException $e) {
64
            /*
65
            if (is_object(Kohana::$log))
66
            {
67
                    // Get the text of the exception
68
                    $error = ErrorException::text($e);
69
70
                    // Add this exception to the log
71
                    Yii::error($error);
72
            }
73
            */
74
75
            // Showing any kind of error will be "inside" image data
76
            return '';
77
        }
78
    }
79
80
    /**
81
     * Resize the image to the given size. Either the width or the height can
82
     * be omitted and the image will be resized proportionally.
83
     *
84
     *     // Resize to 200 pixels on the shortest side
85
     *     $image->resize(200, 200);
86
     *
87
     *     // Resize to 200x200 pixels, keeping aspect ratio
88
     *     $image->resize(200, 200, Image::INVERSE);
89
     *
90
     *     // Resize to 500 pixel width, keeping aspect ratio
91
     *     $image->resize(500, null);
92
     *
93
     *     // Resize to 500 pixel height, keeping aspect ratio
94
     *     $image->resize(null, 500);
95
     *
96
     *     // Resize to 200x500 pixels, ignoring aspect ratio
97
     *     $image->resize(200, 500, Image::NONE);
98
     *
99
     *     // Resize to 400 pixels on the shortest side, puts it in the center
100
     *     // of the image with the transparent edges, keeping aspect ratio,
101
     *     // output size will be 400x400 pixels
102
     *     $image->resize(400, 400, Image::ADAPT);
103
     *
104
     * @param   integer $width new width
105
     * @param   integer $height new height
106
     * @param   integer $master master dimension
107
     * @return  $this
108
     * @uses    Image::_do_resize
109
     */
110
    public function resize($width = null, $height = null, $master = null)
111
    {
112
        if ($master === null) {
113
            // Choose the master dimension automatically
114
            $master = Image::AUTO;
115
        } elseif ($master === Image::CROP) {
116
            if (empty($width) || empty($height)) {
117
                return $this->resize($width, $height, Image::AUTO);
118
            }
119
120
            $master = $this->width / $this->height > $width / $height ? Image::HEIGHT : Image::WIDTH;
121
            $this->resize($width, $height, $master);
122
123
            if ($this->width !== $width || $this->height !== $height) {
124
                $offset_x = round(($this->width - $width) / 2);
125
                $offset_y = round(($this->height - $height) / 2);
126
                $this->crop($width, $height, $offset_x, $offset_y);
127
            }
128
129
            return $this;
130
        }
131
        // Image::WIDTH and Image::HEIGHT deprecated. You can use it in old projects,
132
        // but in new you must pass empty value for non-master dimension
133
        elseif ($master == Image::WIDTH && !empty($width)) {
134
            $master = Image::AUTO;
135
136
            // Set empty height for backward compatibility
137
            $height = null;
138
        } elseif ($master == Image::HEIGHT && !empty($height)) {
139
            $master = Image::AUTO;
140
141
            // Set empty width for backward compatibility
142
            $width = null;
143
        } elseif ($master === Image::ADAPT) {
144
            if (empty($width)) {
145
                $width = $this->width * $height / $this->height;
146
            } elseif (empty($height)) {
147
                $height = $this->height * $width / $this->width;
148
            }
149
        }
150
151
        if (empty($width)) {
152
            if ($master === Image::NONE) {
153
                // Use the current width
154
                $width = $this->width;
155
            } else {
156
                // If width not set, master will be height
157
                $master = Image::HEIGHT;
158
            }
159
        }
160
161
        if (empty($height)) {
162
            if ($master === Image::NONE) {
163
                // Use the current height
164
                $height = $this->height;
165
            } else {
166
                // If height not set, master will be width
167
                $master = Image::WIDTH;
168
            }
169
        }
170
171
        switch ($master) {
172
            case Image::AUTO:
173
                // Choose direction with the greatest reduction ratio
174
                $master = ($this->width / $width) > ($this->height / $height) ? Image::WIDTH : Image::HEIGHT;
175
                break;
176
            case Image::INVERSE:
177
                // Choose direction with the minimum reduction ratio
178
                $master = ($this->width / $width) > ($this->height / $height) ? Image::HEIGHT : Image::WIDTH;
179
                break;
180
        }
181
182
        switch ($master) {
183
            case Image::WIDTH:
184
                // Recalculate the height based on the width proportions
185
                $height = $this->height * $width / $this->width;
186
                break;
187
            case Image::HEIGHT:
188
                // Recalculate the width based on the height proportions
189
                $width = $this->width * $height / $this->height;
190
                break;
191
            case Image::PRECISE:
192
                // Resize to precise size
193
                $ratio = $this->width / $this->height;
194
195
                if ($width / $height > $ratio) {
196
                    $height = $this->height * $width / $this->width;
197
                } else {
198
                    $width = $this->width * $height / $this->height;
199
                }
200
                break;
201
        }
202
203
        // Convert the width and height to integers, minimum value is 1px
204
        $width = max(round($width), 1);
205
        $height = max(round($height), 1);
206
207
        // Adapt the image if the ratios are not equivalent
208
        if ($master === Image::ADAPT && $width / $height !== $this->width / $this->height) {
209
            $image_width = $bg_width = $this->width;
210
            $image_height = $bg_height = $this->height;
211
212
            $offset_x = $offset_y = 0;
213
214
            if ($width / $height > $image_width / $image_height) {
215
                $bg_width = floor($image_height * $width / $height);
216
                $offset_x = abs(floor(($bg_width - $image_width) / 2));
217
            } else {
218
                $bg_height = floor($image_width * $height / $width);
219
                $offset_y = abs(floor(($bg_height - $image_height) / 2));
220
            }
221
222
            $this->_do_adapt($image_width, $image_height, $bg_width, $bg_height, $offset_x, $offset_y);
0 ignored issues
show
Bug introduced by
It seems like $bg_height can also be of type double; however, parameter $bg_height of image\components\drivers\Driver::_do_adapt() does only seem to accept integer, 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

222
            $this->_do_adapt($image_width, $image_height, $bg_width, /** @scrutinizer ignore-type */ $bg_height, $offset_x, $offset_y);
Loading history...
Bug introduced by
It seems like $offset_x can also be of type double; however, parameter $offset_x of image\components\drivers\Driver::_do_adapt() does only seem to accept integer, 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

222
            $this->_do_adapt($image_width, $image_height, $bg_width, $bg_height, /** @scrutinizer ignore-type */ $offset_x, $offset_y);
Loading history...
Bug introduced by
It seems like $bg_width can also be of type double; however, parameter $bg_width of image\components\drivers\Driver::_do_adapt() does only seem to accept integer, 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

222
            $this->_do_adapt($image_width, $image_height, /** @scrutinizer ignore-type */ $bg_width, $bg_height, $offset_x, $offset_y);
Loading history...
Bug introduced by
It seems like $offset_y can also be of type double; however, parameter $offset_y of image\components\drivers\Driver::_do_adapt() does only seem to accept integer, 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

222
            $this->_do_adapt($image_width, $image_height, $bg_width, $bg_height, $offset_x, /** @scrutinizer ignore-type */ $offset_y);
Loading history...
223
        }
224
225
        $this->_do_resize($width, $height);
226
227
        return $this;
228
    }
229
230
    /**
231
     * Crop an image to the given size. Either the width or the height can be
232
     * omitted and the current width or height will be used.
233
     *
234
     * If no offset is specified, the center of the axis will be used.
235
     * If an offset of true is specified, the bottom of the axis will be used.
236
     *
237
     *     // Crop the image to 200x200 pixels, from the center
238
     *     $image->crop(200, 200);
239
     *
240
     * @param   integer $width new width
241
     * @param   integer $height new height
242
     * @param   mixed $offset_x offset from the left
243
     * @param   mixed $offset_y offset from the top
244
     * @return  $this
245
     * @uses    Image::_do_crop
246
     */
247
    public function crop($width, $height, $offset_x = null, $offset_y = null)
248
    {
249
        if ($width > $this->width) {
250
            // Use the current width
251
            $width = $this->width;
252
        }
253
254
        if ($height > $this->height) {
255
            // Use the current height
256
            $height = $this->height;
257
        }
258
259
        if ($offset_x === null) {
260
            // Center the X offset
261
            $offset_x = round(($this->width - $width) / 2);
262
        } elseif ($offset_x === true) {
263
            // Bottom the X offset
264
            $offset_x = $this->width - $width;
265
        } elseif ($offset_x < 0) {
266
            // Set the X offset from the right
267
            $offset_x = $this->width - $width + $offset_x;
268
        }
269
270
        if ($offset_y === null) {
271
            // Center the Y offset
272
            $offset_y = round(($this->height - $height) / 2);
273
        } elseif ($offset_y === true) {
274
            // Bottom the Y offset
275
            $offset_y = $this->height - $height;
276
        } elseif ($offset_y < 0) {
277
            // Set the Y offset from the bottom
278
            $offset_y = $this->height - $height + $offset_y;
279
        }
280
281
        // Determine the maximum possible width and height
282
        $max_width = $this->width - $offset_x;
283
        $max_height = $this->height - $offset_y;
284
285
        if ($width > $max_width) {
286
            // Use the maximum available width
287
            $width = $max_width;
288
        }
289
290
        if ($height > $max_height) {
291
            // Use the maximum available height
292
            $height = $max_height;
293
        }
294
295
        $this->_do_crop($width, $height, $offset_x, $offset_y);
0 ignored issues
show
Bug introduced by
It seems like $height can also be of type double; however, parameter $height of image\components\drivers\Driver::_do_crop() does only seem to accept integer, 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

295
        $this->_do_crop($width, /** @scrutinizer ignore-type */ $height, $offset_x, $offset_y);
Loading history...
Bug introduced by
It seems like $offset_y can also be of type double; however, parameter $offset_y of image\components\drivers\Driver::_do_crop() does only seem to accept integer, 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

295
        $this->_do_crop($width, $height, $offset_x, /** @scrutinizer ignore-type */ $offset_y);
Loading history...
Bug introduced by
It seems like $width can also be of type double; however, parameter $width of image\components\drivers\Driver::_do_crop() does only seem to accept integer, 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

295
        $this->_do_crop(/** @scrutinizer ignore-type */ $width, $height, $offset_x, $offset_y);
Loading history...
Bug introduced by
It seems like $offset_x can also be of type double; however, parameter $offset_x of image\components\drivers\Driver::_do_crop() does only seem to accept integer, 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

295
        $this->_do_crop($width, $height, /** @scrutinizer ignore-type */ $offset_x, $offset_y);
Loading history...
296
297
        return $this;
298
    }
299
300
    /**
301
     * Rotate the image by a given amount.
302
     *
303
     *     // Rotate 45 degrees clockwise
304
     *     $image->rotate(45);
305
     *
306
     *     // Rotate 90% counter-clockwise
307
     *     $image->rotate(-90);
308
     *
309
     * @param   integer $degrees degrees to rotate: -360-360
310
     * @return  $this
311
     * @uses    Image::_do_rotate
312
     */
313
    public function rotate($degrees)
314
    {
315
        // Make the degrees an integer
316
        $degrees = (int)$degrees;
317
318
        if ($degrees > 180) {
319
            do {
320
                // Keep subtracting full circles until the degrees have normalized
321
                $degrees -= 360;
322
            } while ($degrees > 180);
323
        }
324
325
        if ($degrees < -180) {
326
            do {
327
                // Keep adding full circles until the degrees have normalized
328
                $degrees += 360;
329
            } while ($degrees < -180);
330
        }
331
332
        $this->_do_rotate($degrees);
333
334
        return $this;
335
    }
336
337
    /**
338
     * Flip the image along the horizontal or vertical axis.
339
     *
340
     *     // Flip the image from top to bottom
341
     *     $image->flip(Image::HORIZONTAL);
342
     *
343
     *     // Flip the image from left to right
344
     *     $image->flip(Image::VERTICAL);
345
     *
346
     * @param   integer $direction direction: Image::HORIZONTAL, Image::VERTICAL
347
     * @return  $this
348
     * @uses    Image::_do_flip
349
     */
350
    public function flip($direction)
351
    {
352
        if ($direction !== Image::HORIZONTAL) {
353
            // Flip vertically
354
            $direction = Image::VERTICAL;
355
        }
356
357
        $this->_do_flip($direction);
358
359
        return $this;
360
    }
361
362
    /**
363
     * Sharpen the image by a given amount.
364
     *
365
     *     // Sharpen the image by 20%
366
     *     $image->sharpen(20);
367
     *
368
     * @param   integer $amount amount to sharpen: 1-100
369
     * @return  $this
370
     * @uses    Image::_do_sharpen
371
     */
372
    public function sharpen($amount)
373
    {
374
        // The amount must be in the range of 1 to 100
375
        $amount = min(max($amount, 1), 100);
376
377
        $this->_do_sharpen($amount);
378
379
        return $this;
380
    }
381
382
    /**
383
     * Add a reflection to an image. The most opaque part of the reflection
384
     * will be equal to the opacity setting and fade out to full transparent.
385
     * Alpha transparency is preserved.
386
     *
387
     *     // Create a 50 pixel reflection that fades from 0-100% opacity
388
     *     $image->reflection(50);
389
     *
390
     *     // Create a 50 pixel reflection that fades from 100-0% opacity
391
     *     $image->reflection(50, 100, true);
392
     *
393
     *     // Create a 50 pixel reflection that fades from 0-60% opacity
394
     *     $image->reflection(50, 60, true);
395
     *
396
     * [!!] By default, the reflection will be go from transparent at the top
397
     * to opaque at the bottom.
398
     *
399
     * @param   integer $height reflection height
400
     * @param   integer $opacity reflection opacity: 0-100
401
     * @param   boolean $fade_in true to fade in, false to fade out
402
     * @return  $this
403
     * @uses    Image::_do_reflection
404
     */
405
    public function reflection($height = null, $opacity = 100, $fade_in = false)
406
    {
407
        if ($height === null || $height > $this->height) {
408
            // Use the current height
409
            $height = $this->height;
410
        }
411
412
        // The opacity must be in the range of 0 to 100
413
        $opacity = min(max($opacity, 0), 100);
414
415
        $this->_do_reflection($height, $opacity, $fade_in);
416
417
        return $this;
418
    }
419
420
    /**
421
     * Add a watermark to an image with a specified opacity. Alpha transparency
422
     * will be preserved.
423
     *
424
     * If no offset is specified, the center of the axis will be used.
425
     * If an offset of true is specified, the bottom of the axis will be used.
426
     *
427
     *     // Add a watermark to the bottom right of the image
428
     *     $mark = Image::factory('upload/watermark.png');
429
     *     $image->watermark($mark, true, true);
430
     *
431
     * @param   Image $watermark watermark Image instance
432
     * @param   integer $offset_x offset from the left
433
     * @param   integer $offset_y offset from the top
434
     * @param   integer $opacity opacity of watermark: 1-100
435
     * @return  $this
436
     * @uses    Image::_do_watermark
437
     */
438
    public function watermark(Image $watermark, $offset_x = null, $offset_y = null, $opacity = 100)
439
    {
440
        if ($offset_x === null) {
441
            // Center the X offset
442
            $offset_x = round(($this->width - $watermark->width) / 2);
443
        } elseif ($offset_x === true) {
0 ignored issues
show
introduced by
The condition $offset_x === true is always false.
Loading history...
444
            // Bottom the X offset
445
            $offset_x = $this->width - $watermark->width;
446
        } elseif ($offset_x < 0) {
447
            // Set the X offset from the right
448
            $offset_x = $this->width - $watermark->width + $offset_x;
449
        }
450
451
        if ($offset_y === null) {
452
            // Center the Y offset
453
            $offset_y = round(($this->height - $watermark->height) / 2);
454
        } elseif ($offset_y === true) {
0 ignored issues
show
introduced by
The condition $offset_y === true is always false.
Loading history...
455
            // Bottom the Y offset
456
            $offset_y = $this->height - $watermark->height;
457
        } elseif ($offset_y < 0) {
458
            // Set the Y offset from the bottom
459
            $offset_y = $this->height - $watermark->height + $offset_y;
460
        }
461
462
        // The opacity must be in the range of 1 to 100
463
        $opacity = min(max($opacity, 1), 100);
464
465
        $this->_do_watermark($watermark, $offset_x, $offset_y, $opacity);
0 ignored issues
show
Bug introduced by
It seems like $offset_x can also be of type double; however, parameter $offset_x of image\components\drivers\Driver::_do_watermark() does only seem to accept integer, 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

465
        $this->_do_watermark($watermark, /** @scrutinizer ignore-type */ $offset_x, $offset_y, $opacity);
Loading history...
Bug introduced by
It seems like $offset_y can also be of type double; however, parameter $offset_y of image\components\drivers\Driver::_do_watermark() does only seem to accept integer, 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

465
        $this->_do_watermark($watermark, $offset_x, /** @scrutinizer ignore-type */ $offset_y, $opacity);
Loading history...
466
467
        return $this;
468
    }
469
470
    /**
471
     * Set the background color of an image. This is only useful for images
472
     * with alpha transparency.
473
     *
474
     *     // Make the image background black
475
     *     $image->background('#000');
476
     *
477
     *     // Make the image background black with 50% opacity
478
     *     $image->background('#000', 50);
479
     *
480
     * @param   string $color hexadecimal color value
481
     * @param   integer $opacity background opacity: 0-100
482
     * @return  $this
483
     * @uses    Image::_do_background
484
     */
485
    public function background($color, $opacity = 100)
486
    {
487
        if ($color[0] === '#') {
488
            // Remove the pound
489
            $color = substr($color, 1);
490
        }
491
492
        if (strlen($color) === 3) {
493
            // Convert shorthand into longhand hex notation
494
            $color = preg_replace('/./', '$0$0', $color);
495
        }
496
497
        // Convert the hex into RGB values
498
        list ($r, $g, $b) = array_map('hexdec', str_split($color, 2));
499
500
        // The opacity must be in the range of 0 to 100
501
        $opacity = min(max($opacity, 0), 100);
502
503
        $this->_do_background($r, $g, $b, $opacity);
504
505
        return $this;
506
    }
507
508
    /**
509
     * Save the image. If the filename is omitted, the original image will
510
     * be overwritten.
511
     *
512
     *     // Save the image as a PNG
513
     *     $image->save('saved/cool.png');
514
     *
515
     *     // Overwrite the original image
516
     *     $image->save();
517
     *
518
     * [!!] If the file exists, but is not writable, an exception will be thrown.
519
     *
520
     * [!!] If the file does not exist, and the directory is not writable, an
521
     * exception will be thrown.
522
     *
523
     * @param   string $file new image path
524
     * @param   integer $quality quality of image: 1-100
525
     * @return  boolean
526
     * @uses    Image::_save
527
     * @throws  \yii\base\ErrorException
528
     */
529
    public function save($file = null, $quality = 100)
530
    {
531
        if ($file === null) {
532
            // Overwrite the file
533
            $file = $this->file;
534
        }
535
536
        if (is_file($file)) {
537
            if (!is_writable($file)) {
538
                throw new ErrorException(sprintf('File must be writable: %s', $file));
539
            }
540
        } else {
541
            // Get the directory of the file
542
            $directory = realpath(pathinfo($file, PATHINFO_DIRNAME));
543
544
            if (!is_dir($directory) || !is_writable($directory)) {
545
                throw new ErrorException(sprintf('Directory must be writable: %s', $directory));
546
            }
547
        }
548
549
        // The quality must be in the range of 1 to 100
550
        $quality = min(max($quality, 1), 100);
551
552
        return $this->_do_save($file, $quality);
553
    }
554
555
    /**
556
     * Render the image and return the binary string.
557
     *
558
     *     // Render the image at 50% quality
559
     *     $data = $image->render(null, 50);
560
     *
561
     *     // Render the image as a PNG
562
     *     $data = $image->render('png');
563
     *
564
     * @param   string $type image type to return: png, jpg, gif, etc
565
     * @param   integer $quality quality of image: 1-100
566
     * @return  string
567
     * @uses    Image::_do_render
568
     */
569
    public function render($type = null, $quality = 100)
570
    {
571
        if ($type === null) {
572
            // Use the current image type
573
            $type = image_type_to_extension($this->type, false);
574
        }
575
576
        return $this->_do_render($type, $quality);
577
    }
578
579
} // End Image
580
581
582