[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> class-wp-image-editor.php (source)

   1  <?php
   2  /**
   3   * Base WordPress Image Editor
   4   *
   5   * @package WordPress
   6   * @subpackage Image_Editor
   7   */
   8  
   9  /**
  10   * Base image editor class from which implementations extend
  11   *
  12   * @since 3.5.0
  13   */
  14  abstract class WP_Image_Editor {
  15      protected $file              = null;
  16      protected $size              = null;
  17      protected $mime_type         = null;
  18      protected $output_mime_type  = null;
  19      protected $default_mime_type = 'image/jpeg';
  20      protected $quality           = false;
  21  
  22      // Deprecated since 5.8.1. See get_default_quality() below.
  23      protected $default_quality = 82;
  24  
  25      /**
  26       * Each instance handles a single file.
  27       *
  28       * @param string $file Path to the file to load.
  29       */
  30  	public function __construct( $file ) {
  31          $this->file = $file;
  32      }
  33  
  34      /**
  35       * Checks to see if current environment supports the editor chosen.
  36       * Must be overridden in a subclass.
  37       *
  38       * @since 3.5.0
  39       *
  40       * @abstract
  41       *
  42       * @param array $args
  43       * @return bool
  44       */
  45  	public static function test( $args = array() ) {
  46          return false;
  47      }
  48  
  49      /**
  50       * Checks to see if editor supports the mime-type specified.
  51       * Must be overridden in a subclass.
  52       *
  53       * @since 3.5.0
  54       *
  55       * @abstract
  56       *
  57       * @param string $mime_type
  58       * @return bool
  59       */
  60  	public static function supports_mime_type( $mime_type ) {
  61          return false;
  62      }
  63  
  64      /**
  65       * Loads image from $this->file into editor.
  66       *
  67       * @since 3.5.0
  68       * @abstract
  69       *
  70       * @return true|WP_Error True if loaded; WP_Error on failure.
  71       */
  72      abstract public function load();
  73  
  74      /**
  75       * Saves current image to file.
  76       *
  77       * @since 3.5.0
  78       * @abstract
  79       *
  80       * @param string $destfilename Optional. Destination filename. Default null.
  81       * @param string $mime_type    Optional. The mime-type. Default null.
  82       * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string}
  83       */
  84      abstract public function save( $destfilename = null, $mime_type = null );
  85  
  86      /**
  87       * Resizes current image.
  88       *
  89       * At minimum, either a height or width must be provided.
  90       * If one of the two is set to null, the resize will
  91       * maintain aspect ratio according to the provided dimension.
  92       *
  93       * @since 3.5.0
  94       * @abstract
  95       *
  96       * @param int|null $max_w Image width.
  97       * @param int|null $max_h Image height.
  98       * @param bool     $crop
  99       * @return true|WP_Error
 100       */
 101      abstract public function resize( $max_w, $max_h, $crop = false );
 102  
 103      /**
 104       * Resize multiple images from a single source.
 105       *
 106       * @since 3.5.0
 107       * @abstract
 108       *
 109       * @param array $sizes {
 110       *     An array of image size arrays. Default sizes are 'small', 'medium', 'large'.
 111       *
 112       *     @type array ...$0 {
 113       *         @type int  $width  Image width.
 114       *         @type int  $height Image height.
 115       *         @type bool $crop   Optional. Whether to crop the image. Default false.
 116       *     }
 117       * }
 118       * @return array An array of resized images metadata by size.
 119       */
 120      abstract public function multi_resize( $sizes );
 121  
 122      /**
 123       * Crops Image.
 124       *
 125       * @since 3.5.0
 126       * @abstract
 127       *
 128       * @param int  $src_x   The start x position to crop from.
 129       * @param int  $src_y   The start y position to crop from.
 130       * @param int  $src_w   The width to crop.
 131       * @param int  $src_h   The height to crop.
 132       * @param int  $dst_w   Optional. The destination width.
 133       * @param int  $dst_h   Optional. The destination height.
 134       * @param bool $src_abs Optional. If the source crop points are absolute.
 135       * @return true|WP_Error
 136       */
 137      abstract public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false );
 138  
 139      /**
 140       * Rotates current image counter-clockwise by $angle.
 141       *
 142       * @since 3.5.0
 143       * @abstract
 144       *
 145       * @param float $angle
 146       * @return true|WP_Error
 147       */
 148      abstract public function rotate( $angle );
 149  
 150      /**
 151       * Flips current image.
 152       *
 153       * @since 3.5.0
 154       * @abstract
 155       *
 156       * @param bool $horz Flip along Horizontal Axis
 157       * @param bool $vert Flip along Vertical Axis
 158       * @return true|WP_Error
 159       */
 160      abstract public function flip( $horz, $vert );
 161  
 162      /**
 163       * Streams current image to browser.
 164       *
 165       * @since 3.5.0
 166       * @abstract
 167       *
 168       * @param string $mime_type The mime type of the image.
 169       * @return true|WP_Error True on success, WP_Error object on failure.
 170       */
 171      abstract public function stream( $mime_type = null );
 172  
 173      /**
 174       * Gets dimensions of image.
 175       *
 176       * @since 3.5.0
 177       *
 178       * @return int[] {
 179       *     Dimensions of the image.
 180       *
 181       *     @type int $width  The image width.
 182       *     @type int $height The image height.
 183       * }
 184       */
 185  	public function get_size() {
 186          return $this->size;
 187      }
 188  
 189      /**
 190       * Sets current image size.
 191       *
 192       * @since 3.5.0
 193       *
 194       * @param int $width
 195       * @param int $height
 196       * @return true
 197       */
 198  	protected function update_size( $width = null, $height = null ) {
 199          $this->size = array(
 200              'width'  => (int) $width,
 201              'height' => (int) $height,
 202          );
 203          return true;
 204      }
 205  
 206      /**
 207       * Gets the Image Compression quality on a 1-100% scale.
 208       *
 209       * @since 4.0.0
 210       *
 211       * @return int Compression Quality. Range: [1,100]
 212       */
 213  	public function get_quality() {
 214          if ( ! $this->quality ) {
 215              $this->set_quality();
 216          }
 217  
 218          return $this->quality;
 219      }
 220  
 221      /**
 222       * Sets Image Compression quality on a 1-100% scale.
 223       *
 224       * @since 3.5.0
 225       *
 226       * @param int $quality Compression Quality. Range: [1,100]
 227       * @return true|WP_Error True if set successfully; WP_Error on failure.
 228       */
 229  	public function set_quality( $quality = null ) {
 230          // Use the output mime type if present. If not, fall back to the input/initial mime type.
 231          $mime_type = ! empty( $this->output_mime_type ) ? $this->output_mime_type : $this->mime_type;
 232          // Get the default quality setting for the mime type.
 233          $default_quality = $this->get_default_quality( $mime_type );
 234  
 235          if ( null === $quality ) {
 236              /**
 237               * Filters the default image compression quality setting.
 238               *
 239               * Applies only during initial editor instantiation, or when set_quality() is run
 240               * manually without the `$quality` argument.
 241               *
 242               * The WP_Image_Editor::set_quality() method has priority over the filter.
 243               *
 244               * @since 3.5.0
 245               *
 246               * @param int    $quality   Quality level between 1 (low) and 100 (high).
 247               * @param string $mime_type Image mime type.
 248               */
 249              $quality = apply_filters( 'wp_editor_set_quality', $default_quality, $mime_type );
 250  
 251              if ( 'image/jpeg' === $mime_type ) {
 252                  /**
 253                   * Filters the JPEG compression quality for backward-compatibility.
 254                   *
 255                   * Applies only during initial editor instantiation, or when set_quality() is run
 256                   * manually without the `$quality` argument.
 257                   *
 258                   * The WP_Image_Editor::set_quality() method has priority over the filter.
 259                   *
 260                   * The filter is evaluated under two contexts: 'image_resize', and 'edit_image',
 261                   * (when a JPEG image is saved to file).
 262                   *
 263                   * @since 2.5.0
 264                   *
 265                   * @param int    $quality Quality level between 0 (low) and 100 (high) of the JPEG.
 266                   * @param string $context Context of the filter.
 267                   */
 268                  $quality = apply_filters( 'jpeg_quality', $quality, 'image_resize' );
 269              }
 270  
 271              if ( $quality < 0 || $quality > 100 ) {
 272                  $quality = $default_quality;
 273              }
 274          }
 275  
 276          // Allow 0, but squash to 1 due to identical images in GD, and for backward compatibility.
 277          if ( 0 === $quality ) {
 278              $quality = 1;
 279          }
 280  
 281          if ( ( $quality >= 1 ) && ( $quality <= 100 ) ) {
 282              $this->quality = $quality;
 283              return true;
 284          } else {
 285              return new WP_Error( 'invalid_image_quality', __( 'Attempted to set image quality outside of the range [1,100].' ) );
 286          }
 287      }
 288  
 289      /**
 290       * Returns the default compression quality setting for the mime type.
 291       *
 292       * @since 5.8.1
 293       *
 294       * @param string $mime_type
 295       * @return int The default quality setting for the mime type.
 296       */
 297  	protected function get_default_quality( $mime_type ) {
 298          switch ( $mime_type ) {
 299              case 'image/webp':
 300                  $quality = 86;
 301                  break;
 302              case 'image/jpeg':
 303              default:
 304                  $quality = $this->default_quality;
 305          }
 306  
 307          return $quality;
 308      }
 309  
 310      /**
 311       * Returns preferred mime-type and extension based on provided
 312       * file's extension and mime, or current file's extension and mime.
 313       *
 314       * Will default to $this->default_mime_type if requested is not supported.
 315       *
 316       * Provides corrected filename only if filename is provided.
 317       *
 318       * @since 3.5.0
 319       *
 320       * @param string $filename
 321       * @param string $mime_type
 322       * @return array { filename|null, extension, mime-type }
 323       */
 324  	protected function get_output_format( $filename = null, $mime_type = null ) {
 325          $new_ext = null;
 326  
 327          // By default, assume specified type takes priority.
 328          if ( $mime_type ) {
 329              $new_ext = $this->get_extension( $mime_type );
 330          }
 331  
 332          if ( $filename ) {
 333              $file_ext  = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
 334              $file_mime = $this->get_mime_type( $file_ext );
 335          } else {
 336              // If no file specified, grab editor's current extension and mime-type.
 337              $file_ext  = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) );
 338              $file_mime = $this->mime_type;
 339          }
 340  
 341          // Check to see if specified mime-type is the same as type implied by
 342          // file extension. If so, prefer extension from file.
 343          if ( ! $mime_type || ( $file_mime == $mime_type ) ) {
 344              $mime_type = $file_mime;
 345              $new_ext   = $file_ext;
 346          }
 347  
 348          /**
 349           * Filters the image editor output format mapping.
 350           *
 351           * Enables filtering the mime type used to save images. By default,
 352           * the mapping array is empty, so the mime type matches the source image.
 353           *
 354           * @see WP_Image_Editor::get_output_format()
 355           *
 356           * @since 5.8.0
 357           *
 358           * @param string[] $output_format {
 359           *     An array of mime type mappings. Maps a source mime type to a new
 360           *     destination mime type. Default empty array.
 361           *
 362           *     @type string ...$0 The new mime type.
 363           * }
 364           * @param string $filename  Path to the image.
 365           * @param string $mime_type The source image mime type.
 366           */
 367          $output_format = apply_filters( 'image_editor_output_format', array(), $filename, $mime_type );
 368  
 369          if ( isset( $output_format[ $mime_type ] )
 370              && $this->supports_mime_type( $output_format[ $mime_type ] )
 371          ) {
 372              $mime_type = $output_format[ $mime_type ];
 373              $new_ext   = $this->get_extension( $mime_type );
 374          }
 375  
 376          // Double-check that the mime-type selected is supported by the editor.
 377          // If not, choose a default instead.
 378          if ( ! $this->supports_mime_type( $mime_type ) ) {
 379              /**
 380               * Filters default mime type prior to getting the file extension.
 381               *
 382               * @see wp_get_mime_types()
 383               *
 384               * @since 3.5.0
 385               *
 386               * @param string $mime_type Mime type string.
 387               */
 388              $mime_type = apply_filters( 'image_editor_default_mime_type', $this->default_mime_type );
 389              $new_ext   = $this->get_extension( $mime_type );
 390          }
 391  
 392          // Ensure both $filename and $new_ext are not empty.
 393          // $this->get_extension() returns false on error which would effectively remove the extension
 394          // from $filename. That shouldn't happen, files without extensions are not supported.
 395          if ( $filename && $new_ext ) {
 396              $dir = pathinfo( $filename, PATHINFO_DIRNAME );
 397              $ext = pathinfo( $filename, PATHINFO_EXTENSION );
 398  
 399              $filename = trailingslashit( $dir ) . wp_basename( $filename, ".$ext" ) . ".{$new_ext}";
 400          }
 401  
 402          if ( $mime_type && ( $mime_type !== $this->mime_type ) ) {
 403              // The image will be converted when saving. Set the quality for the new mime-type if not already set.
 404              if ( $mime_type !== $this->output_mime_type ) {
 405                  $this->output_mime_type = $mime_type;
 406                  $this->set_quality();
 407              }
 408          } elseif ( ! empty( $this->output_mime_type ) ) {
 409              // Reset output_mime_type and quality.
 410              $this->output_mime_type = null;
 411              $this->set_quality();
 412          }
 413  
 414          return array( $filename, $new_ext, $mime_type );
 415      }
 416  
 417      /**
 418       * Builds an output filename based on current file, and adding proper suffix
 419       *
 420       * @since 3.5.0
 421       *
 422       * @param string $suffix
 423       * @param string $dest_path
 424       * @param string $extension
 425       * @return string filename
 426       */
 427  	public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) {
 428          // $suffix will be appended to the destination filename, just before the extension.
 429          if ( ! $suffix ) {
 430              $suffix = $this->get_suffix();
 431          }
 432  
 433          $dir = pathinfo( $this->file, PATHINFO_DIRNAME );
 434          $ext = pathinfo( $this->file, PATHINFO_EXTENSION );
 435  
 436          $name    = wp_basename( $this->file, ".$ext" );
 437          $new_ext = strtolower( $extension ? $extension : $ext );
 438  
 439          if ( ! is_null( $dest_path ) ) {
 440              if ( ! wp_is_stream( $dest_path ) ) {
 441                  $_dest_path = realpath( $dest_path );
 442                  if ( $_dest_path ) {
 443                      $dir = $_dest_path;
 444                  }
 445              } else {
 446                  $dir = $dest_path;
 447              }
 448          }
 449  
 450          return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}";
 451      }
 452  
 453      /**
 454       * Builds and returns proper suffix for file based on height and width.
 455       *
 456       * @since 3.5.0
 457       *
 458       * @return string|false suffix
 459       */
 460  	public function get_suffix() {
 461          if ( ! $this->get_size() ) {
 462              return false;
 463          }
 464  
 465          return "{$this->size['width']}x{$this->size['height']}";
 466      }
 467  
 468      /**
 469       * Check if a JPEG image has EXIF Orientation tag and rotate it if needed.
 470       *
 471       * @since 5.3.0
 472       *
 473       * @return bool|WP_Error True if the image was rotated. False if not rotated (no EXIF data or the image doesn't need to be rotated).
 474       *                       WP_Error if error while rotating.
 475       */
 476  	public function maybe_exif_rotate() {
 477          $orientation = null;
 478  
 479          if ( is_callable( 'exif_read_data' ) && 'image/jpeg' === $this->mime_type ) {
 480              $exif_data = @exif_read_data( $this->file );
 481  
 482              if ( ! empty( $exif_data['Orientation'] ) ) {
 483                  $orientation = (int) $exif_data['Orientation'];
 484              }
 485          }
 486  
 487          /**
 488           * Filters the `$orientation` value to correct it before rotating or to prevent rotating the image.
 489           *
 490           * @since 5.3.0
 491           *
 492           * @param int    $orientation EXIF Orientation value as retrieved from the image file.
 493           * @param string $file        Path to the image file.
 494           */
 495          $orientation = apply_filters( 'wp_image_maybe_exif_rotate', $orientation, $this->file );
 496  
 497          if ( ! $orientation || 1 === $orientation ) {
 498              return false;
 499          }
 500  
 501          switch ( $orientation ) {
 502              case 2:
 503                  // Flip horizontally.
 504                  $result = $this->flip( true, false );
 505                  break;
 506              case 3:
 507                  // Rotate 180 degrees or flip horizontally and vertically.
 508                  // Flipping seems faster and uses less resources.
 509                  $result = $this->flip( true, true );
 510                  break;
 511              case 4:
 512                  // Flip vertically.
 513                  $result = $this->flip( false, true );
 514                  break;
 515              case 5:
 516                  // Rotate 90 degrees counter-clockwise and flip vertically.
 517                  $result = $this->rotate( 90 );
 518  
 519                  if ( ! is_wp_error( $result ) ) {
 520                      $result = $this->flip( false, true );
 521                  }
 522  
 523                  break;
 524              case 6:
 525                  // Rotate 90 degrees clockwise (270 counter-clockwise).
 526                  $result = $this->rotate( 270 );
 527                  break;
 528              case 7:
 529                  // Rotate 90 degrees counter-clockwise and flip horizontally.
 530                  $result = $this->rotate( 90 );
 531  
 532                  if ( ! is_wp_error( $result ) ) {
 533                      $result = $this->flip( true, false );
 534                  }
 535  
 536                  break;
 537              case 8:
 538                  // Rotate 90 degrees counter-clockwise.
 539                  $result = $this->rotate( 90 );
 540                  break;
 541          }
 542  
 543          return $result;
 544      }
 545  
 546      /**
 547       * Either calls editor's save function or handles file as a stream.
 548       *
 549       * @since 3.5.0
 550       *
 551       * @param string   $filename
 552       * @param callable $callback
 553       * @param array    $arguments
 554       * @return bool
 555       */
 556  	protected function make_image( $filename, $callback, $arguments ) {
 557          $stream = wp_is_stream( $filename );
 558          if ( $stream ) {
 559              ob_start();
 560          } else {
 561              // The directory containing the original file may no longer exist when using a replication plugin.
 562              wp_mkdir_p( dirname( $filename ) );
 563          }
 564  
 565          $result = call_user_func_array( $callback, $arguments );
 566  
 567          if ( $result && $stream ) {
 568              $contents = ob_get_contents();
 569  
 570              $fp = fopen( $filename, 'w' );
 571  
 572              if ( ! $fp ) {
 573                  ob_end_clean();
 574                  return false;
 575              }
 576  
 577              fwrite( $fp, $contents );
 578              fclose( $fp );
 579          }
 580  
 581          if ( $stream ) {
 582              ob_end_clean();
 583          }
 584  
 585          return $result;
 586      }
 587  
 588      /**
 589       * Returns first matched mime-type from extension,
 590       * as mapped from wp_get_mime_types()
 591       *
 592       * @since 3.5.0
 593       *
 594       * @param string $extension
 595       * @return string|false
 596       */
 597  	protected static function get_mime_type( $extension = null ) {
 598          if ( ! $extension ) {
 599              return false;
 600          }
 601  
 602          $mime_types = wp_get_mime_types();
 603          $extensions = array_keys( $mime_types );
 604  
 605          foreach ( $extensions as $_extension ) {
 606              if ( preg_match( "/{$extension}/i", $_extension ) ) {
 607                  return $mime_types[ $_extension ];
 608              }
 609          }
 610  
 611          return false;
 612      }
 613  
 614      /**
 615       * Returns first matched extension from Mime-type,
 616       * as mapped from wp_get_mime_types()
 617       *
 618       * @since 3.5.0
 619       *
 620       * @param string $mime_type
 621       * @return string|false
 622       */
 623  	protected static function get_extension( $mime_type = null ) {
 624          if ( empty( $mime_type ) ) {
 625              return false;
 626          }
 627  
 628          return wp_get_default_extension_for_mime_type( $mime_type );
 629      }
 630  }
 631  


Generated: Tue Mar 19 01:00:02 2024 Cross-referenced by PHPXref 0.7.1