[ Index ] |
PHP Cross Reference of WordPress |
[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 } 75 76 return false; 77 } 78 79 /** 80 * Loads image from $this->file into new GD Resource. 81 * 82 * @since 3.5.0 83 * 84 * @return true|WP_Error True if loaded successfully; WP_Error on failure. 85 */ 86 public function load() { 87 if ( $this->image ) { 88 return true; 89 } 90 91 if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) { 92 return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file ); 93 } 94 95 // Set artificially high because GD uses uncompressed images in memory. 96 wp_raise_memory_limit( 'image' ); 97 98 $file_contents = @file_get_contents( $this->file ); 99 100 if ( ! $file_contents ) { 101 return new WP_Error( 'error_loading_image', __( 'File does not exist?' ), $this->file ); 102 } 103 104 // WebP may not work with imagecreatefromstring(). 105 if ( 106 function_exists( 'imagecreatefromwebp' ) && 107 ( 'image/webp' === wp_get_image_mime( $this->file ) ) 108 ) { 109 $this->image = @imagecreatefromwebp( $this->file ); 110 } else { 111 $this->image = @imagecreatefromstring( $file_contents ); 112 } 113 114 if ( ! is_gd_image( $this->image ) ) { 115 return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file ); 116 } 117 118 $size = wp_getimagesize( $this->file ); 119 120 if ( ! $size ) { 121 return new WP_Error( 'invalid_image', __( 'Could not read image size.' ), $this->file ); 122 } 123 124 if ( function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) { 125 imagealphablending( $this->image, false ); 126 imagesavealpha( $this->image, true ); 127 } 128 129 $this->update_size( $size[0], $size[1] ); 130 $this->mime_type = $size['mime']; 131 132 return $this->set_quality(); 133 } 134 135 /** 136 * Sets or updates current image size. 137 * 138 * @since 3.5.0 139 * 140 * @param int $width 141 * @param int $height 142 * @return true 143 */ 144 protected function update_size( $width = false, $height = false ) { 145 if ( ! $width ) { 146 $width = imagesx( $this->image ); 147 } 148 149 if ( ! $height ) { 150 $height = imagesy( $this->image ); 151 } 152 153 return parent::update_size( $width, $height ); 154 } 155 156 /** 157 * Resizes current image. 158 * 159 * Wraps `::_resize()` which returns a GD resource or GdImage instance. 160 * 161 * At minimum, either a height or width must be provided. If one of the two is set 162 * to null, the resize will maintain aspect ratio according to the provided dimension. 163 * 164 * @since 3.5.0 165 * 166 * @param int|null $max_w Image width. 167 * @param int|null $max_h Image height. 168 * @param bool $crop 169 * @return true|WP_Error 170 */ 171 public function resize( $max_w, $max_h, $crop = false ) { 172 if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) { 173 return true; 174 } 175 176 $resized = $this->_resize( $max_w, $max_h, $crop ); 177 178 if ( is_gd_image( $resized ) ) { 179 imagedestroy( $this->image ); 180 $this->image = $resized; 181 return true; 182 183 } elseif ( is_wp_error( $resized ) ) { 184 return $resized; 185 } 186 187 return new WP_Error( 'image_resize_error', __( 'Image resize failed.' ), $this->file ); 188 } 189 190 /** 191 * @param int $max_w 192 * @param int $max_h 193 * @param bool|array $crop 194 * @return resource|GdImage|WP_Error 195 */ 196 protected function _resize( $max_w, $max_h, $crop = false ) { 197 $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); 198 199 if ( ! $dims ) { 200 return new WP_Error( 'error_getting_dimensions', __( 'Could not calculate resized image dimensions' ), $this->file ); 201 } 202 203 list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; 204 205 $resized = wp_imagecreatetruecolor( $dst_w, $dst_h ); 206 imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); 207 208 if ( is_gd_image( $resized ) ) { 209 $this->update_size( $dst_w, $dst_h ); 210 return $resized; 211 } 212 213 return new WP_Error( 'image_resize_error', __( 'Image resize failed.' ), $this->file ); 214 } 215 216 /** 217 * Create multiple smaller images from a single source. 218 * 219 * Attempts to create all sub-sizes and returns the meta data at the end. This 220 * may result in the server running out of resources. When it fails there may be few 221 * "orphaned" images left over as the meta data is never returned and saved. 222 * 223 * As of 5.3.0 the preferred way to do this is with `make_subsize()`. It creates 224 * the new images one at a time and allows for the meta data to be saved after 225 * each new image is created. 226 * 227 * @since 3.5.0 228 * 229 * @param array $sizes { 230 * An array of image size data arrays. 231 * 232 * Either a height or width must be provided. 233 * If one of the two is set to null, the resize will 234 * maintain aspect ratio according to the source image. 235 * 236 * @type array ...$0 { 237 * Array of height, width values, and whether to crop. 238 * 239 * @type int $width Image width. Optional if `$height` is specified. 240 * @type int $height Image height. Optional if `$width` is specified. 241 * @type bool $crop Optional. Whether to crop the image. Default false. 242 * } 243 * } 244 * @return array An array of resized images' metadata by size. 245 */ 246 public function multi_resize( $sizes ) { 247 $metadata = array(); 248 249 foreach ( $sizes as $size => $size_data ) { 250 $meta = $this->make_subsize( $size_data ); 251 252 if ( ! is_wp_error( $meta ) ) { 253 $metadata[ $size ] = $meta; 254 } 255 } 256 257 return $metadata; 258 } 259 260 /** 261 * Create an image sub-size and return the image meta data value for it. 262 * 263 * @since 5.3.0 264 * 265 * @param array $size_data { 266 * Array of size data. 267 * 268 * @type int $width The maximum width in pixels. 269 * @type int $height The maximum height in pixels. 270 * @type bool $crop Whether to crop the image to exact dimensions. 271 * } 272 * @return array|WP_Error The image data array for inclusion in the `sizes` array in the image meta, 273 * WP_Error object on error. 274 */ 275 public function make_subsize( $size_data ) { 276 if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { 277 return new WP_Error( 'image_subsize_create_error', __( 'Cannot resize the image. Both width and height are not set.' ) ); 278 } 279 280 $orig_size = $this->size; 281 282 if ( ! isset( $size_data['width'] ) ) { 283 $size_data['width'] = null; 284 } 285 286 if ( ! isset( $size_data['height'] ) ) { 287 $size_data['height'] = null; 288 } 289 290 if ( ! isset( $size_data['crop'] ) ) { 291 $size_data['crop'] = false; 292 } 293 294 $resized = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); 295 296 if ( is_wp_error( $resized ) ) { 297 $saved = $resized; 298 } else { 299 $saved = $this->_save( $resized ); 300 imagedestroy( $resized ); 301 } 302 303 $this->size = $orig_size; 304 305 if ( ! is_wp_error( $saved ) ) { 306 unset( $saved['path'] ); 307 } 308 309 return $saved; 310 } 311 312 /** 313 * Crops Image. 314 * 315 * @since 3.5.0 316 * 317 * @param int $src_x The start x position to crop from. 318 * @param int $src_y The start y position to crop from. 319 * @param int $src_w The width to crop. 320 * @param int $src_h The height to crop. 321 * @param int $dst_w Optional. The destination width. 322 * @param int $dst_h Optional. The destination height. 323 * @param bool $src_abs Optional. If the source crop points are absolute. 324 * @return true|WP_Error 325 */ 326 public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { 327 // If destination width/height isn't specified, 328 // use same as width/height from source. 329 if ( ! $dst_w ) { 330 $dst_w = $src_w; 331 } 332 if ( ! $dst_h ) { 333 $dst_h = $src_h; 334 } 335 336 foreach ( array( $src_w, $src_h, $dst_w, $dst_h ) as $value ) { 337 if ( ! is_numeric( $value ) || (int) $value <= 0 ) { 338 return new WP_Error( 'image_crop_error', __( 'Image crop failed.' ), $this->file ); 339 } 340 } 341 342 $dst = wp_imagecreatetruecolor( (int) $dst_w, (int) $dst_h ); 343 344 if ( $src_abs ) { 345 $src_w -= $src_x; 346 $src_h -= $src_y; 347 } 348 349 if ( function_exists( 'imageantialias' ) ) { 350 imageantialias( $dst, true ); 351 } 352 353 imagecopyresampled( $dst, $this->image, 0, 0, (int) $src_x, (int) $src_y, (int) $dst_w, (int) $dst_h, (int) $src_w, (int) $src_h ); 354 355 if ( is_gd_image( $dst ) ) { 356 imagedestroy( $this->image ); 357 $this->image = $dst; 358 $this->update_size(); 359 return true; 360 } 361 362 return new WP_Error( 'image_crop_error', __( 'Image crop failed.' ), $this->file ); 363 } 364 365 /** 366 * Rotates current image counter-clockwise by $angle. 367 * Ported from image-edit.php 368 * 369 * @since 3.5.0 370 * 371 * @param float $angle 372 * @return true|WP_Error 373 */ 374 public function rotate( $angle ) { 375 if ( function_exists( 'imagerotate' ) ) { 376 $transparency = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 ); 377 $rotated = imagerotate( $this->image, $angle, $transparency ); 378 379 if ( is_gd_image( $rotated ) ) { 380 imagealphablending( $rotated, true ); 381 imagesavealpha( $rotated, true ); 382 imagedestroy( $this->image ); 383 $this->image = $rotated; 384 $this->update_size(); 385 return true; 386 } 387 } 388 389 return new WP_Error( 'image_rotate_error', __( 'Image rotate failed.' ), $this->file ); 390 } 391 392 /** 393 * Flips current image. 394 * 395 * @since 3.5.0 396 * 397 * @param bool $horz Flip along Horizontal Axis. 398 * @param bool $vert Flip along Vertical Axis. 399 * @return true|WP_Error 400 */ 401 public function flip( $horz, $vert ) { 402 $w = $this->size['width']; 403 $h = $this->size['height']; 404 $dst = wp_imagecreatetruecolor( $w, $h ); 405 406 if ( is_gd_image( $dst ) ) { 407 $sx = $vert ? ( $w - 1 ) : 0; 408 $sy = $horz ? ( $h - 1 ) : 0; 409 $sw = $vert ? -$w : $w; 410 $sh = $horz ? -$h : $h; 411 412 if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) { 413 imagedestroy( $this->image ); 414 $this->image = $dst; 415 return true; 416 } 417 } 418 419 return new WP_Error( 'image_flip_error', __( 'Image flip failed.' ), $this->file ); 420 } 421 422 /** 423 * Saves current in-memory image to file. 424 * 425 * @since 3.5.0 426 * @since 5.9.0 Renamed `$filename` to `$destfilename` to match parent class 427 * for PHP 8 named parameter support. 428 * 429 * @param string|null $destfilename Optional. Destination filename. Default null. 430 * @param string|null $mime_type Optional. The mime-type. Default null. 431 * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string} 432 */ 433 public function save( $destfilename = null, $mime_type = null ) { 434 $saved = $this->_save( $this->image, $destfilename, $mime_type ); 435 436 if ( ! is_wp_error( $saved ) ) { 437 $this->file = $saved['path']; 438 $this->mime_type = $saved['mime-type']; 439 } 440 441 return $saved; 442 } 443 444 /** 445 * @param resource|GdImage $image 446 * @param string|null $filename 447 * @param string|null $mime_type 448 * @return array|WP_Error 449 */ 450 protected function _save( $image, $filename = null, $mime_type = null ) { 451 list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); 452 453 if ( ! $filename ) { 454 $filename = $this->generate_filename( null, null, $extension ); 455 } 456 457 if ( 'image/gif' === $mime_type ) { 458 if ( ! $this->make_image( $filename, 'imagegif', array( $image, $filename ) ) ) { 459 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 460 } 461 } elseif ( 'image/png' === $mime_type ) { 462 // Convert from full colors to index colors, like original PNG. 463 if ( function_exists( 'imageistruecolor' ) && ! imageistruecolor( $image ) ) { 464 imagetruecolortopalette( $image, false, imagecolorstotal( $image ) ); 465 } 466 467 if ( ! $this->make_image( $filename, 'imagepng', array( $image, $filename ) ) ) { 468 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 469 } 470 } elseif ( 'image/jpeg' === $mime_type ) { 471 if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, $this->get_quality() ) ) ) { 472 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 473 } 474 } elseif ( 'image/webp' == $mime_type ) { 475 if ( ! function_exists( 'imagewebp' ) || ! $this->make_image( $filename, 'imagewebp', array( $image, $filename, $this->get_quality() ) ) ) { 476 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 477 } 478 } else { 479 return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) ); 480 } 481 482 // Set correct file permissions. 483 $stat = stat( dirname( $filename ) ); 484 $perms = $stat['mode'] & 0000666; // Same permissions as parent folder, strip off the executable bits. 485 chmod( $filename, $perms ); 486 487 return array( 488 'path' => $filename, 489 /** 490 * Filters the name of the saved image file. 491 * 492 * @since 2.6.0 493 * 494 * @param string $filename Name of the file. 495 */ 496 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), 497 'width' => $this->size['width'], 498 'height' => $this->size['height'], 499 'mime-type' => $mime_type, 500 'filesize' => wp_filesize( $filename ), 501 ); 502 } 503 504 /** 505 * Returns stream of current image. 506 * 507 * @since 3.5.0 508 * 509 * @param string $mime_type The mime type of the image. 510 * @return bool True on success, false on failure. 511 */ 512 public function stream( $mime_type = null ) { 513 list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type ); 514 515 switch ( $mime_type ) { 516 case 'image/png': 517 header( 'Content-Type: image/png' ); 518 return imagepng( $this->image ); 519 case 'image/gif': 520 header( 'Content-Type: image/gif' ); 521 return imagegif( $this->image ); 522 case 'image/webp': 523 if ( function_exists( 'imagewebp' ) ) { 524 header( 'Content-Type: image/webp' ); 525 return imagewebp( $this->image, null, $this->get_quality() ); 526 } 527 // Fall back to the default if webp isn't supported. 528 default: 529 header( 'Content-Type: image/jpeg' ); 530 return imagejpeg( $this->image, null, $this->get_quality() ); 531 } 532 } 533 534 /** 535 * Either calls editor's save function or handles file as a stream. 536 * 537 * @since 3.5.0 538 * 539 * @param string $filename 540 * @param callable $callback 541 * @param array $arguments 542 * @return bool 543 */ 544 protected function make_image( $filename, $callback, $arguments ) { 545 if ( wp_is_stream( $filename ) ) { 546 $arguments[1] = null; 547 } 548 549 return parent::make_image( $filename, $callback, $arguments ); 550 } 551 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Oct 8 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |