[ 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 $this->set_quality( 225 null, 226 array( 227 'width' => $dst_w, 228 'height' => $dst_h, 229 ) 230 ); 231 232 $resized = wp_imagecreatetruecolor( $dst_w, $dst_h ); 233 imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); 234 235 if ( is_gd_image( $resized ) ) { 236 $this->update_size( $dst_w, $dst_h ); 237 return $resized; 238 } 239 240 return new WP_Error( 'image_resize_error', __( 'Image resize failed.' ), $this->file ); 241 } 242 243 /** 244 * Create multiple smaller images from a single source. 245 * 246 * Attempts to create all sub-sizes and returns the meta data at the end. This 247 * may result in the server running out of resources. When it fails there may be few 248 * "orphaned" images left over as the meta data is never returned and saved. 249 * 250 * As of 5.3.0 the preferred way to do this is with `make_subsize()`. It creates 251 * the new images one at a time and allows for the meta data to be saved after 252 * each new image is created. 253 * 254 * @since 3.5.0 255 * 256 * @param array $sizes { 257 * An array of image size data arrays. 258 * 259 * Either a height or width must be provided. 260 * If one of the two is set to null, the resize will 261 * maintain aspect ratio according to the source image. 262 * 263 * @type array ...$0 { 264 * Array of height, width values, and whether to crop. 265 * 266 * @type int $width Image width. Optional if `$height` is specified. 267 * @type int $height Image height. Optional if `$width` is specified. 268 * @type bool|array $crop Optional. Whether to crop the image. Default false. 269 * } 270 * } 271 * @return array An array of resized images' metadata by size. 272 */ 273 public function multi_resize( $sizes ) { 274 $metadata = array(); 275 276 foreach ( $sizes as $size => $size_data ) { 277 $meta = $this->make_subsize( $size_data ); 278 279 if ( ! is_wp_error( $meta ) ) { 280 $metadata[ $size ] = $meta; 281 } 282 } 283 284 return $metadata; 285 } 286 287 /** 288 * Create an image sub-size and return the image meta data value for it. 289 * 290 * @since 5.3.0 291 * 292 * @param array $size_data { 293 * Array of size data. 294 * 295 * @type int $width The maximum width in pixels. 296 * @type int $height The maximum height in pixels. 297 * @type bool|array $crop Whether to crop the image to exact dimensions. 298 * } 299 * @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta, 300 * WP_Error object on error. 301 */ 302 public function make_subsize( $size_data ) { 303 if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { 304 return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) ); 305 } 306 307 $orig_size = $this->size; 308 309 if ( ! isset( $size_data['width'] ) ) { 310 $size_data['width'] = null; 311 } 312 313 if ( ! isset( $size_data['height'] ) ) { 314 $size_data['height'] = null; 315 } 316 317 if ( ! isset( $size_data['crop'] ) ) { 318 $size_data['crop'] = false; 319 } 320 321 $resized = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); 322 323 if ( is_wp_error( $resized ) ) { 324 $saved = $resized; 325 } else { 326 $saved = $this->_save( $resized ); 327 imagedestroy( $resized ); 328 } 329 330 $this->size = $orig_size; 331 332 if ( ! is_wp_error( $saved ) ) { 333 unset( $saved['path'] ); 334 } 335 336 return $saved; 337 } 338 339 /** 340 * Crops Image. 341 * 342 * @since 3.5.0 343 * 344 * @param int $src_x The start x position to crop from. 345 * @param int $src_y The start y position to crop from. 346 * @param int $src_w The width to crop. 347 * @param int $src_h The height to crop. 348 * @param int $dst_w Optional. The destination width. 349 * @param int $dst_h Optional. The destination height. 350 * @param bool $src_abs Optional. If the source crop points are absolute. 351 * @return true|WP_Error 352 */ 353 public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { 354 /* 355 * If destination width/height isn't specified, 356 * use same as width/height from source. 357 */ 358 if ( ! $dst_w ) { 359 $dst_w = $src_w; 360 } 361 if ( ! $dst_h ) { 362 $dst_h = $src_h; 363 } 364 365 foreach ( array( $src_w, $src_h, $dst_w, $dst_h ) as $value ) { 366 if ( ! is_numeric( $value ) || (int) $value <= 0 ) { 367 return new WP_Error( 'image_crop_error', __( 'Image crop failed.' ), $this->file ); 368 } 369 } 370 371 $dst = wp_imagecreatetruecolor( (int) $dst_w, (int) $dst_h ); 372 373 if ( $src_abs ) { 374 $src_w -= $src_x; 375 $src_h -= $src_y; 376 } 377 378 if ( function_exists( 'imageantialias' ) ) { 379 imageantialias( $dst, true ); 380 } 381 382 imagecopyresampled( $dst, $this->image, 0, 0, (int) $src_x, (int) $src_y, (int) $dst_w, (int) $dst_h, (int) $src_w, (int) $src_h ); 383 384 if ( is_gd_image( $dst ) ) { 385 imagedestroy( $this->image ); 386 $this->image = $dst; 387 $this->update_size(); 388 return true; 389 } 390 391 return new WP_Error( 'image_crop_error', __( 'Image crop failed.' ), $this->file ); 392 } 393 394 /** 395 * Rotates current image counter-clockwise by $angle. 396 * Ported from image-edit.php 397 * 398 * @since 3.5.0 399 * 400 * @param float $angle 401 * @return true|WP_Error 402 */ 403 public function rotate( $angle ) { 404 if ( function_exists( 'imagerotate' ) ) { 405 $transparency = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 ); 406 $rotated = imagerotate( $this->image, $angle, $transparency ); 407 408 if ( is_gd_image( $rotated ) ) { 409 imagealphablending( $rotated, true ); 410 imagesavealpha( $rotated, true ); 411 imagedestroy( $this->image ); 412 $this->image = $rotated; 413 $this->update_size(); 414 return true; 415 } 416 } 417 418 return new WP_Error( 'image_rotate_error', __( 'Image rotate failed.' ), $this->file ); 419 } 420 421 /** 422 * Flips current image. 423 * 424 * @since 3.5.0 425 * 426 * @param bool $horz Flip along Horizontal Axis. 427 * @param bool $vert Flip along Vertical Axis. 428 * @return true|WP_Error 429 */ 430 public function flip( $horz, $vert ) { 431 $w = $this->size['width']; 432 $h = $this->size['height']; 433 $dst = wp_imagecreatetruecolor( $w, $h ); 434 435 if ( is_gd_image( $dst ) ) { 436 $sx = $vert ? ( $w - 1 ) : 0; 437 $sy = $horz ? ( $h - 1 ) : 0; 438 $sw = $vert ? -$w : $w; 439 $sh = $horz ? -$h : $h; 440 441 if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) { 442 imagedestroy( $this->image ); 443 $this->image = $dst; 444 return true; 445 } 446 } 447 448 return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file ); 449 } 450 451 /** 452 * Saves current in-memory image to file. 453 * 454 * @since 3.5.0 455 * @since 5.9.0 Renamed `$filename` to `$destfilename` to match parent class 456 * for PHP 8 named parameter support. 457 * @since 6.0.0 The `$filesize` value was added to the returned array. 458 * 459 * @param string|null $destfilename Optional. Destination filename. Default null. 460 * @param string|null $mime_type Optional. The mime-type. Default null. 461 * @return array|WP_Error { 462 * Array on success or WP_Error if the file failed to save. 463 * 464 * @type string $path Path to the image file. 465 * @type string $file Name of the image file. 466 * @type int $width Image width. 467 * @type int $height Image height. 468 * @type string $mime-type The mime type of the image. 469 * @type int $filesize File size of the image. 470 * } 471 */ 472 public function save( $destfilename = null, $mime_type = null ) { 473 $saved = $this->_save( $this->image, $destfilename, $mime_type ); 474 475 if ( ! is_wp_error( $saved ) ) { 476 $this->file = $saved['path']; 477 $this->mime_type = $saved['mime-type']; 478 } 479 480 return $saved; 481 } 482 483 /** 484 * @since 3.5.0 485 * @since 6.0.0 The `$filesize` value was added to the returned array. 486 * 487 * @param resource|GdImage $image 488 * @param string|null $filename 489 * @param string|null $mime_type 490 * @return array|WP_Error { 491 * Array on success or WP_Error if the file failed to save. 492 * 493 * @type string $path Path to the image file. 494 * @type string $file Name of the image file. 495 * @type int $width Image width. 496 * @type int $height Image height. 497 * @type string $mime-type The mime type of the image. 498 * @type int $filesize File size of the image. 499 * } 500 */ 501 protected function _save( $image, $filename = null, $mime_type = null ) { 502 list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); 503 504 if ( ! $filename ) { 505 $filename = $this->generate_filename( null, null, $extension ); 506 } 507 508 if ( function_exists( 'imageinterlace' ) ) { 509 /** 510 * Filters whether to output progressive images (if available). 511 * 512 * @since 6.5.0 513 * 514 * @param bool $interlace Whether to use progressive images for output if available. Default false. 515 * @param string $mime_type The mime type being saved. 516 */ 517 imageinterlace( $image, apply_filters( 'image_save_progressive', false, $mime_type ) ); 518 } 519 520 if ( 'image/gif' === $mime_type ) { 521 if ( ! $this->make_image( $filename, 'imagegif', array( $image, $filename ) ) ) { 522 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 523 } 524 } elseif ( 'image/png' === $mime_type ) { 525 // Convert from full colors to index colors, like original PNG. 526 if ( function_exists( 'imageistruecolor' ) && ! imageistruecolor( $image ) ) { 527 imagetruecolortopalette( $image, false, imagecolorstotal( $image ) ); 528 } 529 530 if ( ! $this->make_image( $filename, 'imagepng', array( $image, $filename ) ) ) { 531 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 532 } 533 } elseif ( 'image/jpeg' === $mime_type ) { 534 if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, $this->get_quality() ) ) ) { 535 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 536 } 537 } elseif ( 'image/webp' === $mime_type ) { 538 if ( ! function_exists( 'imagewebp' ) 539 || ! $this->make_image( $filename, 'imagewebp', array( $image, $filename, $this->get_quality() ) ) 540 ) { 541 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 542 } 543 } elseif ( 'image/avif' === $mime_type ) { 544 if ( ! function_exists( 'imageavif' ) 545 || ! $this->make_image( $filename, 'imageavif', array( $image, $filename, $this->get_quality() ) ) 546 ) { 547 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 548 } 549 } else { 550 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 551 } 552 553 // Set correct file permissions. 554 $stat = stat( dirname( $filename ) ); 555 $perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits. 556 chmod( $filename, $perms ); 557 558 return array( 559 'path' => $filename, 560 /** 561 * Filters the name of the saved image file. 562 * 563 * @since 2.6.0 564 * 565 * @param string $filename Name of the file. 566 */ 567 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), 568 'width' => $this->size['width'], 569 'height' => $this->size['height'], 570 'mime-type' => $mime_type, 571 'filesize' => wp_filesize( $filename ), 572 ); 573 } 574 575 /** 576 * Sets Image Compression quality on a 1-100% scale. Handles WebP lossless images. 577 * 578 * @since 6.7.0 579 * @since 6.8.0 The `$dims` parameter was added. 580 * 581 * @param int $quality Compression Quality. Range: [1,100] 582 * @param array $dims Optional. Image dimensions array with 'width' and 'height' keys. 583 * @return true|WP_Error True if set successfully; WP_Error on failure. 584 */ 585 public function set_quality( $quality = null, $dims = array() ) { 586 $quality_result = parent::set_quality( $quality, $dims ); 587 if ( is_wp_error( $quality_result ) ) { 588 return $quality_result; 589 } else { 590 $quality = $this->get_quality(); 591 } 592 593 // Handle setting the quality for WebP lossless images, see https://php.watch/versions/8.1/gd-webp-lossless. 594 try { 595 if ( 'image/webp' === $this->mime_type && defined( 'IMG_WEBP_LOSSLESS' ) ) { 596 $webp_info = wp_get_webp_info( $this->file ); 597 if ( ! empty( $webp_info['type'] ) && 'lossless' === $webp_info['type'] ) { 598 $quality = IMG_WEBP_LOSSLESS; 599 parent::set_quality( $quality, $dims ); 600 } 601 } 602 } catch ( Exception $e ) { 603 return new WP_Error( 'image_quality_error', $e->getMessage() ); 604 } 605 $this->quality = $quality; 606 return true; 607 } 608 609 /** 610 * Returns stream of current image. 611 * 612 * @since 3.5.0 613 * 614 * @param string $mime_type The mime type of the image. 615 * @return bool True on success, false on failure. 616 */ 617 public function stream( $mime_type = null ) { 618 list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type ); 619 620 switch ( $mime_type ) { 621 case 'image/png': 622 header( 'Content-Type: image/png' ); 623 return imagepng( $this->image ); 624 case 'image/gif': 625 header( 'Content-Type: image/gif' ); 626 return imagegif( $this->image ); 627 case 'image/webp': 628 if ( function_exists( 'imagewebp' ) ) { 629 header( 'Content-Type: image/webp' ); 630 return imagewebp( $this->image, null, $this->get_quality() ); 631 } else { 632 // Fall back to JPEG. 633 header( 'Content-Type: image/jpeg' ); 634 return imagejpeg( $this->image, null, $this->get_quality() ); 635 } 636 case 'image/avif': 637 if ( function_exists( 'imageavif' ) ) { 638 header( 'Content-Type: image/avif' ); 639 return imageavif( $this->image, null, $this->get_quality() ); 640 } 641 // Fall back to JPEG. 642 default: 643 header( 'Content-Type: image/jpeg' ); 644 return imagejpeg( $this->image, null, $this->get_quality() ); 645 } 646 } 647 648 /** 649 * Either calls editor's save function or handles file as a stream. 650 * 651 * @since 3.5.0 652 * 653 * @param string $filename 654 * @param callable $callback 655 * @param array $arguments 656 * @return bool 657 */ 658 protected function make_image( $filename, $callback, $arguments ) { 659 if ( wp_is_stream( $filename ) ) { 660 $arguments[1] = null; 661 } 662 663 return parent::make_image( $filename, $callback, $arguments ); 664 } 665 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Dec 21 08:20:01 2024 | Cross-referenced by PHPXref |