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: