~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/class-wp-image-editor-imagick.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * WordPress Imagick Image Editor
 
4
 *
 
5
 * @package WordPress
 
6
 * @subpackage Image_Editor
 
7
 */
 
8
 
 
9
/**
 
10
 * WordPress Image Editor Class for Image Manipulation through Imagick PHP Module
 
11
 *
 
12
 * @since 3.5.0
 
13
 * @package WordPress
 
14
 * @subpackage Image_Editor
 
15
 * @uses WP_Image_Editor Extends class
 
16
 */
 
17
class WP_Image_Editor_Imagick extends WP_Image_Editor {
 
18
 
 
19
        protected $image = null; // Imagick Object
 
20
 
 
21
        public function __destruct() {
 
22
                if ( $this->image instanceof Imagick ) {
 
23
                        // we don't need the original in memory anymore
 
24
                        $this->image->clear();
 
25
                        $this->image->destroy();
 
26
                }
 
27
        }
 
28
 
 
29
        /**
 
30
         * Checks to see if current environment supports Imagick.
 
31
         *
 
32
         * We require Imagick 2.2.0 or greater, based on whether the queryFormats()
 
33
         * method can be called statically.
 
34
         *
 
35
         * @since 3.5.0
 
36
         * @access public
 
37
         *
 
38
         * @return boolean
 
39
         */
 
40
        public static function test( $args = array() ) {
 
41
 
 
42
                // First, test Imagick's extension and classes.
 
43
                if ( ! extension_loaded( 'imagick' ) || ! class_exists( 'Imagick' ) || ! class_exists( 'ImagickPixel' ) )
 
44
                        return false;
 
45
 
 
46
                if ( version_compare( phpversion( 'imagick' ), '2.2.0', '<' ) )
 
47
                        return false;
 
48
 
 
49
                $required_methods = array(
 
50
                        'clear',
 
51
                        'destroy',
 
52
                        'valid',
 
53
                        'getimage',
 
54
                        'writeimage',
 
55
                        'getimageblob',
 
56
                        'getimagegeometry',
 
57
                        'getimageformat',
 
58
                        'setimageformat',
 
59
                        'setimagecompression',
 
60
                        'setimagecompressionquality',
 
61
                        'setimagepage',
 
62
                        'scaleimage',
 
63
                        'cropimage',
 
64
                        'rotateimage',
 
65
                        'flipimage',
 
66
                        'flopimage',
 
67
                );
 
68
 
 
69
                // Now, test for deep requirements within Imagick.
 
70
                if ( ! defined( 'imagick::COMPRESSION_JPEG' ) )
 
71
                        return false;
 
72
 
 
73
                if ( array_diff( $required_methods, get_class_methods( 'Imagick' ) ) )
 
74
                        return false;
 
75
 
 
76
                return true;
 
77
        }
 
78
 
 
79
        /**
 
80
         * Checks to see if editor supports the mime-type specified.
 
81
         *
 
82
         * @since 3.5.0
 
83
         * @access public
 
84
         *
 
85
         * @param string $mime_type
 
86
         * @return boolean
 
87
         */
 
88
        public static function supports_mime_type( $mime_type ) {
 
89
                $imagick_extension = strtoupper( self::get_extension( $mime_type ) );
 
90
 
 
91
                if ( ! $imagick_extension )
 
92
                        return false;
 
93
 
 
94
                // setIteratorIndex is optional unless mime is an animated format.
 
95
                // Here, we just say no if you are missing it and aren't loading a jpeg.
 
96
                if ( ! method_exists( 'Imagick', 'setIteratorIndex' ) && $mime_type != 'image/jpeg' )
 
97
                                return false;
 
98
 
 
99
                try {
 
100
                        return ( (bool) @Imagick::queryFormats( $imagick_extension ) );
 
101
                }
 
102
                catch ( Exception $e ) {
 
103
                        return false;
 
104
                }
 
105
        }
 
106
 
 
107
        /**
 
108
         * Loads image from $this->file into new Imagick Object.
 
109
         *
 
110
         * @since 3.5.0
 
111
         * @access protected
 
112
         *
 
113
         * @return boolean|WP_Error True if loaded; WP_Error on failure.
 
114
         */
 
115
        public function load() {
 
116
                if ( $this->image instanceof Imagick )
 
117
                        return true;
 
118
 
 
119
                if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) )
 
120
                        return new WP_Error( 'error_loading_image', __('File doesn&#8217;t exist?'), $this->file );
 
121
 
 
122
                /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */
 
123
                // Even though Imagick uses less PHP memory than GD, set higher limit for users that have low PHP.ini limits
 
124
                @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) );
 
125
 
 
126
                try {
 
127
                        $this->image = new Imagick( $this->file );
 
128
 
 
129
                        if( ! $this->image->valid() )
 
130
                                return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file);
 
131
 
 
132
                        // Select the first frame to handle animated images properly
 
133
                        if ( is_callable( array( $this->image, 'setIteratorIndex' ) ) )
 
134
                                $this->image->setIteratorIndex(0);
 
135
 
 
136
                        $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() );
 
137
                }
 
138
                catch ( Exception $e ) {
 
139
                        return new WP_Error( 'invalid_image', $e->getMessage(), $this->file );
 
140
                }
 
141
 
 
142
                $updated_size = $this->update_size();
 
143
                if ( is_wp_error( $updated_size ) )
 
144
                                return $updated_size;
 
145
 
 
146
                return true;
 
147
        }
 
148
 
 
149
        /**
 
150
         * Sets Image Compression quality on a 1-100% scale.
 
151
         *
 
152
         * @since 3.5.0
 
153
         * @access public
 
154
         *
 
155
         * @param int $quality Compression Quality. Range: [1,100]
 
156
         * @return boolean|WP_Error True if set successfully; WP_Error on failure.
 
157
         */
 
158
        public function set_quality( $quality = null ) {
 
159
                $quality_result = parent::set_quality( $quality );
 
160
                if ( is_wp_error( $quality_result ) ) {
 
161
                        return $quality_result;
 
162
                } else {
 
163
                        $quality = $this->get_quality();
 
164
                }
 
165
 
 
166
                try {
 
167
                        if ( 'image/jpeg' == $this->mime_type ) {
 
168
                                $this->image->setImageCompressionQuality( $quality );
 
169
                                $this->image->setImageCompression( imagick::COMPRESSION_JPEG );
 
170
                        }
 
171
                        else {
 
172
                                $this->image->setImageCompressionQuality( $quality );
 
173
                        }
 
174
                }
 
175
                catch ( Exception $e ) {
 
176
                        return new WP_Error( 'image_quality_error', $e->getMessage() );
 
177
                }
 
178
 
 
179
                return true;
 
180
        }
 
181
 
 
182
        /**
 
183
         * Sets or updates current image size.
 
184
         *
 
185
         * @since 3.5.0
 
186
         * @access protected
 
187
         *
 
188
         * @param int $width
 
189
         * @param int $height
 
190
         */
 
191
        protected function update_size( $width = null, $height = null ) {
 
192
                $size = null;
 
193
                if ( !$width || !$height ) {
 
194
                        try {
 
195
                                $size = $this->image->getImageGeometry();
 
196
                        }
 
197
                        catch ( Exception $e ) {
 
198
                                return new WP_Error( 'invalid_image', __('Could not read image size'), $this->file );
 
199
                        }
 
200
                }
 
201
 
 
202
                if ( ! $width )
 
203
                        $width = $size['width'];
 
204
 
 
205
                if ( ! $height )
 
206
                        $height = $size['height'];
 
207
 
 
208
                return parent::update_size( $width, $height );
 
209
        }
 
210
 
 
211
        /**
 
212
         * Resizes current image.
 
213
         *
 
214
         * At minimum, either a height or width must be provided.
 
215
         * If one of the two is set to null, the resize will
 
216
         * maintain aspect ratio according to the provided dimension.
 
217
         *
 
218
         * @since 3.5.0
 
219
         * @access public
 
220
         *
 
221
         * @param  int|null $max_w Image width.
 
222
         * @param  int|null $max_h Image height.
 
223
         * @param  boolean  $crop
 
224
         * @return boolean|WP_Error
 
225
         */
 
226
        public function resize( $max_w, $max_h, $crop = false ) {
 
227
                if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) )
 
228
                        return true;
 
229
 
 
230
                $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop );
 
231
                if ( ! $dims )
 
232
                        return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') );
 
233
                list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
 
234
 
 
235
                if ( $crop ) {
 
236
                        return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
 
237
                }
 
238
 
 
239
                try {
 
240
                        /**
 
241
                         * @TODO: Thumbnail is more efficient, given a newer version of Imagemagick.
 
242
                         * $this->image->thumbnailImage( $dst_w, $dst_h );
 
243
                         */
 
244
                        $this->image->scaleImage( $dst_w, $dst_h );
 
245
                }
 
246
                catch ( Exception $e ) {
 
247
                        return new WP_Error( 'image_resize_error', $e->getMessage() );
 
248
                }
 
249
 
 
250
                return $this->update_size( $dst_w, $dst_h );
 
251
        }
 
252
 
 
253
        /**
 
254
         * Resize multiple images from a single source.
 
255
         *
 
256
         * @since 3.5.0
 
257
         * @access public
 
258
         *
 
259
         * @param array $sizes {
 
260
         *     An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
 
261
         *
 
262
         *     Either a height or width must be provided.
 
263
         *     If one of the two is set to null, the resize will
 
264
         *     maintain aspect ratio according to the provided dimension.
 
265
         *
 
266
         *     @type array $size {
 
267
         *         @type int  ['width']  Optional. Image width.
 
268
         *         @type int  ['height'] Optional. Image height.
 
269
         *         @type bool $crop   Optional. Whether to crop the image. Default false.
 
270
         *     }
 
271
         * }
 
272
         * @return array An array of resized images' metadata by size.
 
273
         */
 
274
        public function multi_resize( $sizes ) {
 
275
                $metadata = array();
 
276
                $orig_size = $this->size;
 
277
                $orig_image = $this->image->getImage();
 
278
 
 
279
                foreach ( $sizes as $size => $size_data ) {
 
280
                        if ( ! $this->image )
 
281
                                $this->image = $orig_image->getImage();
 
282
 
 
283
                        if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) {
 
284
                                continue;
 
285
                        }
 
286
 
 
287
                        if ( ! isset( $size_data['width'] ) ) {
 
288
                                $size_data['width'] = null;
 
289
                        }
 
290
                        if ( ! isset( $size_data['height'] ) ) {
 
291
                                $size_data['height'] = null;
 
292
                        }
 
293
 
 
294
                        if ( ! isset( $size_data['crop'] ) ) {
 
295
                                $size_data['crop'] = false;
 
296
                        }
 
297
 
 
298
                        $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] );
 
299
 
 
300
                        if( ! is_wp_error( $resize_result ) ) {
 
301
                                $resized = $this->_save( $this->image );
 
302
 
 
303
                                $this->image->clear();
 
304
                                $this->image->destroy();
 
305
                                $this->image = null;
 
306
 
 
307
                                if ( ! is_wp_error( $resized ) && $resized ) {
 
308
                                        unset( $resized['path'] );
 
309
                                        $metadata[$size] = $resized;
 
310
                                }
 
311
                        }
 
312
 
 
313
                        $this->size = $orig_size;
 
314
                }
 
315
 
 
316
                $this->image = $orig_image;
 
317
 
 
318
                return $metadata;
 
319
        }
 
320
 
 
321
        /**
 
322
         * Crops Image.
 
323
         *
 
324
         * @since 3.5.0
 
325
         * @access public
 
326
         *
 
327
         * @param string|int $src The source file or Attachment ID.
 
328
         * @param int $src_x The start x position to crop from.
 
329
         * @param int $src_y The start y position to crop from.
 
330
         * @param int $src_w The width to crop.
 
331
         * @param int $src_h The height to crop.
 
332
         * @param int $dst_w Optional. The destination width.
 
333
         * @param int $dst_h Optional. The destination height.
 
334
         * @param boolean $src_abs Optional. If the source crop points are absolute.
 
335
         * @return boolean|WP_Error
 
336
         */
 
337
        public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) {
 
338
                if ( $src_abs ) {
 
339
                        $src_w -= $src_x;
 
340
                        $src_h -= $src_y;
 
341
                }
 
342
 
 
343
                try {
 
344
                        $this->image->cropImage( $src_w, $src_h, $src_x, $src_y );
 
345
                        $this->image->setImagePage( $src_w, $src_h, 0, 0);
 
346
 
 
347
                        if ( $dst_w || $dst_h ) {
 
348
                                // If destination width/height isn't specified, use same as
 
349
                                // width/height from source.
 
350
                                if ( ! $dst_w )
 
351
                                        $dst_w = $src_w;
 
352
                                if ( ! $dst_h )
 
353
                                        $dst_h = $src_h;
 
354
 
 
355
                                $this->image->scaleImage( $dst_w, $dst_h );
 
356
                                return $this->update_size();
 
357
                        }
 
358
                }
 
359
                catch ( Exception $e ) {
 
360
                        return new WP_Error( 'image_crop_error', $e->getMessage() );
 
361
                }
 
362
                return $this->update_size();
 
363
        }
 
364
 
 
365
        /**
 
366
         * Rotates current image counter-clockwise by $angle.
 
367
         *
 
368
         * @since 3.5.0
 
369
         * @access public
 
370
         *
 
371
         * @param float $angle
 
372
         * @return boolean|WP_Error
 
373
         */
 
374
        public function rotate( $angle ) {
 
375
                /**
 
376
                 * $angle is 360-$angle because Imagick rotates clockwise
 
377
                 * (GD rotates counter-clockwise)
 
378
                 */
 
379
                try {
 
380
                        $this->image->rotateImage( new ImagickPixel('none'), 360-$angle );
 
381
 
 
382
                        // Since this changes the dimensions of the image, update the size.
 
383
                        $result = $this->update_size();
 
384
                        if ( is_wp_error( $result ) )
 
385
                                return $result;
 
386
 
 
387
                        $this->image->setImagePage( $this->size['width'], $this->size['height'], 0, 0 );
 
388
                }
 
389
                catch ( Exception $e ) {
 
390
                        return new WP_Error( 'image_rotate_error', $e->getMessage() );
 
391
                }
 
392
                return true;
 
393
        }
 
394
 
 
395
        /**
 
396
         * Flips current image.
 
397
         *
 
398
         * @since 3.5.0
 
399
         * @access public
 
400
         *
 
401
         * @param boolean $horz Flip along Horizontal Axis
 
402
         * @param boolean $vert Flip along Vertical Axis
 
403
         * @returns boolean|WP_Error
 
404
         */
 
405
        public function flip( $horz, $vert ) {
 
406
                try {
 
407
                        if ( $horz )
 
408
                                $this->image->flipImage();
 
409
 
 
410
                        if ( $vert )
 
411
                                $this->image->flopImage();
 
412
                }
 
413
                catch ( Exception $e ) {
 
414
                        return new WP_Error( 'image_flip_error', $e->getMessage() );
 
415
                }
 
416
                return true;
 
417
        }
 
418
 
 
419
        /**
 
420
         * Saves current image to file.
 
421
         *
 
422
         * @since 3.5.0
 
423
         * @access public
 
424
         *
 
425
         * @param string $destfilename
 
426
         * @param string $mime_type
 
427
         * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
 
428
         */
 
429
        public function save( $destfilename = null, $mime_type = null ) {
 
430
                $saved = $this->_save( $this->image, $destfilename, $mime_type );
 
431
 
 
432
                if ( ! is_wp_error( $saved ) ) {
 
433
                        $this->file = $saved['path'];
 
434
                        $this->mime_type = $saved['mime-type'];
 
435
 
 
436
                        try {
 
437
                                $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) );
 
438
                        }
 
439
                        catch ( Exception $e ) {
 
440
                                return new WP_Error( 'image_save_error', $e->getMessage(), $this->file );
 
441
                        }
 
442
                }
 
443
 
 
444
                return $saved;
 
445
        }
 
446
 
 
447
        protected function _save( $image, $filename = null, $mime_type = null ) {
 
448
                list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type );
 
449
 
 
450
                if ( ! $filename )
 
451
                        $filename = $this->generate_filename( null, null, $extension );
 
452
 
 
453
                try {
 
454
                        // Store initial Format
 
455
                        $orig_format = $this->image->getImageFormat();
 
456
 
 
457
                        $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) );
 
458
                        $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) );
 
459
 
 
460
                        // Reset original Format
 
461
                        $this->image->setImageFormat( $orig_format );
 
462
                }
 
463
                catch ( Exception $e ) {
 
464
                        return new WP_Error( 'image_save_error', $e->getMessage(), $filename );
 
465
                }
 
466
 
 
467
                // Set correct file permissions
 
468
                $stat = stat( dirname( $filename ) );
 
469
                $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits
 
470
                @ chmod( $filename, $perms );
 
471
 
 
472
                /** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
 
473
                return array(
 
474
                        'path'      => $filename,
 
475
                        'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
 
476
                        'width'     => $this->size['width'],
 
477
                        'height'    => $this->size['height'],
 
478
                        'mime-type' => $mime_type,
 
479
                );
 
480
        }
 
481
 
 
482
        /**
 
483
         * Streams current image to browser.
 
484
         *
 
485
         * @since 3.5.0
 
486
         * @access public
 
487
         *
 
488
         * @param string $mime_type
 
489
         * @return boolean|WP_Error
 
490
         */
 
491
        public function stream( $mime_type = null ) {
 
492
                list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type );
 
493
 
 
494
                try {
 
495
                        // Temporarily change format for stream
 
496
                        $this->image->setImageFormat( strtoupper( $extension ) );
 
497
 
 
498
                        // Output stream of image content
 
499
                        header( "Content-Type: $mime_type" );
 
500
                        print $this->image->getImageBlob();
 
501
 
 
502
                        // Reset Image to original Format
 
503
                        $this->image->setImageFormat( $this->get_extension( $this->mime_type ) );
 
504
                }
 
505
                catch ( Exception $e ) {
 
506
                        return new WP_Error( 'image_stream_error', $e->getMessage() );
 
507
                }
 
508
 
 
509
                return true;
 
510
        }
 
511
}