Passed
Push — master ( 48d769...5ccf6e )
by Michael
07:14
created

Zebra_Image::_flip()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 75
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 35
nc 5
nop 1
dl 0
loc 75
rs 8.4736
c 0
b 0
f 0

How to fix   Long Method   

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
/**
4
 *  Methods used with the {@link resize()} method.
5
 */
6
define('ZEBRA_IMAGE_BOXED', 0);
7
define('ZEBRA_IMAGE_NOT_BOXED', 1);
8
define('ZEBRA_IMAGE_CROP_TOPLEFT', 2);
9
define('ZEBRA_IMAGE_CROP_TOPCENTER', 3);
10
define('ZEBRA_IMAGE_CROP_TOPRIGHT', 4);
11
define('ZEBRA_IMAGE_CROP_MIDDLELEFT', 5);
12
define('ZEBRA_IMAGE_CROP_CENTER', 6);
13
define('ZEBRA_IMAGE_CROP_MIDDLERIGHT', 7);
14
define('ZEBRA_IMAGE_CROP_BOTTOMLEFT', 8);
15
define('ZEBRA_IMAGE_CROP_BOTTOMCENTER', 9);
16
define('ZEBRA_IMAGE_CROP_BOTTOMRIGHT', 10);
17
18
// this enables handling of partially broken JPEG files without warnings/errors
19
ini_set('gd.jpeg_ignore_warning', true);
20
21
/**
22
 *  A compact (one-file only) and lightweight PHP library for image manipulation providing methods for performing several
23
 *  types of image manipulation operations and applying filters to images.
24
 *
25
 *  Read more {@link https://github.com/stefangabos/Zebra_Image/ here}
26
 *
27
 *  @author     Stefan Gabos <[email protected]>
28
 *  @version    2.2.7 (last revision: August 21, 2017)
29
 *  @copyright  (c) 2006 - 2017 Stefan Gabos
30
 *  @license    http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE
31
 *  @package    Zebra_Image
32
 */
33
class Zebra_Image {
34
35
    /**
36
     *  Indicates the file system permissions to be set for newly created images.
37
     *
38
     *  Better is to leave this setting as it is.
39
     *
40
     *  If you know what you are doing, here is how you can calculate the permission levels:
41
     *
42
     *  - 400 Owner Read
43
     *  - 200 Owner Write
44
     *  - 100 Owner Execute
45
     *  - 40 Group Read
46
     *  - 20 Group Write
47
     *  - 10 Group Execute
48
     *  - 4 Global Read
49
     *  - 2 Global Write
50
     *  - 1 Global Execute
51
     *
52
     *  Default is 0755
53
     *
54
     *  @var integer
55
     */
56
    public $chmod_value;
57
58
    /**
59
     *  If set to FALSE, images having both width and height smaller than the required width and height, will be left
60
     *  untouched ({@link jpeg_quality} and {@link png_compression} will still apply).
61
     *
62
     *  Available only for the {@link resize()} method
63
     *
64
     *  Default is TRUE
65
     *
66
     *  @var boolean
67
     */
68
    public $enlarge_smaller_images;
69
70
    /**
71
     *  In case of an error read this property's value to see the error's code.
72
     *
73
     *  Possible error codes are:
74
     *
75
     *  - 1:  source file could not be found
76
     *  - 2:  source file is not readable
77
     *  - 3:  could not write target file
78
     *  - 4:  unsupported source file format
79
     *  - 5:  unsupported target file format
80
     *  - 6:  GD library version does not support target file format
81
     *  - 7:  GD library is not installed!
82
     *  - 8:  "chmod" command is disabled via configuration
83
     *  - 9:  "exif_read_data" function is not available
84
     *
85
     *  Default is 0 (no error).
86
     *
87
     *  @var integer
88
     */
89
    public $error;
90
91
    /**
92
     *  If set to TRUE, JPEG images will be auto-rotated according to the {@link http://keyj.emphy.de/exif-orientation-rant/ Exif Orientation Tag}
93
     *  so that they are always shown correctly.
94
     *
95
     *  <samp>If you set this to TRUE you must also enable exif-support with --enable-exif. Windows users must enable both
96
     *  the php_mbstring.dll and php_exif.dll DLL's in php.ini. The php_mbstring.dll DLL must be loaded before the
97
     *  php_exif.dll DLL so adjust your php.ini accordingly. See {@link http://php.net/manual/en/exif.installation.php the PHP manual}</samp>
98
     *
99
     *  Default is FALSE
100
     *
101
     *  @since 2.2.4
102
     *
103
     *  @var boolean
104
     */
105
    public $auto_handle_exif_orientation;
106
107
    /**
108
     *  Indicates the quality of the output image (better quality means bigger file size).
109
     *
110
     *  Used only if the file at {@link target_path} is a JPG/JPEG image.
111
     *
112
     *  Range is 0 - 100
113
     *
114
     *  Default is 85
115
     *
116
     *  @var integer
117
     */
118
    public $jpeg_quality;
119
120
    /**
121
     *  Indicates the compression level of the output image (lower compression means bigger file size).
122
     *
123
     *  Available only if PHP version is 5.1.2+, and only if the file at {@link target_path} is a PNG image. It will be
124
     *  ignored otherwise.
125
     *
126
     *  Range is 0 - 9
127
     *
128
     *  Default is 9
129
     *
130
     *  @since 2.2
131
     *
132
     *  @var integer
133
     */
134
    public $png_compression;
135
136
    /**
137
     *  Specifies whether, upon resizing, images should preserve their aspect ratio.
138
     *
139
     *  Available only for the {@link resize()} method
140
     *
141
     *  Default is TRUE
142
     *
143
     *  @var boolean
144
     */
145
    public $preserve_aspect_ratio;
146
147
    /**
148
     *  Indicates whether a target files should preserve the source file's date/time.
149
     *
150
     *  Default is TRUE
151
     *
152
     *  @since 1.0.4
153
     *
154
     *  @var boolean
155
     */
156
    public $preserve_time;
157
158
    /**
159
     *  Indicates whether the target image should have a "sharpen" filter applied to it.
160
     *
161
     *  Can be very useful when creating thumbnails and should be used only when creating thumbnails.
162
     *
163
     *  <i>The sharpen filter relies on the "imageconvolution" PHP function which is available only for PHP version
164
     *  5.1.0+, and will leave the images unaltered for older versions!</i>
165
     *
166
     *  Default is FALSE
167
     *
168
     *  @since 2.2
169
     *
170
     *  @var boolean
171
     */
172
    public $sharpen_images;
173
174
    /**
175
     *  Path to an image file to apply the transformations to.
176
     *
177
     *  Supported file types are <b>GIF</b>, <b>PNG</b> and <b>JPEG</b>.
178
     *
179
     *  @var    string
180
     */
181
    public $source_path;
182
183
    /**
184
     *  Path (including file name) to where to save the transformed image.
185
     *
186
     *  <i>Can be a different than {@link source_path} - the type of the transformed image will be as indicated by the
187
     *  file's extension (supported file types are GIF, PNG and JPEG)</i>.
188
     *
189
     *  @var    string
190
     */
191
    public $target_path;
192
193
    /**
194
     *  Constructor of the class.
195
     *
196
     *  Initializes the class and the default properties
197
     *
198
     *  @return void
199
     */
200
    public function __construct() {
201
202
        // set default values for properties
203
        $this->chmod_value = 0755;
204
205
        $this->error = 0;
206
207
        $this->jpeg_quality = 85;
208
209
        $this->png_compression = 9;
210
211
        $this->preserve_aspect_ratio = $this->preserve_time = $this->enlarge_smaller_images = true;
212
213
        $this->sharpen_images = $this->auto_handle_exif_orientation = false;
214
215
        $this->source_path = $this->target_path = '';
216
217
    }
218
219
    /**
220
     *  Applies one or more filters to the image given as {@link source_path} and outputs it as the file specified as
221
     *  {@link target_path}.
222
     *
223
     *  <samp>This method is available only if the {@link http://php.net/manual/en/function.imagefilter.php imagefilter}
224
     *  function is available (available from PHP 5+), and will leave images unaltered otherwise.</samp>
225
     *
226
     *  <code>
227
     *  // include the Zebra_Image library
228
     *  require 'path/to/Zebra_Image.php';
229
     *
230
     *  // instantiate the class
231
     *  $img = new Zebra_Image();
232
     *
233
     *  // a source image
234
     *  $img->source_path = 'path/to/source.ext';
235
     *
236
     *  // path to where should the resulting image be saved
237
     *  // note that by simply setting a different extension to the file will
238
     *  // instruct the script to create an image of that particular type
239
     *  $img->target_path = 'path/to/target.ext';
240
     *
241
     *  // apply the "grayscale" filter
242
     *  $img->apply_filter('grayscale');
243
     *
244
     *  // apply the "contrast" filter
245
     *  $img->apply_filter('contrast', -20);
246
     *  </code>
247
     *
248
     *  You can also apply multiple filters at once. In this case, the method requires a single argument, an array of
249
     *  arrays, containing the filters and associated arguments, where applicable:
250
     *
251
     *  <code>
252
     *  // create a sepia effect
253
     *  // note how we're applying multiple filters at once
254
     *  // each filter is in its own array
255
     *  $img->apply_filter(array(
256
     *
257
     *      // first we apply the "grayscale" filter
258
     *      array('grayscale'),
259
     *
260
     *      // then we apply the "colorize" filter with 90, 60, 40 as
261
     *      // the values for red, green and blue
262
     *      array('colorize', 90, 60, 40),
263
     *
264
     *  ));
265
     *  </code>
266
     *
267
     *  @param  string  $filter     The (case-insensitive) name of the filter to apply. Can be one of the following:
268
     *
269
     *                              -   <b>brightness</b>       -   changes the brightness of the image; use <b>arg1</b>
270
     *                                                              to set the level of brightness; the range of brightness
271
     *                                                              is -255 to 255;
272
     *                              -   <b>colorize</b>         -   adds (subtracts) specified RGB values to each pixel;
273
     *                                                              use <b>arg1</b>, <b>arg2</b> and <b>arg3</b> in the
274
     *                                                              form of red, green, blue and <b>arg4</b> for the alpha
275
     *                                                              channel. the range for each color is -255 to 255 and
276
     *                                                              0 to 127 for alpha; <i>alpha support is available only
277
     *                                                              for PHP 5.2.5+</i>;
278
     *                              -   <b>contrast</b>         -   changes the contrast of the image; use <b>arg1</b>
279
     *                                                              to set the level of contrast; the range of contrast
280
     *                                                              is -100 to 100;
281
     *                              -   <b>gausian_blur</b>     -   blurs the image using the Gaussian method;
282
     *                              -   <b>grayscale</b>        -   converts the image into grayscale;
283
     *                              -   <b>edgedetect</b>       -   uses edge detection to highlight the edges in the image;
284
     *                              -   <b>emboss</b>           -   embosses the image;
285
     *                              -   <b>mean_removal</b>     -   uses mean removal to achieve a "sketchy" effect;
286
     *                              -   <b>negate</b>           -   reverses all the colors of the image;
287
     *                              -   <b>pixelate</b>         -   applies pixelation effect to the image, use <b>arg1</b>
288
     *                                                              to set the block size and <b>arg2</b> to set the
289
     *                                                              pixelation effect mode; <i>this filter is available
290
     *                                                              only for PHP 5.3.0+</i>;
291
     *                              -   <b>selective_blur</b>   -   blurs the image;
292
     *                              -   <b>smooth</b>           -   makes the image smoother. Use <b>arg1</b> to set the
293
     *                                                              level of smoothness. applies a 9-cell convolution matrix
294
     *                                                              where center pixel has the weight of <b>arg1</b> and
295
     *                                                              others weight of 1.0. the result is normalized by dividing
296
     *                                                              the sum with <b>arg1</b> + 8.0 (sum of the matrix).
297
     *                                                              any float is accepted;
298
     *
299
     *  @param  mixed   $arg1       Used by the following filters:
300
     *                              -   <b>brightness</b>       -   sets the brightness level (-255 to 255)
301
     *                              -   <b>contrast</b>         -   sets the contrast level (-100 to 100)
302
     *                              -   <b>colorize</b>         -   sets the value of the red component (-255 to 255)
303
     *                              -   <b>smooth</b>           -   sets the smoothness level
304
     *                              -   <b>pixelate</b>         -   sets the block size, in pixels
305
     *
306
     *  @param  mixed   $arg2       Used by the following filters:
307
     *                              -   <b>colorize</b>         -   sets the value of the green component (-255 to 255)
308
     *                              -   <b>pixelate</b>         -   whether to use advanced pixelation effect or not (defaults to FALSE).
309
     *
310
     *  @param  mixed   $arg3       Used by the following filters:
311
     *                              -   <b>colorize</b>         -   sets the value of the blue component (-255 to 255)
312
     *
313
     *  @param  mixed   $arg4       Used by the following filters:
314
     *                              -   <b>colorize</b>         -   alpha channel; a value between 0 and 127. 0 indicates
315
     *                                                              completely opaque while 127 indicates completely
316
     *                                                              transparent.
317
     *
318
     *  @since 2.2.2
319
     *
320
     *  @return boolean             Returns TRUE on success or FALSE on error.
321
     *
322
     *                              If {@link http://php.net/manual/en/function.imagefilter.php imagefilter} is not
323
     *                              available the method will return FALSE without setting an {@link error} code.
324
     *
325
     *                              If the requested filter doesn't exist, or invalid arguments are passed, the method
326
     *                              will trigger a warning.
327
     *
328
     *                              If FALSE is returned and you are sure that
329
     *                              {@link http://php.net/manual/en/function.imagefilter.php imagefilter} exists and that
330
     *                              the requested filter is valid, check the {@link error} property to see the error code.
331
     */
332
    public function apply_filter($filter, $arg1 = '', $arg2 = '', $arg3 = '', $arg4 = '') {
333
334
        // if "imagefilter" function exists and the requested filter exists
335
        if (function_exists('imagefilter'))
336
337
            // if image resource was successfully created
338
            if ($this->_create_from_source()) {
339
340
                // prepare the target image
341
                $target_identifier = $this->_prepare_image($this->source_width, $this->source_height, -1);
342
343
                // copy the original image
344
                imagecopyresampled(
345
346
                    $target_identifier,
347
                    $this->source_identifier,
348
                    0,
349
                    0,
350
                    0,
351
                    0,
352
                    $this->source_width,
353
                    $this->source_height,
354
                    $this->source_width,
355
                    $this->source_height
356
357
                );
358
359
                // if multiple filters are to be applied at once
360
                if (is_array($filter)) {
361
362
                    // iterate through the filters
363
                    foreach ($filter as $arguments)
364
365
                        // if filter exists
366
                        if (defined('IMG_FILTER_' . strtoupper($arguments[0]))) {
367
368
                            // try to apply the filter...
369
                            if (!@imagefilter(...array_merge([$target_identifier, constant('IMG_FILTER_' . strtoupper($arguments[0]))], array_slice($arguments, 1))))
370
371
                                // ...and trigger an error if the filter could not be applied
372
                                trigger_error('Invalid arguments used for "' . strtoupper($arguments[0]) . '" filter', E_USER_WARNING);
373
374
                        // if filter doesn't exists, trigger an error
375
                        } else trigger_error('Filter "' . strtoupper($arguments[0]) . '" is not available', E_USER_WARNING);
376
377
                // if a single filter is to be applied and it is available
378
                } elseif (defined('IMG_FILTER_' . strtoupper($filter))) {
379
380
                    // get all the arguments passed to the method
381
                    $arguments = func_get_args();
382
383
                    // try to apply the filter...
384
                    if (!@imagefilter(...array_merge([$target_identifier, constant('IMG_FILTER_' . strtoupper($filter))], array_slice($arguments, 1))))
385
386
                        // ...and trigger an error if the filter could not be applied
387
                        trigger_error('Invalid arguments used for "' . strtoupper($arguments[0]) . '" filter', E_USER_WARNING);
388
389
                // if filter doesn't exists, trigger an error
390
                } else trigger_error('Filter "' . strtoupper($arguments[0]) . '" is not available', E_USER_WARNING);
391
392
                // write image
393
                return $this->_write_image($target_identifier);
394
395
            }
396
397
        // if script gets this far, return false
398
        // note that we do not set the error level as it has been already set
399
        // by the _create_from_source() method earlier, if the case
400
        return false;
401
402
    }
403
404
    /**
405
     *  Crops a portion of the image given as {@link source_path} and outputs it as the file specified as {@link target_path}.
406
     *
407
     *  <code>
408
     *  // include the Zebra_Image library
409
     *  require 'path/to/Zebra_Image.php';
410
     *
411
     *  // instantiate the class
412
     *  $img = new Zebra_Image();
413
     *
414
     *  // a source image
415
     *  $img->source_path = 'path/to/source.ext';
416
     *
417
     *  // path to where should the resulting image be saved
418
     *  // note that by simply setting a different extension to the file will
419
     *  // instruct the script to create an image of that particular type
420
     *  $img->target_path = 'path/to/target.ext';
421
     *
422
     *  // crop a rectangle of 100x100 pixels, starting from the top-left corner
423
     *  $img->crop(0, 0, 100, 100);
424
     *  </code>
425
     *
426
     *  @param  integer     $start_x    x coordinate to start cropping from
427
     *
428
     *  @param  integer     $start_y    y coordinate to start cropping from
429
     *
430
     *  @param  integer     $end_x      x coordinate where to end the cropping
431
     *
432
     *  @param  integer     $end_y      y coordinate where to end the cropping
433
     *
434
     *  @since  1.0.4
435
     *
436
     *  @return boolean     Returns TRUE on success or FALSE on error.
437
     *
438
     *                      If FALSE is returned, check the {@link error} property to see the error code.
439
     */
440
    public function crop($start_x, $start_y, $end_x, $end_y) {
441
442
        // this method might be also called internally
443
        // in this case, there's a fifth argument that points to an already existing image identifier
444
        $args = func_get_args();
445
446
        // if fifth argument exists
447
        if (isset($args[4]) && is_resource($args[4])) {
448
449
            // that it is the image identifier that we'll be using further on
450
            $this->source_identifier = $args[4];
451
452
            // set this to true so that the script will continue to execute at the next IF
453
            $result = true;
454
455
        // if method is called as usually
456
        // try to create an image resource from source path
457
        } else $result = $this->_create_from_source();
458
459
        // if image resource was successfully created
460
        if (false !== $result) {
461
462
            // prepare the target image
463
            $target_identifier = $this->_prepare_image($end_x - $start_x, $end_y - $start_y, -1);
464
465
            // crop the image
466
            imagecopyresampled(
467
468
                $target_identifier,
469
                $this->source_identifier,
470
                0,
471
                0,
472
                $start_x,
473
                $start_y,
474
                $end_x - $start_x,
475
                $end_y - $start_y,
476
                $end_x - $start_x,
477
                $end_y - $start_y
478
479
            );
480
481
            // write image
482
            return $this->_write_image($target_identifier);
483
484
        }
485
486
        // if script gets this far, return false
487
        // note that we do not set the error level as it has been already set
488
        // by the _create_from_source() method earlier
489
        return false;
490
491
    }
492
493
    /**
494
     *  Flips both horizontally and vertically the image given as {@link source_path} and outputs the resulted image as
495
     *  {@link target_path}
496
     *
497
     *  <code>
498
     *  // include the Zebra_Image library
499
     *  require 'path/to/Zebra_Image.php';
500
     *
501
     *  // instantiate the class
502
     *  $img = new Zebra_Image();
503
     *
504
     *  // a source image
505
     *  $img->source_path = 'path/to/source.ext';
506
     *
507
     *  // path to where should the resulting image be saved
508
     *  // note that by simply setting a different extension to the file will
509
     *  // instruct the script to create an image of that particular type
510
     *  $img->target_path = 'path/to/target.ext';
511
     *
512
     *  // flip the image both horizontally and vertically
513
     *  $img->flip_both();
514
     *  </code>
515
     *
516
     *  @since 2.1
517
     *
518
     *  @return boolean     Returns TRUE on success or FALSE on error.
519
     *
520
     *                      If FALSE is returned, check the {@link error} property to see the error code.
521
     */
522
    public function flip_both() {
523
524
        return $this->_flip('both');
525
526
    }
527
528
    /**
529
     *  Flips horizontally the image given as {@link source_path} and outputs the resulted image as {@link target_path}
530
     *
531
     *  <code>
532
     *  // include the Zebra_Image library
533
     *  require 'path/to/Zebra_Image.php';
534
     *
535
     *  // instantiate the class
536
     *  $img = new Zebra_Image();
537
     *
538
     *  // a source image
539
     *  $img->source_path = 'path/to/source.ext';
540
     *
541
     *  // path to where should the resulting image be saved
542
     *  // note that by simply setting a different extension to the file will
543
     *  // instruct the script to create an image of that particular type
544
     *  $img->target_path = 'path/to/target.ext';
545
     *
546
     *  // flip the image horizontally
547
     *  $img->flip_horizontal();
548
     *  </code>
549
     *
550
     *  @return boolean     Returns TRUE on success or FALSE on error.
551
     *
552
     *                      If FALSE is returned, check the {@link error} property to see the error code.
553
     */
554
    public function flip_horizontal() {
555
556
        return $this->_flip('horizontal');
557
558
    }
559
560
    /**
561
     *  Flips vertically the image given as {@link source_path} and outputs the resulted image as {@link target_path}
562
     *
563
     *  <code>
564
     *  // include the Zebra_Image library
565
     *  require 'path/to/Zebra_Image.php';
566
     *
567
     *  // instantiate the class
568
     *  $img = new Zebra_Image();
569
     *
570
     *  // a source image
571
     *  $img->source_path = 'path/to/source.ext';
572
     *
573
     *  // path to where should the resulting image be saved
574
     *  // note that by simply setting a different extension to the file will
575
     *  // instruct the script to create an image of that particular type
576
     *  $img->target_path = 'path/to/target.ext';
577
     *
578
     *  // flip the image vertically
579
     *  $img->flip_vertical();
580
     *  </code>
581
     *
582
     *  @return boolean     Returns TRUE on success or FALSE on error.
583
     *
584
     *                      If FALSE is returned, check the {@link error} property to see the error code.
585
     */
586
    public function flip_vertical() {
587
588
        return $this->_flip('vertical');
589
590
    }
591
592
    /**
593
     *  Resizes the image given as {@link source_path} and outputs the resulted image as {@link target_path}.
594
     *
595
     *  <code>
596
     *  // include the Zebra_Image library
597
     *  require 'path/to/Zebra_Image.php';
598
     *
599
     *  // instantiate the class
600
     *  $img = new Zebra_Image();
601
     *
602
     *  // a source image
603
     *  $img->source_path = 'path/to/source.ext';
604
     *
605
     *  // path to where should the resulting image be saved
606
     *  // note that by simply setting a different extension to the file will
607
     *  // instruct the script to create an image of that particular type
608
     *  $img->target_path = 'path/to/target.ext';
609
     *
610
     *  // apply a "sharpen" filter to the resulting images
611
     *  $img->sharpen_images = true;
612
     *
613
     *  // resize the image to exactly 150x150 pixels, without altering aspect ratio, by using the CROP_CENTER method
614
     *  $img->resize(150, 150, ZEBRA_IMAGE_CROP_CENTER);
615
     *  </code>
616
     *
617
     * @param  integer $width                   The width to resize the image to.
618
     *
619
     *                                          If set to <b>0</b>, the width will be automatically adjusted, depending
620
     *                                          on the value of the <b>height</b> argument so that the image preserves
621
     *                                          its aspect ratio.
622
     *
623
     *                                          If {@link preserve_aspect_ratio} is set to TRUE and both this and the
624
     *                                          <b>height</b> arguments are values greater than <b>0</b>, the image will
625
     *                                          be resized to the exact required width and height and the aspect ratio
626
     *                                          will be preserved - (also see the description for the <b>method</b>
627
     *                                          argument below on how can this be done).
628
     *
629
     *                                          If {@link preserve_aspect_ratio} is set to FALSE, the image will be
630
     *                                          resized to the required width and the aspect ratio will be ignored.
631
     *
632
     *                                          If both <b>width</b> and <b>height</b> are set to <b>0</b>, a copy of
633
     *                                          the source image will be created ({@link jpeg_quality} and
634
     *                                          {@link png_compression} will still apply).
635
     *
636
     *                                          If either <b>width</b> or <b>height</b> are set to <b>0</b>, the script
637
     *                                          will consider the value of the {@link preserve_aspect_ratio} to bet set
638
     *                                          to TRUE regardless of its actual value!
639
     *
640
     * @param  integer $height                  The height to resize the image to.
641
     *
642
     *                                          If set to <b>0</b>, the height will be automatically adjusted, depending
643
     *                                          on the value of the <b>width</b> argument so that the image preserves
644
     *                                          its aspect ratio.
645
     *
646
     *                                          If {@link preserve_aspect_ratio} is set to TRUE and both this and the
647
     *                                          <b>width</b> arguments are values greater than <b>0</b>, the image will
648
     *                                          be resized to the exact required width and height and the aspect ratio
649
     *                                          will be preserved - (also see the description for the <b>method</b>
650
     *                                          argument below on how can this be done).
651
     *
652
     *                                          If {@link preserve_aspect_ratio} is set to FALSE, the image will be
653
     *                                          resized to the required height and the aspect ratio will be ignored.
654
     *
655
     *                                          If both <b>width</b> and <b>height</b> are set to <b>0</b>, a copy of
656
     *                                          the source image will be created ({@link jpeg_quality} and
657
     *                                          {@link png_compression} will still apply).
658
     *
659
     *                                          If either <b>height</b> or <b>width</b> are set to <b>0</b>, the script
660
     *                                          will consider the value of the {@link preserve_aspect_ratio} to bet set
661
     *                                          to TRUE regardless of its actual value!
662
     *
663
     * @param  int     $method                  (Optional) Method to use when resizing images to exact width and height
664
     *                                          while preserving aspect ratio.
665
     *
666
     *                                          If the {@link preserve_aspect_ratio} property is set to TRUE and both the
667
     *                                          <b>width</b> and <b>height</b> arguments are values greater than <b>0</b>,
668
     *                                          the image will be resized to the exact given width and height and the
669
     *                                          aspect ratio will be preserved by using on of the following methods:
670
     *
671
     *                                          -   <b>ZEBRA_IMAGE_BOXED</b> - the image will be scaled so that it will
672
     *                                              fit in a box with the given width and height (both width/height will
673
     *                                              be smaller or equal to the required width/height) and then it will
674
     *                                              be centered both horizontally and vertically. The blank area will be
675
     *                                              filled with the color specified by the <b>bgcolor</b> argument. (the
676
     *                                              blank area will be filled only if the image is not transparent!)
677
     *
678
     *                                          -   <b>ZEBRA_IMAGE_NOT_BOXED</b> - the image will be scaled so that it
679
     *                                              <i>could</i> fit in a box with the given width and height but will
680
     *                                              not be enclosed in a box with given width and height. The new width/
681
     *                                              height will be both smaller or equal to the required width/height
682
     *
683
     *                                          -   <b>ZEBRA_IMAGE_CROP_TOPLEFT</b>
684
     *                                          -   <b>ZEBRA_IMAGE_CROP_TOPCENTER</b>
685
     *                                          -   <b>ZEBRA_IMAGE_CROP_TOPRIGHT</b>
686
     *                                          -   <b>ZEBRA_IMAGE_CROP_MIDDLELEFT</b>
687
     *                                          -   <b>ZEBRA_IMAGE_CROP_CENTER</b>
688
     *                                          -   <b>ZEBRA_IMAGE_CROP_MIDDLERIGHT</b>
689
     *                                          -   <b>ZEBRA_IMAGE_CROP_BOTTOMLEFT</b>
690
     *                                          -   <b>ZEBRA_IMAGE_CROP_BOTTOMCENTER</b>
691
     *                                          -   <b>ZEBRA_IMAGE_CROP_BOTTOMRIGHT</b>
692
     *
693
     *                                          For the methods involving crop, first the image is scaled so that both
694
     *                                          its sides are equal or greater than the respective sizes of the bounding
695
     *                                          box; next, a region of required width and height will be cropped from
696
     *                                          indicated region of the resulted image.
697
     *
698
     *                                          Default is ZEBRA_IMAGE_CROP_CENTER
699
     *
700
     * @param string   $background_color        (Optional) The hexadecimal color (like "#FFFFFF" or "#FFF") of the
701
     *                                          blank area. See the <b>method</b> argument.
702
     *
703
     *                                          When set to -1 the script will preserve transparency for transparent GIF
704
     *                                          and PNG images. For non-transparent images the background will be white
705
     *                                          in this case.
706
     *
707
     *                                          Default is #FFFFFF.
708
     *
709
     * @return boolean                         Returns TRUE on success or FALSE on error.
710
     *
711
     *                                          If FALSE is returned, check the {@link error} property to see what went
712
     *                                          wrong
713
     */
714
    public function resize($width = 0, $height = 0, $method = ZEBRA_IMAGE_CROP_CENTER, $background_color = '#FFFFFF') {
715
716
        // if image resource was successfully created
717
        if ($this->_create_from_source()) {
718
719
            // if either width or height is to be adjusted automatically
720
            // set a flag telling the script that, even if $preserve_aspect_ratio is set to false
721
            // treat everything as if it was set to true
722
            if (0 == $width || 0 == $height) $auto_preserve_aspect_ratio = true;
723
724
            // if aspect ratio needs to be preserved
725
            if ($this->preserve_aspect_ratio || isset($auto_preserve_aspect_ratio)) {
726
727
                // if height is given and width is to be computed accordingly
728
                if (0 == $width && $height > 0) {
729
730
                    // get the original image's aspect ratio
731
                    $aspect_ratio = $this->source_width / $this->source_height;
732
733
                    // the target image's height is as given as argument to the method
734
                    $target_height = $height;
735
736
                    // compute the target image's width, preserving the aspect ratio
737
                    $target_width = round($height * $aspect_ratio);
738
739
                // if width is given and height is to be computed accordingly
740
                } elseif ($width > 0 && 0 == $height) {
741
742
                    // get the original image's aspect ratio
743
                    $aspect_ratio = $this->source_height / $this->source_width;
744
745
                    // the target image's width is as given as argument to the method
746
                    $target_width = $width;
747
748
                    // compute the target image's height, preserving the aspect ratio
749
                    $target_height = round($width * $aspect_ratio);
750
751
                // if both width and height are given and ZEBRA_IMAGE_BOXED or ZEBRA_IMAGE_NOT_BOXED methods are to be used
752
                } elseif ($width > 0 && $height > 0 && (0 == $method || 1 == $method)) {
753
754
                    // compute the horizontal and vertical aspect ratios
755
                    $vertical_aspect_ratio = $height / $this->source_height;
756
                    $horizontal_aspect_ratio = $width / $this->source_width;
757
758
                    // if the image's newly computed height would be inside the bounding box
759
                    if (round($horizontal_aspect_ratio * $this->source_height < $height)) {
760
761
                        // the target image's width is as given as argument to the method
762
                        $target_width = $width;
763
764
                        // compute the target image's height so that the image will stay inside the bounding box
765
                        $target_height = round($horizontal_aspect_ratio * $this->source_height);
766
767
                    // otherwise
768
                    } else {
769
770
                        // the target image's height is as given as argument to the method
771
                        $target_height = $height;
772
773
                        // compute the target image's width so that the image will stay inside the bounding box
774
                        $target_width = round($vertical_aspect_ratio * $this->source_width);
775
776
                    }
777
778
                // if both width and height are given and image is to be cropped in order to get to the required size
779
                } elseif ($width > 0 && $height > 0 && $method > 1 && $method < 11) {
780
781
                    // compute the horizontal and vertical aspect ratios
782
                    $vertical_aspect_ratio = $this->source_height / $height;
783
                    $horizontal_aspect_ratio = $this->source_width /  $width;
784
785
                    // we'll use one of the two
786
                    $aspect_ratio =
787
788
                        $vertical_aspect_ratio < $horizontal_aspect_ratio ?
789
790
                        $vertical_aspect_ratio :
791
792
                        $horizontal_aspect_ratio;
793
794
                    // compute the target image's width, preserving the aspect ratio
795
                    $target_width = round($this->source_width / $aspect_ratio);
796
797
                    // compute the target image's height, preserving the aspect ratio
798
                    $target_height = round($this->source_height / $aspect_ratio);
799
800
                // for any other case
801
                } else {
802
803
                    // we will create a copy of the source image
804
                    $target_width = $this->source_width;
805
                    $target_height = $this->source_height;
806
807
                }
808
809
            // if aspect ratio does not need to be preserved
810
            } else {
811
812
                // compute the target image's width
813
                $target_width = ($width > 0 ? $width : $this->source_width);
814
815
                // compute the target image's height
816
                $target_height = ($height > 0 ? $height : $this->source_height);
817
818
            }
819
820
            // if
821
            if (
822
823
                // all images are to be resized - including images that are smaller than the given width/height
824
                $this->enlarge_smaller_images ||
825
826
                // smaller images than the given width/height are to be left untouched
827
                // but current image has at leas one side that is larger than the required width/height
828
                ($width > 0 && $height > 0 ?
829
830
                    ($this->source_width > $width || $this->source_height > $height) :
831
832
                    ($this->source_width > $target_width || $this->source_height > $target_height)
833
834
                )
835
836
            ) {
837
838
                // if
839
                if (
840
841
                    // aspect ratio needs to be preserved AND
842
                    ($this->preserve_aspect_ratio || isset($auto_preserve_aspect_ratio)) &&
843
844
                    // both width and height are given
845
                    ($width > 0 && $height > 0) &&
846
847
                    // images are to be cropped
848
                    ($method > 1 && $method < 11)
849
850
                ) {
851
852
                    // prepare the target image
853
                    $target_identifier = $this->_prepare_image($target_width, $target_height, $background_color);
854
855
                    imagecopyresampled(
856
857
                        $target_identifier,
858
                        $this->source_identifier,
859
                        0,
860
                        0,
861
                        0,
862
                        0,
863
                        $target_width,
864
                        $target_height,
865
                        $this->source_width,
866
                        $this->source_height
867
868
                    );
869
870
                    // do the crop according to the required method
871
                    switch ($method) {
872
873
                        // if image needs to be cropped from the top-left corner
874
                        case ZEBRA_IMAGE_CROP_TOPLEFT:
875
876
                            // crop accordingly
877
                            return $this->crop(
878
                                0,
879
                                0,
880
                                $width,
881
                                $height,
882
                                $target_identifier // crop this resource instead
883
                            );
884
885
                            break;
886
887
                        // if image needs to be cropped from the top-center
888
                        case ZEBRA_IMAGE_CROP_TOPCENTER:
889
890
                            // crop accordingly
891
                            return $this->crop(
892
                                floor(($target_width - $width) / 2),
893
                                0,
894
                                floor(($target_width - $width) / 2) + $width,
895
                                $height,
896
                                $target_identifier // crop this resource instead
897
                            );
898
899
                            break;
900
901
                        // if image needs to be cropped from the top-right corner
902
                        case ZEBRA_IMAGE_CROP_TOPRIGHT:
903
904
                            // crop accordingly
905
                            return $this->crop(
906
                                $target_width - $width,
907
                                0,
908
                                $target_width,
909
                                $height,
910
                                $target_identifier // crop this resource instead
911
                            );
912
913
                            break;
914
915
                        // if image needs to be cropped from the middle-left
916
                        case ZEBRA_IMAGE_CROP_MIDDLELEFT:
917
918
                            // crop accordingly
919
                            return $this->crop(
920
921
                                0,
922
                                floor(($target_height - $height) / 2),
923
                                $width,
924
                                floor(($target_height - $height) / 2) + $height,
925
                                $target_identifier // crop this resource instead
926
927
                            );
928
929
                            break;
930
931
                        // if image needs to be cropped from the center of the image
932
                        case ZEBRA_IMAGE_CROP_CENTER:
933
934
                            // crop accordingly
935
                            return $this->crop(
936
937
                                floor(($target_width - $width) / 2),
938
                                floor(($target_height - $height) / 2),
939
                                floor(($target_width - $width) / 2) + $width,
940
                                floor(($target_height - $height) / 2) + $height,
941
                                $target_identifier // crop this resource instead
942
943
                            );
944
945
                            break;
946
947
                        // if image needs to be cropped from the middle-right
948
                        case ZEBRA_IMAGE_CROP_MIDDLERIGHT:
949
950
                            // crop accordingly
951
                            return $this->crop(
952
953
                                $target_width - $width,
954
                                floor(($target_height - $height) / 2),
955
                                $target_width,
956
                                floor(($target_height - $height) / 2) + $height,
957
                                $target_identifier // crop this resource instead
958
959
                            );
960
961
                            break;
962
963
                        // if image needs to be cropped from the bottom-left corner
964
                        case ZEBRA_IMAGE_CROP_BOTTOMLEFT:
965
966
                            // crop accordingly
967
                            return $this->crop(
968
969
                                0,
970
                                $target_height - $height,
971
                                $width,
972
                                $target_height,
973
                                $target_identifier // crop this resource instead
974
975
                            );
976
977
                            break;
978
979
                        // if image needs to be cropped from the bottom-center
980
                        case ZEBRA_IMAGE_CROP_BOTTOMCENTER:
981
982
                            // crop accordingly
983
                            return $this->crop(
984
985
                                floor(($target_width - $width) / 2),
986
                                $target_height - $height,
987
                                floor(($target_width - $width) / 2) + $width,
988
                                $target_height,
989
                                $target_identifier // crop this resource instead
990
991
                            );
992
993
                            break;
994
995
                        // if image needs to be cropped from the bottom-right corner
996
                        case ZEBRA_IMAGE_CROP_BOTTOMRIGHT:
997
998
                            // crop accordingly
999
                            return $this->crop(
1000
1001
                                $target_width - $width,
1002
                                $target_height - $height,
1003
                                $target_width,
1004
                                $target_height,
1005
                                $target_identifier // crop this resource instead
1006
1007
                            );
1008
1009
                            break;
1010
1011
                    }
1012
1013
                // if aspect ratio doesn't need to be preserved or
1014
                // it needs to be preserved and method is ZEBRA_IMAGE_BOXED or ZEBRA_IMAGE_NOT_BOXED
1015
                } else {
1016
1017
                    // prepare the target image
1018
                    $target_identifier = $this->_prepare_image(
1019
                        ($width > 0 && $height > 0 && ZEBRA_IMAGE_NOT_BOXED != $method ? $width : $target_width),
1020
                        ($width > 0 && $height > 0 && ZEBRA_IMAGE_NOT_BOXED != $method ? $height : $target_height),
1021
                        $background_color
1022
                    );
1023
1024
                    imagecopyresampled(
1025
1026
                        $target_identifier,
1027
                        $this->source_identifier,
1028
                        ($width > 0 && $height > 0 && ZEBRA_IMAGE_NOT_BOXED != $method ? ($width - $target_width) / 2 : 0),
1029
                        ($width > 0 && $height > 0 && ZEBRA_IMAGE_NOT_BOXED != $method ? ($height - $target_height) / 2 : 0),
1030
                        0,
1031
                        0,
1032
                        $target_width,
1033
                        $target_height,
1034
                        $this->source_width,
1035
                        $this->source_height
1036
1037
                    );
1038
1039
                    // if script gets this far, write the image to disk
1040
                    return $this->_write_image($target_identifier);
1041
1042
                }
1043
1044
            // if we get here it means that
1045
            // smaller images than the given width/height are to be left untouched
1046
            // therefore, we save the image as it is
1047
            } else {
1048
1049
                // prepare the target image
1050
                $target_identifier = $this->_prepare_image($this->source_width, $this->source_height, $background_color);
1051
1052
                imagecopyresampled(
1053
1054
                    $target_identifier,
1055
                    $this->source_identifier,
1056
                    0,
1057
                    0,
1058
                    0,
1059
                    0,
1060
                    $this->source_width,
1061
                    $this->source_height,
1062
                    $this->source_width,
1063
                    $this->source_height
1064
1065
                );
1066
1067
                // previously to 2.2.7 I was simply calling the _write_images() method without the code from above this
1068
                // comment and therefore, when resizing transparent images to a format which doesn't support transparency
1069
                // and the "enlarge_smaller_images" property being set to FALSE, the "background_color" argument was not
1070
                // applied and lead to unexpected background colors for the resulting images
1071
                return $this->_write_image($target_identifier);
1072
1073
            }
1074
1075
        }
1076
1077
        // if script gets this far return false
1078
        // note that we do not set the error level as it has been already set
1079
        // by the _create_from_source() method earlier
1080
        return false;
1081
1082
    }
1083
1084
    /**
1085
     *  Rotates the image given as {@link source_path} and outputs the resulted image as {@link target_path}.
1086
     *
1087
     *  <code>
1088
     *  // include the Zebra_Image library
1089
     *  require 'path/to/Zebra_Image.php';
1090
     *
1091
     *  // instantiate the class
1092
     *  $img = new Zebra_Image();
1093
     *
1094
     *  // a source image
1095
     *  $img->source_path = 'path/to/source.ext';
1096
     *
1097
     *  // path to where should the resulting image be saved
1098
     *  // note that by simply setting a different extension to the file will
1099
     *  // instruct the script to create an image of that particular type
1100
     *  $img->target_path = 'path/to/target.ext';
1101
     *
1102
     *  // rotate the image 45 degrees, clockwise
1103
     *  $img->rotate(45);
1104
     *  </code>
1105
     *
1106
     *  @param  double  $angle                  Angle by which to rotate the image clockwise.
1107
     *
1108
     *                                          Between 0 and 360.
1109
     *
1110
     *  @param  mixed   $background_color       (Optional) The hexadecimal color (like "#FFFFFF" or "#FFF") of the
1111
     *                                          uncovered zone after the rotation.
1112
     *
1113
     *                                          When set to -1 the script will preserve transparency for transparent GIF
1114
     *                                          and PNG images. For non-transparent images the background will be white
1115
     *                                          in this case.
1116
     *
1117
     *                                          Default is -1.
1118
     *
1119
     *  @return boolean                         Returns TRUE on success or FALSE on error.
1120
     *
1121
     *                                          If FALSE is returned, check the {@link error} property to see the error
1122
     *                                          code.
1123
     */
1124
    public function rotate($angle, $background_color = -1) {
1125
1126
        // get function arguments
1127
        $arguments = func_get_args();
1128
1129
        // if a third argument exists
1130
        $use_existing_source = (isset($arguments[2]) && false === $arguments[2]);
1131
1132
        // if we came here just to fix orientation or if image resource was successfully created
1133
        if ($use_existing_source || $this->_create_from_source()) {
1134
1135
            // angles are given clockwise but imagerotate works counterclockwise so we need to negate our value
1136
            $angle = -$angle;
1137
1138
            // if source image is PNG
1139
            if (IMAGETYPE_PNG == $this->source_type && -1 == $background_color) {
1140
1141
                // rotate the image
1142
                // but if using -1 as background color didn't work (as is the case for PNG8)
1143
                if (!($target_identifier = imagerotate($this->source_identifier, $angle, -1))) {
1144
1145
                    // we will be using #FFF as the color to fill the uncovered zone after the rotation
1146
                    $background_color = imagecolorallocate($this->source_identifier, 255, 255, 255);
1147
1148
                    // rotate the image
1149
                    $target_identifier = imagerotate($this->source_identifier, $angle, $background_color);
1150
1151
                }
1152
1153
            // if source image is a transparent GIF
1154
            } elseif (IMAGETYPE_GIF == $this->source_type && $this->source_transparent_color_index >= 0) {
1155
1156
                // convert the background color to RGB values
1157
                $background_color = $this->_hex2rgb($background_color);
1158
1159
                // allocate the color to the image identifier
1160
                $background_color = imagecolorallocate(
1161
1162
                    $this->source_identifier,
1163
                    $background_color['r'],
1164
                    $background_color['g'],
1165
                    $background_color['b']
1166
1167
                );
1168
1169
                // rotate the image
1170
                $this->source_identifier = imagerotate($this->source_identifier, $angle, $background_color);
1171
1172
                // get the width of rotated image
1173
                $width = imagesx($this->source_identifier);
1174
1175
                // get the height of rotated image
1176
                $height = imagesy($this->source_identifier);
1177
1178
                // create a blank image with the new width and height
1179
                // (this intermediary step is for preserving transparency)
1180
                $target_identifier = $this->_prepare_image($width, $height, -1);
1181
1182
                // copy the rotated image on to the new one
1183
                imagecopyresampled($target_identifier, $this->source_identifier, 0, 0, 0, 0, $width, $height, $width, $height);
1184
1185
            // for the other cases
1186
            } else {
1187
1188
                // convert the color to RGB values
1189
                $background_color = $this->_hex2rgb($background_color);
1190
1191
                // allocate the color to the image identifier
1192
                $background_color = imagecolorallocate(
1193
1194
                    $this->source_identifier,
1195
                    $background_color['r'],
1196
                    $background_color['g'],
1197
                    $background_color['b']
1198
1199
                );
1200
1201
                // rotate the image
1202
                $target_identifier = imagerotate($this->source_identifier, $angle, $background_color);
1203
1204
            }
1205
1206
            // if we called this method from the _create_from_source() method
1207
            // because we are fixing orientation
1208
            if ($use_existing_source) {
1209
1210
                // make any further method work on the rotated image
1211
                $this->source_identifier = $target_identifier;
1212
1213
                // update the width and height of the image to the values
1214
                // of the rotated image
1215
                $this->source_width = imagesx($target_identifier);
1216
                $this->source_height = imagesy($target_identifier);
1217
1218
                return true;
1219
1220
            // write image otherwise
1221
            } else return $this->_write_image($target_identifier);
1222
1223
        }
1224
1225
        // if script gets this far return false
1226
        // note that we do not set the error level as it has been already set
1227
        // by the _create_from_source() method earlier
1228
       return false;
1229
1230
    }
1231
1232
    /**
1233
     *  Returns an array containing the image identifier representing the image obtained from {@link $source_path}, the
1234
     *  image's width and height and the image's type
1235
     *
1236
     *  @access private
1237
     */
1238
    private function _create_from_source() {
1239
1240
        // perform some error checking first
1241
        // if the GD library is not installed
1242
        if (!function_exists('gd_info')) {
1243
1244
            // save the error level and stop the execution of the script
1245
            $this->error = 7;
1246
1247
            return false;
1248
1249
        // if source file does not exist
1250
        } elseif (!is_file($this->source_path)) {
1251
1252
            // save the error level and stop the execution of the script
1253
            $this->error = 1;
1254
1255
            return false;
1256
1257
        // if source file is not readable
1258
        } elseif (!is_readable($this->source_path)) {
1259
1260
            // save the error level and stop the execution of the script
1261
            $this->error = 2;
1262
1263
            return false;
1264
1265
        // if target file is same as source file and source file is not writable
1266
        } elseif ($this->target_path == $this->source_path && !is_writable($this->source_path)) {
1267
1268
            // save the error level and stop the execution of the script
1269
            $this->error = 3;
1270
1271
            return false;
1272
1273
        // try to get source file width, height and type
1274
        // and if it founds an unsupported file type
1275
        } elseif (!list($this->source_width, $this->source_height, $this->source_type) = @getimagesize($this->source_path)) {
1276
1277
            // save the error level and stop the execution of the script
1278
            $this->error = 4;
1279
1280
            return false;
1281
1282
        // if no errors so far
1283
        } else {
1284
1285
            // get target file's type based on the file extension
1286
            $this->target_type = strtolower(substr($this->target_path, strrpos($this->target_path, '.') + 1));
1287
1288
            // create an image from file using extension dependant function
1289
            // checks for file extension
1290
            switch ($this->source_type) {
1291
1292
                // if GIF
1293
                case IMAGETYPE_GIF:
1294
1295
                    // create an image from file
1296
                    $identifier = imagecreatefromgif($this->source_path);
1297
1298
                    // get the index of the transparent color (if any)
1299
                    if (($this->source_transparent_color_index = imagecolortransparent($identifier)) >= 0)
1300
1301
                        // get the transparent color's RGB values
1302
                        // we have to mute errors because there are GIF images which *are* transparent and everything
1303
                        // works as expected, but imagecolortransparent() returns a color that is outside the range of
1304
                        // colors in the image's pallette...
1305
                        $this->source_transparent_color = @imagecolorsforindex($identifier, $this->source_transparent_color_index);
1306
1307
                    break;
1308
1309
                // if JPEG
1310
                case IMAGETYPE_JPEG:
1311
1312
                    // create an image from file
1313
                    $identifier = imagecreatefromjpeg($this->source_path);
1314
1315
                    break;
1316
1317
                // if PNG
1318
                case IMAGETYPE_PNG:
1319
1320
                    // create an image from file
1321
                    $identifier = imagecreatefrompng($this->source_path);
1322
1323
                    // disable blending
1324
                    imagealphablending($identifier, false);
1325
1326
                    break;
1327
1328
                default:
1329
1330
                    // if unsupported file type
1331
                    // note that we call this if the file is not GIF, JPG or PNG even though the getimagesize function
1332
                    // handles more image types
1333
                    $this->error = 4;
1334
1335
                    return false;
1336
1337
            }
1338
1339
        }
1340
1341
        // if target file has to have the same timestamp as the source image
1342
        // save it as a global property of the class
1343
        if ($this->preserve_time) $this->source_image_time = filemtime($this->source_path);
1344
1345
        // make available the source image's identifier
1346
        $this->source_identifier = $identifier;
1347
1348
        // for JPEG files, if we need to handle exif orientation automatically
1349
        if ($this->auto_handle_exif_orientation && IMAGETYPE_JPEG === $this->source_type)
1350
1351
            // if "exif_read_data" function is not available, return false
1352
            if (!function_exists('exif_read_data')) {
1353
1354
                // save the error level and stop the execution of the script
1355
                $this->error = 9;
1356
1357
                return false;
1358
1359
            // if "exif_read_data" function is available, EXIF information is available, orientation information is available and orientation needs fixing
1360
            } elseif (($exif = exif_read_data($this->source_path)) && isset($exif['Orientation']) && in_array($exif['Orientation'], [3, 6, 8])) {
1361
1362
                // fix the orientation
1363
                switch ($exif['Orientation']) {
1364
1365
                    case 3:
1366
1367
                        // 180 rotate left
1368
                        $this->rotate(180, -1, false);
1369
                        break;
1370
1371
                    case 6:
1372
1373
                        // 90 rotate right
1374
                        $this->rotate(90, -1, false);
1375
                        break;
1376
1377
                    case 8:
1378
1379
                        // 90 rotate left
1380
                        $this->rotate(-90, -1, false);
1381
                        break;
1382
1383
                }
1384
1385
            }
1386
1387
        return true;
1388
1389
    }
1390
1391
    /**
1392
     *  Flips horizontally or vertically or both ways the image given as {@link source_path}.
1393
     *
1394
     * @since  2.1
1395
     *
1396
     * @access private
1397
     *
1398
     * @param $orientation
1399
     * @return boolean     Returns TRUE on success or FALSE on error.
1400
     *
1401
     *                      If FALSE is returned, check the {@link error} property to see the error code.
1402
     */
1403
    private function _flip($orientation) {
1404
1405
        // if image resource was successfully created
1406
        if ($this->_create_from_source()) {
1407
1408
            // prepare the target image
1409
            $target_identifier = $this->_prepare_image($this->source_width, $this->source_height, -1);
1410
1411
            // flip according to $orientation
1412
            switch ($orientation) {
1413
1414
                case 'horizontal':
1415
1416
                    imagecopyresampled(
1417
1418
                        $target_identifier,
1419
                        $this->source_identifier,
1420
                        0,
1421
                        0, $this->source_width - 1,
1422
                        0,
1423
                        $this->source_width,
1424
                        $this->source_height,
1425
                        -$this->source_width,
1426
                        $this->source_height
1427
1428
                    );
1429
1430
                    break;
1431
1432
                case 'vertical':
1433
1434
                    imagecopyresampled(
1435
1436
                        $target_identifier,
1437
                        $this->source_identifier,
1438
                        0,
1439
                        0,
1440
                        0, $this->source_height - 1,
1441
                        $this->source_width,
1442
                        $this->source_height,
1443
                        $this->source_width,
1444
                        -$this->source_height
1445
1446
                    );
1447
1448
                    break;
1449
1450
                case 'both':
1451
1452
                    imagecopyresampled(
1453
1454
                        $target_identifier,
1455
                        $this->source_identifier,
1456
                        0,
1457
                        0, $this->source_width - 1, $this->source_height - 1,
1458
                        $this->source_width,
1459
                        $this->source_height,
1460
                        -$this->source_width,
1461
                        -$this->source_height
1462
1463
                    );
1464
1465
                    break;
1466
1467
            }
1468
1469
            // write image
1470
            return $this->_write_image($target_identifier);
1471
1472
        }
1473
1474
        // if script gets this far, return false
1475
        // note that we do not set the error level as it has been already set
1476
        // by the _create_from_source() method earlier
1477
        return false;
1478
1479
    }
1480
1481
    /**
1482
     *  Converts a hexadecimal representation of a color (i.e. #123456 or #AAA) to a RGB representation.
1483
     *
1484
     *  The RGB values will be a value between 0 and 255 each.
1485
     *
1486
     *  @param  string  $color              Hexadecimal representation of a color (i.e. #123456 or #AAA).
1487
     *
1488
     *  @param  string  $default_on_error   Hexadecimal representation of a color to be used in case $color is not
1489
     *                                      recognized as a hexadecimal color.
1490
     *
1491
     *  @return array                       Returns an associative array with the values of (R)ed, (G)reen and (B)lue
1492
     *
1493
     *  @access private
1494
     */
1495
    private function _hex2rgb($color, $default_on_error = '#FFFFFF') {
1496
1497
        // if color is not formatted correctly
1498
        // use the default color
1499
        if (0 == preg_match('/^#?([a-f]|[0-9]){3}(([a-f]|[0-9]){3})?$/i', $color)) $color = $default_on_error;
1500
1501
        // trim off the "#" prefix from $background_color
1502
        $color = ltrim($color, '#');
1503
1504
        // if color is given using the shorthand (i.e. "FFF" instead of "FFFFFF")
1505
        if (3 == strlen($color)) {
1506
1507
            $tmp = '';
1508
1509
            // take each value
1510
            // and duplicate it
1511
            for ($i = 0; $i < 3; $i++) $tmp .= str_repeat($color[$i], 2);
1512
1513
            // the color in it's full, 6 characters length notation
1514
            $color = $tmp;
1515
1516
        }
1517
1518
        // decimal representation of the color
1519
        $int = hexdec($color);
1520
1521
        // extract and return the RGB values
1522
        return [
1523
1524
            'r' =>  0xFF & ($int >> 0x10),
1525
            'g' =>  0xFF & ($int >> 0x8),
1526
            'b' =>  0xFF & $int
1527
1528
        ];
1529
1530
    }
1531
1532
    /**
1533
     *  Creates a blank image of given width, height and background color.
1534
     *
1535
     *  @param  integer     $width              Width of the new image.
1536
     *
1537
     *  @param  integer     $height             Height of the new image.
1538
     *
1539
     *  @param  string      $background_color   (Optional) The hexadecimal color of the background.
1540
     *
1541
     *                                          Can also be -1 case in which the script will try to create a transparent
1542
     *                                          image, if possible.
1543
     *
1544
     *                                          Default is "#FFFFFF".
1545
     *
1546
     *  @return  resource   identifier of the newly created image.
1547
     *
1548
     *  @access private
1549
     */
1550
    private function _prepare_image($width, $height, $background_color = '#FFFFFF') {
1551
1552
        // create a blank image
1553
        $identifier = imagecreatetruecolor((int)$width <= 0 ? 1 : (int)$width, (int)$height <= 0 ? 1 : (int)$height);
1554
1555
        // if we are creating a PNG image
1556
        if ('png' === $this->target_type && -1 == $background_color) {
1557
1558
            // disable blending
1559
            imagealphablending($identifier, false);
1560
1561
            // allocate a transparent color
1562
            $transparent_color = imagecolorallocatealpha($identifier, 0, 0, 0, 127);
1563
1564
            // fill the image with the transparent color
1565
			imagefill($identifier, 0, 0, $transparent_color);
1566
1567
            //save full alpha channel information
1568
			imagesavealpha($identifier, true);
1569
1570
        // if source image is a transparent GIF
1571
        } elseif ('gif' === $this->target_type && -1 == $background_color && $this->source_transparent_color_index >= 0) {
1572
1573
            // allocate the source image's transparent color also to the new image resource
1574
            $transparent_color = imagecolorallocate(
1575
                $identifier,
1576
                $this->source_transparent_color['red'],
1577
                $this->source_transparent_color['green'],
1578
                $this->source_transparent_color['blue']
1579
            );
1580
1581
            // fill the background of the new image with transparent color
1582
            imagefill($identifier, 0, 0, $transparent_color);
1583
1584
            // from now on, every pixel having the same RGB as the transparent color will be transparent
1585
            imagecolortransparent($identifier, $transparent_color);
1586
1587
        // for other image types
1588
        } else {
1589
1590
            // if transparent background color specified, revert to white
1591
            if (-1 == $background_color) $background_color = '#FFFFFF';
1592
1593
            // convert hex color to rgb
1594
            $background_color = $this->_hex2rgb($background_color);
1595
1596
            // prepare the background color
1597
            $background_color = imagecolorallocate($identifier, $background_color['r'], $background_color['g'], $background_color['b']);
1598
1599
            // fill the image with the background color
1600
            imagefill($identifier, 0, 0, $background_color);
1601
1602
        }
1603
1604
        // return the image's identifier
1605
        return $identifier;
1606
1607
    }
1608
1609
    /**
1610
     *  Sharpens images. Useful when creating thumbnails.
1611
     *
1612
     *  Code taken from the comments at {@link http://docs.php.net/imageconvolution}.
1613
     *
1614
     *  <i>This function will yield a result only for PHP version 5.1.0+ and will leave the image unaltered for older
1615
     *  versions!</i>
1616
     *
1617
     * @param $image
1618
     * @return mixed
1619
     * @access private
1620
     */
1621
    private function _sharpen_image($image) {
1622
1623
        // if the "sharpen_images" is set to true and we're running an appropriate version of PHP
1624
        // (the "imageconvolution" is available only for PHP 5.1.0+)
1625
        if ($this->sharpen_images && version_compare(PHP_VERSION, '5.1.0') >= 0) {
1626
1627
            // the convolution matrix as an array of three arrays of three floats
1628
            $matrix = [
1629
                [-1.2, -1, -1.2],
1630
                [-1, 20, -1],
1631
                [-1.2, -1, -1.2],
1632
            ];
1633
1634
            // the divisor of the matrix
1635
            $divisor = array_sum(array_map('array_sum', $matrix));
1636
1637
            // color offset
1638
            $offset = 0;
1639
1640
            // sharpen image
1641
            imageconvolution($image, $matrix, $divisor, $offset);
1642
1643
        }
1644
1645
        // return the image's identifier
1646
        return $image;
1647
1648
    }
1649
1650
    /**
1651
     *  Creates a new image from given image identifier having the extension as specified by {@link target_path}.
1652
     *
1653
     *  @param  resource $identifier identifier  An image identifier
1654
     *
1655
     *  @return boolean                 Returns TRUE on success or FALSE on error.
1656
     *
1657
     *                                  If FALSE is returned, check the {@link error} property to see the error code.
1658
     *
1659
     *  @access private
1660
     */
1661
    private function _write_image($identifier) {
1662
1663
        // sharpen image if it's required
1664
        $this->_sharpen_image($identifier);
1665
1666
        // image saving process goes according to required extension
1667
        switch ($this->target_type) {
1668
1669
            // if GIF
1670
            case 'gif':
1671
1672
                // if GD support for this file type is not available
1673
                // in version 1.6 of GD the support for GIF files was dropped see
1674
                // http://php.net/manual/en/function.imagegif.php#function.imagegif.notes
1675
                if (!function_exists('imagegif')) {
1676
1677
                    // save the error level and stop the execution of the script
1678
                    $this->error = 6;
1679
1680
                    return false;
1681
1682
                // if, for some reason, file could not be created
1683
                } elseif (@!imagegif($identifier, $this->target_path)) {
1684
1685
                    // save the error level and stop the execution of the script
1686
                    $this->error = 3;
1687
1688
                    return false;
1689
1690
                }
1691
1692
                break;
1693
1694
            // if JPEG
1695
            case 'jpg':
1696
            case 'jpeg':
1697
1698
                // if GD support for this file type is not available
1699
                if (!function_exists('imagejpeg')) {
1700
1701
                    // save the error level and stop the execution of the script
1702
                    $this->error = 6;
1703
1704
                    return false;
1705
1706
                // if, for some reason, file could not be created
1707
                } elseif (@!imagejpeg($identifier, $this->target_path, $this->jpeg_quality)) {
1708
1709
                    // save the error level and stop the execution of the script
1710
                    $this->error = 3;
1711
1712
                    return false;
1713
1714
                }
1715
1716
                break;
1717
1718
            // if PNG
1719
            case 'png':
1720
1721
                // save full alpha channel information
1722
                imagesavealpha($identifier, true);
1723
1724
                // if GD support for this file type is not available
1725
                if (!function_exists('imagepng')) {
1726
1727
                    // save the error level and stop the execution of the script
1728
                    $this->error = 6;
1729
1730
                    return false;
1731
1732
                // if, for some reason, file could not be created
1733
                } elseif (@!imagepng($identifier, $this->target_path, $this->png_compression)) {
1734
1735
                    // save the error level and stop the execution of the script
1736
                    $this->error = 3;
1737
1738
                    return false;
1739
1740
                }
1741
1742
                break;
1743
1744
            // if not a supported file extension
1745
            default:
1746
1747
                // save the error level and stop the execution of the script
1748
                $this->error = 5;
1749
1750
                return false;
1751
1752
        }
1753
1754
        // get a list of functions disabled via configuration
1755
        $disabled_functions = @ini_get('disable_functions');
1756
1757
        // if the 'chmod' function is not disabled via configuration
1758
        if ('' == $disabled_functions || false === strpos('chmod', $disabled_functions)) {
1759
1760
            // chmod the file
1761
            chmod($this->target_path, intval($this->chmod_value, 8));
1762
1763
        // save the error level
1764
        } else $this->error = 8;
1765
1766
        // if target file has to have the same timestamp as the source image
1767
        if ($this->preserve_time && isset($this->source_image_time)) {
1768
1769
            // touch the newly created file
1770
            @touch($this->target_path, $this->source_image_time);
1771
1772
        }
1773
1774
        // return true
1775
        return true;
1776
1777
    }
1778
1779
}
1780