[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WordPress GD Image Editor 4 * 5 * @package WordPress 6 * @subpackage Image_Editor 7 */ 8 9 /** 10 * WordPress Image Editor Class for Image Manipulation through GD 11 * 12 * @since 3.5.0 13 * 14 * @see WP_Image_Editor 15 */ 16 class WP_Image_Editor_GD extends WP_Image_Editor { 17 /** 18 * GD Resource. 19 * 20 * @var resource|GdImage 21 */ 22 protected $image; 23 24 public function __destruct() { 25 if ( $this->image ) { 26 // We don't need the original in memory anymore. 27 imagedestroy( $this->image ); 28 } 29 } 30 31 /** 32 * Checks to see if current environment supports GD. 33 * 34 * @since 3.5.0 35 * 36 * @param array $args 37 * @return bool 38 */ 39 public static function test( $args = array() ) { 40 if ( ! extension_loaded( 'gd' ) || ! function_exists( 'gd_info' ) ) { 41 return false; 42 } 43 44 // On some setups GD library does not provide imagerotate() - Ticket #11536. 45 if ( isset( $args['methods'] ) && 46 in_array( 'rotate', $args['methods'], true ) && 47 ! function_exists( 'imagerotate' ) ) { 48 49 return false; 50 } 51 52 return true; 53 } 54 55 /** 56 * Checks to see if editor supports the mime-type specified. 57 * 58 * @since 3.5.0 59 * 60 * @param string $mime_type 61 * @return bool 62 */ 63 public static function supports_mime_type( $mime_type ) { 64 $image_types = imagetypes(); 65 switch ( $mime_type ) { 66 case 'image/jpeg': 67 return ( $image_types & IMG_JPG ) !== 0; 68 case 'image/png': 69 return ( $image_types & IMG_PNG ) !== 0; 70 case 'image/gif': 71 return ( $image_types & IMG_GIF ) !== 0; 72 case 'image/webp': 73 return ( $image_types & IMG_WEBP ) !== 0; 74 case 'image/avif': 75 return ( $image_types & IMG_AVIF ) !== 0 && function_exists( 'imageavif' ); 76 } 77 78 return false; 79 } 80 81 /** 82 * Loads image from $this->file into new GD Resource. 83 * 84 * @since 3.5.0 85 * 86 * @return true|WP_Error True if loaded successfully; WP_Error on failure. 87 */ 88 public function load() { 89 if ( $this->image ) { 90 return true; 91 } 92 93 if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) { 94 return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file ); 95 } 96 97 // Set artificially high because GD uses uncompressed images in memory. 98 wp_raise_memory_limit( 'image' ); 99 100 $file_contents = @file_get_contents( $this->file ); 101 102 if ( ! $file_contents ) { 103 return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file ); 104 } 105 106 // Handle WebP and AVIF mime types explicitly, falling back to imagecreatefromstring. 107 if ( 108 function_exists( 'imagecreatefromwebp' ) && ( 'image/webp' === wp_get_image_mime( $this->file ) ) 109 ) { 110 $this->image = @imagecreatefromwebp( $this->file ); 111 } elseif ( 112 function_exists( 'imagecreatefromavif' ) && ( 'image/avif' === wp_get_image_mime( $this->file ) ) 113 ) { 114 $this->image = @imagecreatefromavif( $this->file ); 115 } else { 116 $this->image = @imagecreatefromstring( $file_contents ); 117 } 118 119 if ( ! is_gd_image( $this->image ) ) { 120 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); 121 } 122 123 $size = wp_getimagesize( $this->file ); 124 125 if ( ! $size ) { 126 return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file ); 127 } 128 129 if ( function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) { 130 imagealphablending( $this->image, false ); 131 imagesavealpha( $this->image, true ); 132 } 133 134 $this->update_size( $size[0], $size[1] ); 135 $this->mime_type = $size['mime']; 136 137 return $this->set_quality(); 138 } 139 140 /** 141 * Sets or updates current image size. 142 * 143 * @since 3.5.0 144 * 145 * @param int $width 146 * @param int $height 147 * @return true 148 */ 149 protected function update_size( $width = false, $height = false ) { 150 if ( ! $width ) { 151 $width = imagesx( $this->image ); 152 } 153 154 if ( ! $height ) { 155 $height = imagesy( $this->image ); 156 } 157 158 return parent::update_size( $width, $height ); 159 } 160 161 /** 162 * Resizes current image. 163 * 164 * Wraps `::_resize()` which returns a GD resource or GdImage instance. 165 * 166 * At minimum, either a height or width must be provided. If one of the two is set 167 * to null, the resize will maintain aspect ratio according to the provided dimension. 168 * 169 * @since 3.5.0 170 * 171 * @param int|null $max_w Image width. 172 * @param int|null $max_h Image height. 173 * @param bool|array $crop { 174 * Optional. Image cropping behavior. If false, the image will be scaled (default). 175 * If true, image will be cropped to the specified dimensions using center positions. 176 * If an array, the image will be cropped using the array to specify the crop location: 177 * 178 * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'. 179 * @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'. 180 * } 181 * @return true|WP_Error 182 */ 183 public function resize( $max_w, $max_h, $crop = false ) { 184 if ( ( $this->size['width'] === $max_w ) && ( $this->size['height'] === $max_h ) ) { 185 return true; 186 } 187 188 $resized = $this->_resize( $max_w, $max_h, $crop ); 189 190 if ( is_gd_image( $resized ) ) { 191 imagedestroy( $this->image ); 192 $this->image = $resized; 193 return true; 194 195 } elseif ( is_wp_error( $resized ) ) { 196 return $resized; 197 } 198 199 return new WP_Error( 'image_resize_error', __( 'Image resize failed.' ), $this->file ); 200 } 201 202 /** 203 * @param int $max_w 204 * @param int $max_h 205 * @param bool|array $crop { 206 * Optional. Image cropping behavior. If false, the image will be scaled (default). 207 * If true, image will be cropped to the specified dimensions using center positions. 208 * If an array, the image will be cropped using the array to specify the crop location: 209 * 210 * @type string $0 The x crop position. Accepts 'left', 'center', or 'right'. 211 * @type string $1 The y crop position. Accepts 'top', 'center', or 'bottom'. 212 * } 213 * @return resource|GdImage|WP_Error 214 */ 215 protected function _resize( $max_w, $max_h, $crop = false ) { 216 $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); 217 218 if ( ! $dims ) { 219 return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ), $this->file ); 220 } 221 222 list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; 223 224 $resized = wp_imagecreatetruecolor( $dst_w, $dst_h ); 225 imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); 226 227 if ( is_gd_image( $resized ) ) { 228 $this->update_size( $dst_w, $dst_h ); 229 return $resized; 230 } 231 232 return new WP_Error( 'image_resize_error', __( 'Image resize failed.' ), $this->file ); 233 } 234 235 /** 236 * Create multiple smaller images from a single source. 237 * 238 * Attempts to create all sub-sizes and returns the meta data at the end. This 239 * may result in the server running out of resources. When it fails there may be few 240 * "orphaned" images left over as the meta data is never returned and saved. 241 * 242 * As of 5.3.0 the preferred way to do this is with `make_subsize()`. It creates 243 * the new images one at a time and allows for the meta data to be saved after 244 * each new image is created. 245 * 246 * @since 3.5.0 247 * 248 * @param array $sizes { 249 * An array of image size data arrays. 250 * 251 * Either a height or width must be provided. 252 * If one of the two is set to null, the resize will 253 * maintain aspect ratio according to the source image. 254 * 255 * @type array ...$0 { 256 * Array of height, width values, and whether to crop. 257 * 258 * @type int $width Image width. Optional if `$height` is specified. 259 * @type int $height Image height. Optional if `$width` is specified. 260 * @type bool|array $crop Optional. Whether to crop the image. Default false. 261 * } 262 * } 263 * @return array An array of resized images' metadata by size. 264 */ 265 public function multi_resize( $sizes ) { 266 $metadata = array(); 267 268 foreach ( $sizes as $size => $size_data ) { 269 $meta = $this->make_subsize( $size_data ); 270 271 if ( ! is_wp_error( $meta ) ) { 272 $metadata[ $size ] = $meta; 273 } 274 } 275 276 return $metadata; 277 } 278 279 /** 280 * Create an image sub-size and return the image meta data value for it. 281 * 282 * @since 5.3.0 283 * 284 * @param array $size_data { 285 * Array of size data. 286 * 287 * @type int $width The maximum width in pixels. 288 * @type int $height The maximum height in pixels. 289 * @type bool|array $crop Whether to crop the image to exact dimensions. 290 * } 291 * @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta, 292 * WP_Error object on error. 293 */ 294 public function make_subsize( $size_data ) { 295 if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { 296 return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) ); 297 } 298 299 $orig_size = $this->size; 300 301 if ( ! isset( $size_data['width'] ) ) { 302 $size_data['width'] = null; 303 } 304 305 if ( ! isset( $size_data['height'] ) ) { 306 $size_data['height'] = null; 307 } 308 309 if ( ! isset( $size_data['crop'] ) ) { 310 $size_data['crop'] = false; 311 } 312 313 $resized = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); 314 315 if ( is_wp_error( $resized ) ) { 316 $saved = $resized; 317 } else { 318 $saved = $this->_save( $resized ); 319 imagedestroy( $resized ); 320 } 321 322 $this->size = $orig_size; 323 324 if ( ! is_wp_error( $saved ) ) { 325 unset( $saved['path'] ); 326 } 327 328 return $saved; 329 } 330 331 /** 332 * Crops Image. 333 * 334 * @since 3.5.0 335 * 336 * @param int $src_x The start x position to crop from. 337 * @param int $src_y The start y position to crop from. 338 * @param int $src_w The width to crop. 339 * @param int $src_h The height to crop. 340 * @param int $dst_w Optional. The destination width. 341 * @param int $dst_h Optional. The destination height. 342 * @param bool $src_abs Optional. If the source crop points are absolute. 343 * @return true|WP_Error 344 */ 345 public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { 346 /* 347 * If destination width/height isn't specified, 348 * use same as width/height from source. 349 */ 350 if ( ! $dst_w ) { 351 $dst_w = $src_w; 352 } 353 if ( ! $dst_h ) { 354 $dst_h = $src_h; 355 } 356 357 foreach ( array( $src_w, $src_h, $dst_w, $dst_h ) as $value ) { 358 if ( ! is_numeric( $value ) || (int) $value <= 0 ) { 359 return new WP_Error( 'image_crop_error', __( 'Image crop failed.' ), $this->file ); 360 } 361 } 362 363 $dst = wp_imagecreatetruecolor( (int) $dst_w, (int) $dst_h ); 364 365 if ( $src_abs ) { 366 $src_w -= $src_x; 367 $src_h -= $src_y; 368 } 369 370 if ( function_exists( 'imageantialias' ) ) { 371 imageantialias( $dst, true ); 372 } 373 374 imagecopyresampled( $dst, $this->image, 0, 0, (int) $src_x, (int) $src_y, (int) $dst_w, (int) $dst_h, (int) $src_w, (int) $src_h ); 375 376 if ( is_gd_image( $dst ) ) { 377 imagedestroy( $this->image ); 378 $this->image = $dst; 379 $this->update_size(); 380 return true; 381 } 382 383 return new WP_Error( 'image_crop_error', __( 'Image crop failed.' ), $this->file ); 384 } 385 386 /** 387 * Rotates current image counter-clockwise by $angle. 388 * Ported from image-edit.php 389 * 390 * @since 3.5.0 391 * 392 * @param float $angle 393 * @return true|WP_Error 394 */ 395 public function rotate( $angle ) { 396 if ( function_exists( 'imagerotate' ) ) { 397 $transparency = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 ); 398 $rotated = imagerotate( $this->image, $angle, $transparency ); 399 400 if ( is_gd_image( $rotated ) ) { 401 imagealphablending( $rotated, true ); 402 imagesavealpha( $rotated, true ); 403 imagedestroy( $this->image ); 404 $this->image = $rotated; 405 $this->update_size(); 406 return true; 407 } 408 } 409 410 return new WP_Error( 'image_rotate_error', __( 'Image rotate failed.' ), $this->file ); 411 } 412 413 /** 414 * Flips current image. 415 * 416 * @since 3.5.0 417 * 418 * @param bool $horz Flip along Horizontal Axis. 419 * @param bool $vert Flip along Vertical Axis. 420 * @return true|WP_Error 421 */ 422 public function flip( $horz, $vert ) { 423 $w = $this->size['width']; 424 $h = $this->size['height']; 425 $dst = wp_imagecreatetruecolor( $w, $h ); 426 427 if ( is_gd_image( $dst ) ) { 428 $sx = $vert ? ( $w - 1 ) : 0; 429 $sy = $horz ? ( $h - 1 ) : 0; 430 $sw = $vert ? -$w : $w; 431 $sh = $horz ? -$h : $h; 432 433 if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) { 434 imagedestroy( $this->image ); 435 $this->image = $dst; 436 return true; 437 } 438 } 439 440 return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file ); 441 } 442 443 /** 444 * Saves current in-memory image to file. 445 * 446 * @since 3.5.0 447 * @since 5.9.0 Renamed `$filename` to `$destfilename` to match parent class 448 * for PHP 8 named parameter support. 449 * @since 6.0.0 The `$filesize` value was added to the returned array. 450 * 451 * @param string|null $destfilename Optional. Destination filename. Default null. 452 * @param string|null $mime_type Optional. The mime-type. Default null. 453 * @return array|WP_Error { 454 * Array on success or WP_Error if the file failed to save. 455 * 456 * @type string $path Path to the image file. 457 * @type string $file Name of the image file. 458 * @type int $width Image width. 459 * @type int $height Image height. 460 * @type string $mime-type The mime type of the image. 461 * @type int $filesize File size of the image. 462 * } 463 */ 464 public function save( $destfilename = null, $mime_type = null ) { 465 $saved = $this->_save( $this->image, $destfilename, $mime_type ); 466 467 if ( ! is_wp_error( $saved ) ) { 468 $this->file = $saved['path']; 469 $this->mime_type = $saved['mime-type']; 470 } 471 472 return $saved; 473 } 474 475 /** 476 * @since 3.5.0 477 * @since 6.0.0 The `$filesize` value was added to the returned array. 478 * 479 * @param resource|GdImage $image 480 * @param string|null $filename 481 * @param string|null $mime_type 482 * @return array|WP_Error { 483 * Array on success or WP_Error if the file failed to save. 484 * 485 * @type string $path Path to the image file. 486 * @type string $file Name of the image file. 487 * @type int $width Image width. 488 * @type int $height Image height. 489 * @type string $mime-type The mime type of the image. 490 * @type int $filesize File size of the image. 491 * } 492 */ 493 protected function _save( $image, $filename = null, $mime_type = null ) { 494 list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); 495 496 if ( ! $filename ) { 497 $filename = $this->generate_filename( null, null, $extension ); 498 } 499 500 if ( function_exists( 'imageinterlace' ) ) { 501 /** 502 * Filters whether to output progressive images (if available). 503 * 504 * @since 6.5.0 505 * 506 * @param bool $interlace Whether to use progressive images for output if available. Default false. 507 * @param string $mime_type The mime type being saved. 508 */ 509 imageinterlace( $image, apply_filters( 'image_save_progressive', false, $mime_type ) ); 510 } 511 512 if ( 'image/gif' === $mime_type ) { 513 if ( ! $this->make_image( $filename, 'imagegif', array( $image, $filename ) ) ) { 514 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 515 } 516 } elseif ( 'image/png' === $mime_type ) { 517 // Convert from full colors to index colors, like original PNG. 518 if ( function_exists( 'imageistruecolor' ) && ! imageistruecolor( $image ) ) { 519 imagetruecolortopalette( $image, false, imagecolorstotal( $image ) ); 520 } 521 522 if ( ! $this->make_image( $filename, 'imagepng', array( $image, $filename ) ) ) { 523 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 524 } 525 } elseif ( 'image/jpeg' === $mime_type ) { 526 if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, $this->get_quality() ) ) ) { 527 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 528 } 529 } elseif ( 'image/webp' === $mime_type ) { 530 if ( ! function_exists( 'imagewebp' ) 531 || ! $this->make_image( $filename, 'imagewebp', array( $image, $filename, $this->get_quality() ) ) 532 ) { 533 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 534 } 535 } elseif ( 'image/avif' === $mime_type ) { 536 if ( ! function_exists( 'imageavif' ) 537 || ! $this->make_image( $filename, 'imageavif', array( $image, $filename, $this->get_quality() ) ) 538 ) { 539 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 540 } 541 } else { 542 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 543 } 544 545 // Set correct file permissions. 546 $stat = stat( dirname( $filename ) ); 547 $perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits. 548 chmod( $filename, $perms ); 549 550 return array( 551 'path' => $filename, 552 /** 553 * Filters the name of the saved image file. 554 * 555 * @since 2.6.0 556 * 557 * @param string $filename Name of the file. 558 */ 559 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), 560 'width' => $this->size['width'], 561 'height' => $this->size['height'], 562 'mime-type' => $mime_type, 563 'filesize' => wp_filesize( $filename ), 564 ); 565 } 566 567 /** 568 * Sets Image Compression quality on a 1-100% scale. Handles WebP lossless images. 569 * 570 * @since 6.7.0 571 * 572 * @param int $quality Compression Quality. Range: [1,100] 573 * @return true|WP_Error True if set successfully; WP_Error on failure. 574 */ 575 public function set_quality( $quality = null ) { 576 $quality_result = parent::set_quality( $quality ); 577 if ( is_wp_error( $quality_result ) ) { 578 return $quality_result; 579 } else { 580 $quality = $this->get_quality(); 581 } 582 583 // Handle setting the quality for WebP lossless images, see https://php.watch/versions/8.1/gd-webp-lossless. 584 try { 585 if ( 'image/webp' === $this->mime_type && defined( 'IMG_WEBP_LOSSLESS' ) ) { 586 $webp_info = wp_get_webp_info( $this->file ); 587 if ( ! empty( $webp_info['type'] ) && 'lossless' === $webp_info['type'] ) { 588 $quality = IMG_WEBP_LOSSLESS; 589 parent::set_quality( $quality ); 590 } 591 } 592 } catch ( Exception $e ) { 593 return new WP_Error( 'image_quality_error', $e->getMessage() ); 594 } 595 $this->quality = $quality; 596 return true; 597 } 598 599 /** 600 * Returns stream of current image. 601 * 602 * @since 3.5.0 603 * 604 * @param string $mime_type The mime type of the image. 605 * @return bool True on success, false on failure. 606 */ 607 public function stream( $mime_type = null ) { 608 list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type ); 609 610 switch ( $mime_type ) { 611 case 'image/png': 612 header( 'Content-Type: image/png' ); 613 return imagepng( $this->image ); 614 case 'image/gif': 615 header( 'Content-Type: image/gif' ); 616 return imagegif( $this->image ); 617 case 'image/webp': 618 if ( function_exists( 'imagewebp' ) ) { 619 header( 'Content-Type: image/webp' ); 620 return imagewebp( $this->image, null, $this->get_quality() ); 621 } else { 622 // Fall back to JPEG. 623 header( 'Content-Type: image/jpeg' ); 624 return imagejpeg( $this->image, null, $this->get_quality() ); 625 } 626 case 'image/avif': 627 if ( function_exists( 'imageavif' ) ) { 628 header( 'Content-Type: image/avif' ); 629 return imageavif( $this->image, null, $this->get_quality() ); 630 } 631 // Fall back to JPEG. 632 default: 633 header( 'Content-Type: image/jpeg' ); 634 return imagejpeg( $this->image, null, $this->get_quality() ); 635 } 636 } 637 638 /** 639 * Either calls editor's save function or handles file as a stream. 640 * 641 * @since 3.5.0 642 * 643 * @param string $filename 644 * @param callable $callback 645 * @param array $arguments 646 * @return bool 647 */ 648 protected function make_image( $filename, $callback, $arguments ) { 649 if ( wp_is_stream( $filename ) ) { 650 $arguments[1] = null; 651 } 652 653 return parent::make_image( $filename, $callback, $arguments ); 654 } 655 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |