Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like WP_Image_Editor_Imagick often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use WP_Image_Editor_Imagick, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class WP_Image_Editor_Imagick extends WP_Image_Editor { |
||
18 | /** |
||
19 | * Imagick object. |
||
20 | * |
||
21 | * @access protected |
||
22 | * @var Imagick |
||
23 | */ |
||
24 | protected $image; |
||
25 | |||
26 | public function __destruct() { |
||
33 | |||
34 | /** |
||
35 | * Checks to see if current environment supports Imagick. |
||
36 | * |
||
37 | * We require Imagick 2.2.0 or greater, based on whether the queryFormats() |
||
38 | * method can be called statically. |
||
39 | * |
||
40 | * @since 3.5.0 |
||
41 | * |
||
42 | * @static |
||
43 | * @access public |
||
44 | * |
||
45 | * @param array $args |
||
46 | * @return bool |
||
47 | */ |
||
48 | public static function test( $args = array() ) { |
||
94 | |||
95 | /** |
||
96 | * Checks to see if editor supports the mime-type specified. |
||
97 | * |
||
98 | * @since 3.5.0 |
||
99 | * |
||
100 | * @static |
||
101 | * @access public |
||
102 | * |
||
103 | * @param string $mime_type |
||
104 | * @return bool |
||
105 | */ |
||
106 | public static function supports_mime_type( $mime_type ) { |
||
124 | |||
125 | /** |
||
126 | * Loads image from $this->file into new Imagick Object. |
||
127 | * |
||
128 | * @since 3.5.0 |
||
129 | * @access protected |
||
130 | * |
||
131 | * @return true|WP_Error True if loaded; WP_Error on failure. |
||
132 | */ |
||
133 | public function load() { |
||
167 | |||
168 | /** |
||
169 | * Sets Image Compression quality on a 1-100% scale. |
||
170 | * |
||
171 | * @since 3.5.0 |
||
172 | * @access public |
||
173 | * |
||
174 | * @param int $quality Compression Quality. Range: [1,100] |
||
175 | * @return true|WP_Error True if set successfully; WP_Error on failure. |
||
176 | */ |
||
177 | public function set_quality( $quality = null ) { |
||
200 | |||
201 | /** |
||
202 | * Sets or updates current image size. |
||
203 | * |
||
204 | * @since 3.5.0 |
||
205 | * @access protected |
||
206 | * |
||
207 | * @param int $width |
||
208 | * @param int $height |
||
209 | * |
||
210 | * @return true|WP_Error |
||
211 | */ |
||
212 | protected function update_size( $width = null, $height = null ) { |
||
231 | |||
232 | /** |
||
233 | * Resizes current image. |
||
234 | * |
||
235 | * At minimum, either a height or width must be provided. |
||
236 | * If one of the two is set to null, the resize will |
||
237 | * maintain aspect ratio according to the provided dimension. |
||
238 | * |
||
239 | * @since 3.5.0 |
||
240 | * @access public |
||
241 | * |
||
242 | * @param int|null $max_w Image width. |
||
243 | * @param int|null $max_h Image height. |
||
244 | * @param bool $crop |
||
245 | * @return bool|WP_Error |
||
246 | */ |
||
247 | public function resize( $max_w, $max_h, $crop = false ) { |
||
268 | |||
269 | /** |
||
270 | * Efficiently resize the current image |
||
271 | * |
||
272 | * This is a WordPress specific implementation of Imagick::thumbnailImage(), |
||
273 | * which resizes an image to given dimensions and removes any associated profiles. |
||
274 | * |
||
275 | * @since 4.5.0 |
||
276 | * @access protected |
||
277 | * |
||
278 | * @param int $dst_w The destination width. |
||
279 | * @param int $dst_h The destination height. |
||
280 | * @param string $filter_name Optional. The Imagick filter to use when resizing. Default 'FILTER_TRIANGLE'. |
||
281 | * @param bool $strip_meta Optional. Strip all profiles, excluding color profiles, from the image. Default true. |
||
282 | * @return bool|WP_Error |
||
283 | */ |
||
284 | protected function thumbnail_image( $dst_w, $dst_h, $filter_name = 'FILTER_TRIANGLE', $strip_meta = true ) { |
||
285 | $allowed_filters = array( |
||
286 | 'FILTER_POINT', |
||
287 | 'FILTER_BOX', |
||
288 | 'FILTER_TRIANGLE', |
||
289 | 'FILTER_HERMITE', |
||
290 | 'FILTER_HANNING', |
||
291 | 'FILTER_HAMMING', |
||
292 | 'FILTER_BLACKMAN', |
||
293 | 'FILTER_GAUSSIAN', |
||
294 | 'FILTER_QUADRATIC', |
||
295 | 'FILTER_CUBIC', |
||
296 | 'FILTER_CATROM', |
||
297 | 'FILTER_MITCHELL', |
||
298 | 'FILTER_LANCZOS', |
||
299 | 'FILTER_BESSEL', |
||
300 | 'FILTER_SINC', |
||
301 | ); |
||
302 | |||
303 | /** |
||
304 | * Set the filter value if '$filter_name' name is in our whitelist and the related |
||
305 | * Imagick constant is defined or fall back to our default filter. |
||
306 | */ |
||
307 | if ( in_array( $filter_name, $allowed_filters ) && defined( 'Imagick::' . $filter_name ) ) { |
||
308 | $filter = constant( 'Imagick::' . $filter_name ); |
||
309 | } else { |
||
310 | $filter = defined( 'Imagick::FILTER_TRIANGLE' ) ? Imagick::FILTER_TRIANGLE : false; |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * Filter whether to strip metadata from images when they're resized. |
||
315 | * |
||
316 | * This filter only applies when resizing using the Imagick editor since GD |
||
317 | * always strips profiles by default. |
||
318 | * |
||
319 | * @since 4.5.0 |
||
320 | * |
||
321 | * @param bool $strip_meta Whether to strip image metadata during resizing. Default true. |
||
322 | */ |
||
323 | if ( apply_filters( 'image_strip_meta', $strip_meta ) ) { |
||
324 | $this->strip_meta(); // Fail silently if not supported. |
||
325 | } |
||
326 | |||
327 | try { |
||
328 | /* |
||
329 | * To be more efficient, resample large images to 5x the destination size before resizing |
||
330 | * whenever the output size is less that 1/3 of the original image size (1/3^2 ~= .111), |
||
331 | * unless we would be resampling to a scale smaller than 128x128. |
||
332 | */ |
||
333 | if ( is_callable( array( $this->image, 'sampleImage' ) ) ) { |
||
334 | $resize_ratio = ( $dst_w / $this->size['width'] ) * ( $dst_h / $this->size['height'] ); |
||
335 | $sample_factor = 5; |
||
336 | |||
337 | if ( $resize_ratio < .111 && ( $dst_w * $sample_factor > 128 && $dst_h * $sample_factor > 128 ) ) { |
||
338 | $this->image->sampleImage( $dst_w * $sample_factor, $dst_h * $sample_factor ); |
||
339 | } |
||
340 | } |
||
341 | |||
342 | /* |
||
343 | * Use resizeImage() when it's available and a valid filter value is set. |
||
344 | * Otherwise, fall back to the scaleImage() method for resizing, which |
||
345 | * results in better image quality over resizeImage() with default filter |
||
346 | * settings and retains backwards compatibility with pre 4.5 functionality. |
||
347 | */ |
||
348 | if ( is_callable( array( $this->image, 'resizeImage' ) ) && $filter ) { |
||
349 | $this->image->setOption( 'filter:support', '2.0' ); |
||
350 | $this->image->resizeImage( $dst_w, $dst_h, $filter, 1 ); |
||
351 | } else { |
||
352 | $this->image->scaleImage( $dst_w, $dst_h ); |
||
353 | } |
||
354 | |||
355 | // Set appropriate quality settings after resizing. |
||
356 | if ( 'image/jpeg' == $this->mime_type ) { |
||
357 | if ( is_callable( array( $this->image, 'unsharpMaskImage' ) ) ) { |
||
358 | $this->image->unsharpMaskImage( 0.25, 0.25, 8, 0.065 ); |
||
359 | } |
||
360 | |||
361 | $this->image->setOption( 'jpeg:fancy-upsampling', 'off' ); |
||
362 | } |
||
363 | |||
364 | if ( 'image/png' === $this->mime_type ) { |
||
365 | $this->image->setOption( 'png:compression-filter', '5' ); |
||
366 | $this->image->setOption( 'png:compression-level', '9' ); |
||
367 | $this->image->setOption( 'png:compression-strategy', '1' ); |
||
368 | $this->image->setOption( 'png:exclude-chunk', 'all' ); |
||
369 | } |
||
370 | |||
371 | /* |
||
372 | * If alpha channel is not defined, set it opaque. |
||
373 | * |
||
374 | * Note that Imagick::getImageAlphaChannel() is only available if Imagick |
||
375 | * has been compiled against ImageMagick version 6.4.0 or newer. |
||
376 | */ |
||
377 | if ( is_callable( array( $this->image, 'getImageAlphaChannel' ) ) |
||
378 | && is_callable( array( $this->image, 'setImageAlphaChannel' ) ) |
||
379 | && defined( Imagick::ALPHACHANNEL_UNDEFINED ) |
||
380 | && defined( Imagick::ALPHACHANNEL_OPAQUE ) |
||
381 | ) { |
||
382 | if ( $this->image->getImageAlphaChannel() === Imagick::ALPHACHANNEL_UNDEFINED ) { |
||
383 | $this->image->setImageAlphaChannel( Imagick::ALPHACHANNEL_OPAQUE ); |
||
384 | } |
||
385 | } |
||
386 | |||
387 | // Limit the bit depth of resized images to 8 bits per channel. |
||
388 | if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) { |
||
389 | if ( 8 < $this->image->getImageDepth() ) { |
||
390 | $this->image->setImageDepth( 8 ); |
||
391 | } |
||
392 | } |
||
393 | |||
394 | if ( is_callable( array( $this->image, 'setInterlaceScheme' ) ) && defined( 'Imagick::INTERLACE_NO' ) ) { |
||
395 | $this->image->setInterlaceScheme( Imagick::INTERLACE_NO ); |
||
396 | } |
||
397 | |||
398 | } |
||
399 | catch ( Exception $e ) { |
||
400 | return new WP_Error( 'image_resize_error', $e->getMessage() ); |
||
401 | } |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * Resize multiple images from a single source. |
||
406 | * |
||
407 | * @since 3.5.0 |
||
408 | * @access public |
||
409 | * |
||
410 | * @param array $sizes { |
||
411 | * An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'. |
||
412 | * |
||
413 | * Either a height or width must be provided. |
||
414 | * If one of the two is set to null, the resize will |
||
415 | * maintain aspect ratio according to the provided dimension. |
||
416 | * |
||
417 | * @type array $size { |
||
418 | * Array of height, width values, and whether to crop. |
||
419 | * |
||
420 | * @type int $width Image width. Optional if `$height` is specified. |
||
421 | * @type int $height Image height. Optional if `$width` is specified. |
||
422 | * @type bool $crop Optional. Whether to crop the image. Default false. |
||
423 | * } |
||
424 | * } |
||
425 | * @return array An array of resized images' metadata by size. |
||
426 | */ |
||
427 | public function multi_resize( $sizes ) { |
||
474 | |||
475 | /** |
||
476 | * Crops Image. |
||
477 | * |
||
478 | * @since 3.5.0 |
||
479 | * @access public |
||
480 | * |
||
481 | * @param int $src_x The start x position to crop from. |
||
482 | * @param int $src_y The start y position to crop from. |
||
483 | * @param int $src_w The width to crop. |
||
484 | * @param int $src_h The height to crop. |
||
485 | * @param int $dst_w Optional. The destination width. |
||
486 | * @param int $dst_h Optional. The destination height. |
||
487 | * @param bool $src_abs Optional. If the source crop points are absolute. |
||
488 | * @return bool|WP_Error |
||
489 | */ |
||
490 | public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { |
||
521 | |||
522 | /** |
||
523 | * Rotates current image counter-clockwise by $angle. |
||
524 | * |
||
525 | * @since 3.5.0 |
||
526 | * @access public |
||
527 | * |
||
528 | * @param float $angle |
||
529 | * @return true|WP_Error |
||
530 | */ |
||
531 | public function rotate( $angle ) { |
||
551 | |||
552 | /** |
||
553 | * Flips current image. |
||
554 | * |
||
555 | * @since 3.5.0 |
||
556 | * @access public |
||
557 | * |
||
558 | * @param bool $horz Flip along Horizontal Axis |
||
559 | * @param bool $vert Flip along Vertical Axis |
||
560 | * @return true|WP_Error |
||
561 | */ |
||
562 | public function flip( $horz, $vert ) { |
||
575 | |||
576 | /** |
||
577 | * Saves current image to file. |
||
578 | * |
||
579 | * @since 3.5.0 |
||
580 | * @access public |
||
581 | * |
||
582 | * @param string $destfilename |
||
583 | * @param string $mime_type |
||
584 | * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string} |
||
585 | */ |
||
586 | public function save( $destfilename = null, $mime_type = null ) { |
||
603 | |||
604 | /** |
||
605 | * |
||
606 | * @param Imagick $image |
||
607 | * @param string $filename |
||
608 | * @param string $mime_type |
||
609 | * @return array|WP_Error |
||
610 | */ |
||
611 | protected function _save( $image, $filename = null, $mime_type = null ) { |
||
645 | |||
646 | /** |
||
647 | * Streams current image to browser. |
||
648 | * |
||
649 | * @since 3.5.0 |
||
650 | * @access public |
||
651 | * |
||
652 | * @param string $mime_type |
||
653 | * @return true|WP_Error |
||
654 | */ |
||
655 | public function stream( $mime_type = null ) { |
||
675 | |||
676 | /** |
||
677 | * Strips all image meta except color profiles from an image. |
||
678 | * |
||
679 | * @since 4.5.0 |
||
680 | * @access protected |
||
681 | * |
||
682 | * @return true|WP_Error True if stripping metadata was successful. WP_Error object on error. |
||
683 | */ |
||
684 | protected function strip_meta() { |
||
685 | |||
686 | View Code Duplication | if ( ! is_callable( array( $this->image, 'getImageProfiles' ) ) ) { |
|
687 | /* translators: %s: ImageMagick method name */ |
||
688 | return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::getImageProfiles()</code>' ) ); |
||
689 | } |
||
690 | |||
691 | View Code Duplication | if ( ! is_callable( array( $this->image, 'removeImageProfile' ) ) ) { |
|
692 | /* translators: %s: ImageMagick method name */ |
||
693 | return new WP_Error( 'image_strip_meta_error', sprintf( __( '%s is required to strip image meta.' ), '<code>Imagick::removeImageProfile()</code>' ) ); |
||
694 | } |
||
695 | |||
696 | /* |
||
697 | * Protect a few profiles from being stripped for the following reasons: |
||
698 | * |
||
699 | * - icc: Color profile information |
||
700 | * - icm: Color profile information |
||
701 | * - iptc: Copyright data |
||
702 | * - exif: Orientation data |
||
703 | * - xmp: Rights usage data |
||
704 | */ |
||
705 | $protected_profiles = array( |
||
706 | 'icc', |
||
707 | 'icm', |
||
708 | 'iptc', |
||
709 | 'exif', |
||
710 | 'xmp', |
||
711 | ); |
||
712 | |||
713 | try { |
||
714 | // Strip profiles. |
||
715 | foreach ( $this->image->getImageProfiles( '*', true ) as $key => $value ) { |
||
716 | if ( ! in_array( $key, $protected_profiles ) ) { |
||
717 | $this->image->removeImageProfile( $key ); |
||
718 | } |
||
719 | } |
||
720 | |||
721 | } catch ( Exception $e ) { |
||
722 | return new WP_Error( 'image_strip_meta_error', $e->getMessage() ); |
||
723 | } |
||
724 | |||
725 | return true; |
||
726 | } |
||
727 | |||
728 | } |
||
729 |
If you suppress an error, we recommend checking for the error condition explicitly: