|
| 1 | +<?php |
| 2 | + |
| 3 | +require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; |
| 4 | + |
| 5 | +class Image_Editor_Vips extends \WP_Image_Editor { |
| 6 | + /** |
| 7 | + * GD Resource. |
| 8 | + * |
| 9 | + * @var resource |
| 10 | + */ |
| 11 | + protected $image; |
| 12 | + |
| 13 | + public function __destruct() { |
| 14 | + if ( $this->image ) { |
| 15 | + // we don't need the original in memory anymore |
| 16 | + imagedestroy( $this->image ); |
| 17 | + } |
| 18 | + } |
| 19 | + |
| 20 | + /** |
| 21 | + * Checks to see if current environment supports GD. |
| 22 | + * |
| 23 | + * @since 3.5.0 |
| 24 | + * |
| 25 | + * @static |
| 26 | + * |
| 27 | + * @param array $args |
| 28 | + * @return bool |
| 29 | + */ |
| 30 | + public static function test( $args = array() ) { |
| 31 | + if ( ! extension_loaded('gd') || ! function_exists('gd_info') ) |
| 32 | + return false; |
| 33 | + |
| 34 | + // On some setups GD library does not provide imagerotate() - Ticket #11536 |
| 35 | + if ( isset( $args['methods'] ) && |
| 36 | + in_array( 'rotate', $args['methods'] ) && |
| 37 | + ! function_exists('imagerotate') ){ |
| 38 | + |
| 39 | + return false; |
| 40 | + } |
| 41 | + |
| 42 | + return true; |
| 43 | + } |
| 44 | + |
| 45 | + /** |
| 46 | + * Checks to see if editor supports the mime-type specified. |
| 47 | + * |
| 48 | + * @since 3.5.0 |
| 49 | + * |
| 50 | + * @static |
| 51 | + * |
| 52 | + * @param string $mime_type |
| 53 | + * @return bool |
| 54 | + */ |
| 55 | + public static function supports_mime_type( $mime_type ) { |
| 56 | + $image_types = imagetypes(); |
| 57 | + switch( $mime_type ) { |
| 58 | + case 'image/jpeg': |
| 59 | + return ($image_types & IMG_JPG) != 0; |
| 60 | + case 'image/png': |
| 61 | + return ($image_types & IMG_PNG) != 0; |
| 62 | + case 'image/gif': |
| 63 | + return ($image_types & IMG_GIF) != 0; |
| 64 | + } |
| 65 | + |
| 66 | + return false; |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * Loads image from $this->file into new GD Resource. |
| 71 | + * |
| 72 | + * @since 3.5.0 |
| 73 | + * |
| 74 | + * @return bool|WP_Error True if loaded successfully; WP_Error on failure. |
| 75 | + */ |
| 76 | + public function load() { |
| 77 | + if ( $this->image ) |
| 78 | + return true; |
| 79 | + |
| 80 | + if ( ! is_file( $this->file ) && ! preg_match( '|^https?://|', $this->file ) ) |
| 81 | + return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); |
| 82 | + |
| 83 | + // Set artificially high because GD uses uncompressed images in memory. |
| 84 | + wp_raise_memory_limit( 'image' ); |
| 85 | + |
| 86 | + |
| 87 | + |
| 88 | + //$this->image = @imagecreatefromstring( file_get_contents( $this->file ) ); |
| 89 | + $image_file = file_get_contents( $this->file ); |
| 90 | + $this->image = Jcupitt\Vips\Image::newFromFile($this->file); |
| 91 | + |
| 92 | + //if ( ! is_resource( $this->image ) ) |
| 93 | + // return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file ); |
| 94 | + |
| 95 | + //$size = @getimagesize( $this->file ); |
| 96 | + //if ( ! $size ) |
| 97 | + // return new WP_Error( 'invalid_image', __('Could not read image size.'), $this->file ); |
| 98 | + |
| 99 | + //if ( function_exists( 'imagealphablending' ) && function_exists( 'imagesavealpha' ) ) { |
| 100 | + // imagealphablending( $this->image, false ); |
| 101 | + // imagesavealpha( $this->image, true ); |
| 102 | + //} |
| 103 | + |
| 104 | + $this->update_size( $this->image->width, $this->image->height ); |
| 105 | + |
| 106 | + $file_info = new finfo(FILEINFO_MIME_TYPE); |
| 107 | + $mime_type = $file_info->buffer(file_get_contents($image_file)); |
| 108 | + $this->mime_type = $mime_type; |
| 109 | + |
| 110 | + return $this->set_quality(); |
| 111 | + } |
| 112 | + |
| 113 | + /** |
| 114 | + * Sets or updates current image size. |
| 115 | + * |
| 116 | + * @since 3.5.0 |
| 117 | + * |
| 118 | + * @param int $width |
| 119 | + * @param int $height |
| 120 | + * @return true |
| 121 | + */ |
| 122 | + protected function update_size( $width = false, $height = false ) { |
| 123 | + if ( ! $width ) |
| 124 | + $width = $this->image->width; |
| 125 | + |
| 126 | + if ( ! $height ) |
| 127 | + $height = $this->image->height; |
| 128 | + |
| 129 | + return parent::update_size( $width, $height ); |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Resizes current image. |
| 134 | + * Wraps _resize, since _resize returns a GD Resource. |
| 135 | + * |
| 136 | + * At minimum, either a height or width must be provided. |
| 137 | + * If one of the two is set to null, the resize will |
| 138 | + * maintain aspect ratio according to the provided dimension. |
| 139 | + * |
| 140 | + * @since 3.5.0 |
| 141 | + * |
| 142 | + * @param int|null $max_w Image width. |
| 143 | + * @param int|null $max_h Image height. |
| 144 | + * @param bool $crop |
| 145 | + * @return true|WP_Error |
| 146 | + */ |
| 147 | + public function resize( $max_w, $max_h, $crop = false ) { |
| 148 | + if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) |
| 149 | + return true; |
| 150 | + |
| 151 | + $resized = $this->_resize( $max_w, $max_h, $crop ); |
| 152 | + |
| 153 | + //if ( is_resource( $resized ) ) { |
| 154 | + // imagedestroy( $this->image ); |
| 155 | + $this->image = $resized; |
| 156 | + return true; |
| 157 | + |
| 158 | + //} elseif ( is_wp_error( $resized ) ) |
| 159 | + // return $resized; |
| 160 | + |
| 161 | + //return new WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file ); |
| 162 | + } |
| 163 | + |
| 164 | + /** |
| 165 | + * |
| 166 | + * @param int $max_w |
| 167 | + * @param int $max_h |
| 168 | + * @param bool|array $crop |
| 169 | + * @return resource|WP_Error |
| 170 | + */ |
| 171 | + protected function _resize( $max_w, $max_h, $crop = false ) { |
| 172 | + $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); |
| 173 | + if ( ! $dims ) { |
| 174 | + return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions'), $this->file ); |
| 175 | + } |
| 176 | + list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; |
| 177 | + |
| 178 | + //$resized = wp_imagecreatetruecolor( $dst_w, $dst_h ); |
| 179 | + //imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); |
| 180 | + |
| 181 | + error_log('$src_x ' . $src_x); |
| 182 | + error_log('$src_y ' . $src_y); |
| 183 | + error_log('$src_w ' . $src_w); |
| 184 | + error_log('$src_h ' . $src_h); |
| 185 | + error_log('$dst_w ' . $dst_w); |
| 186 | + error_log('$dst_h ' . $dst_h); |
| 187 | + error_log('$dst_x ' . $dst_x); |
| 188 | + error_log('$dst_y ' . $dst_y); |
| 189 | + |
| 190 | + $resized = $this->image->resize(max($dst_h/$src_h, $dst_w/$src_w))->crop($dst_x, $dst_y, $dst_w, $dst_h); |
| 191 | + |
| 192 | + //if ( is_resource( $resized ) ) { |
| 193 | + $this->update_size( $dst_w, $dst_h ); |
| 194 | + return $resized; |
| 195 | + //} |
| 196 | + |
| 197 | + //return new WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file ); |
| 198 | + } |
| 199 | + |
| 200 | + /** |
| 201 | + * Resize multiple images from a single source. |
| 202 | + * |
| 203 | + * @since 3.5.0 |
| 204 | + * |
| 205 | + * @param array $sizes { |
| 206 | + * An array of image size arrays. Default sizes are 'small', 'medium', 'medium_large', 'large'. |
| 207 | + * |
| 208 | + * Either a height or width must be provided. |
| 209 | + * If one of the two is set to null, the resize will |
| 210 | + * maintain aspect ratio according to the provided dimension. |
| 211 | + * |
| 212 | + * @type array $size { |
| 213 | + * Array of height, width values, and whether to crop. |
| 214 | + * |
| 215 | + * @type int $width Image width. Optional if `$height` is specified. |
| 216 | + * @type int $height Image height. Optional if `$width` is specified. |
| 217 | + * @type bool $crop Optional. Whether to crop the image. Default false. |
| 218 | + * } |
| 219 | + * } |
| 220 | + * @return array An array of resized images' metadata by size. |
| 221 | + */ |
| 222 | + public function multi_resize( $sizes ) { |
| 223 | + $metadata = array(); |
| 224 | + $orig_size = $this->size; |
| 225 | + |
| 226 | + foreach ( $sizes as $size => $size_data ) { |
| 227 | + if ( ! isset( $size_data['width'] ) && ! isset( $size_data['height'] ) ) { |
| 228 | + continue; |
| 229 | + } |
| 230 | + |
| 231 | + if ( ! isset( $size_data['width'] ) ) { |
| 232 | + $size_data['width'] = null; |
| 233 | + } |
| 234 | + if ( ! isset( $size_data['height'] ) ) { |
| 235 | + $size_data['height'] = null; |
| 236 | + } |
| 237 | + |
| 238 | + if ( ! isset( $size_data['crop'] ) ) { |
| 239 | + $size_data['crop'] = false; |
| 240 | + } |
| 241 | + |
| 242 | + $image = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); |
| 243 | + $duplicate = ( ( $orig_size['width'] == $size_data['width'] ) && ( $orig_size['height'] == $size_data['height'] ) ); |
| 244 | + |
| 245 | + if ( ! is_wp_error( $image ) && ! $duplicate ) { |
| 246 | + $resized = $this->_save( $image ); |
| 247 | + |
| 248 | + imagedestroy( $image ); |
| 249 | + |
| 250 | + if ( ! is_wp_error( $resized ) && $resized ) { |
| 251 | + unset( $resized['path'] ); |
| 252 | + $metadata[$size] = $resized; |
| 253 | + } |
| 254 | + } |
| 255 | + |
| 256 | + $this->size = $orig_size; |
| 257 | + } |
| 258 | + |
| 259 | + return $metadata; |
| 260 | + } |
| 261 | + |
| 262 | + /** |
| 263 | + * Crops Image. |
| 264 | + * |
| 265 | + * @since 3.5.0 |
| 266 | + * |
| 267 | + * @param int $src_x The start x position to crop from. |
| 268 | + * @param int $src_y The start y position to crop from. |
| 269 | + * @param int $src_w The width to crop. |
| 270 | + * @param int $src_h The height to crop. |
| 271 | + * @param int $dst_w Optional. The destination width. |
| 272 | + * @param int $dst_h Optional. The destination height. |
| 273 | + * @param bool $src_abs Optional. If the source crop points are absolute. |
| 274 | + * @return bool|WP_Error |
| 275 | + */ |
| 276 | + public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { |
| 277 | + // If destination width/height isn't specified, use same as |
| 278 | + // width/height from source. |
| 279 | + if ( ! $dst_w ) |
| 280 | + $dst_w = $src_w; |
| 281 | + if ( ! $dst_h ) |
| 282 | + $dst_h = $src_h; |
| 283 | + |
| 284 | + $dst = wp_imagecreatetruecolor( $dst_w, $dst_h ); |
| 285 | + |
| 286 | + if ( $src_abs ) { |
| 287 | + $src_w -= $src_x; |
| 288 | + $src_h -= $src_y; |
| 289 | + } |
| 290 | + |
| 291 | + if ( function_exists( 'imageantialias' ) ) |
| 292 | + imageantialias( $dst, true ); |
| 293 | + |
| 294 | + imagecopyresampled( $dst, $this->image, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); |
| 295 | + |
| 296 | + if ( is_resource( $dst ) ) { |
| 297 | + imagedestroy( $this->image ); |
| 298 | + $this->image = $dst; |
| 299 | + $this->update_size(); |
| 300 | + return true; |
| 301 | + } |
| 302 | + |
| 303 | + return new WP_Error( 'image_crop_error', __('Image crop failed.'), $this->file ); |
| 304 | + } |
| 305 | + |
| 306 | + /** |
| 307 | + * Rotates current image counter-clockwise by $angle. |
| 308 | + * Ported from image-edit.php |
| 309 | + * |
| 310 | + * @since 3.5.0 |
| 311 | + * |
| 312 | + * @param float $angle |
| 313 | + * @return true|WP_Error |
| 314 | + */ |
| 315 | + public function rotate( $angle ) { |
| 316 | + if ( function_exists('imagerotate') ) { |
| 317 | + $transparency = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 ); |
| 318 | + $rotated = imagerotate( $this->image, $angle, $transparency ); |
| 319 | + |
| 320 | + if ( is_resource( $rotated ) ) { |
| 321 | + imagealphablending( $rotated, true ); |
| 322 | + imagesavealpha( $rotated, true ); |
| 323 | + imagedestroy( $this->image ); |
| 324 | + $this->image = $rotated; |
| 325 | + $this->update_size(); |
| 326 | + return true; |
| 327 | + } |
| 328 | + } |
| 329 | + return new WP_Error( 'image_rotate_error', __('Image rotate failed.'), $this->file ); |
| 330 | + } |
| 331 | + |
| 332 | + /** |
| 333 | + * Flips current image. |
| 334 | + * |
| 335 | + * @since 3.5.0 |
| 336 | + * |
| 337 | + * @param bool $horz Flip along Horizontal Axis |
| 338 | + * @param bool $vert Flip along Vertical Axis |
| 339 | + * @return true|WP_Error |
| 340 | + */ |
| 341 | + public function flip( $horz, $vert ) { |
| 342 | + $w = $this->size['width']; |
| 343 | + $h = $this->size['height']; |
| 344 | + $dst = wp_imagecreatetruecolor( $w, $h ); |
| 345 | + |
| 346 | + if ( is_resource( $dst ) ) { |
| 347 | + $sx = $vert ? ($w - 1) : 0; |
| 348 | + $sy = $horz ? ($h - 1) : 0; |
| 349 | + $sw = $vert ? -$w : $w; |
| 350 | + $sh = $horz ? -$h : $h; |
| 351 | + |
| 352 | + if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) { |
| 353 | + imagedestroy( $this->image ); |
| 354 | + $this->image = $dst; |
| 355 | + return true; |
| 356 | + } |
| 357 | + } |
| 358 | + return new WP_Error( 'image_flip_error', __('Image flip failed.'), $this->file ); |
| 359 | + } |
| 360 | + |
| 361 | + /** |
| 362 | + * Saves current in-memory image to file. |
| 363 | + * |
| 364 | + * @since 3.5.0 |
| 365 | + * |
| 366 | + * @param string|null $filename |
| 367 | + * @param string|null $mime_type |
| 368 | + * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string} |
| 369 | + */ |
| 370 | + public function save( $filename = null, $mime_type = null ) { |
| 371 | + $saved = $this->_save( $this->image, $filename, $mime_type ); |
| 372 | + |
| 373 | + if ( ! is_wp_error( $saved ) ) { |
| 374 | + $this->file = $saved['path']; |
| 375 | + $this->mime_type = $saved['mime-type']; |
| 376 | + } |
| 377 | + |
| 378 | + return $saved; |
| 379 | + } |
| 380 | + |
| 381 | + /** |
| 382 | + * @param resource $image |
| 383 | + * @param string|null $filename |
| 384 | + * @param string|null $mime_type |
| 385 | + * @return WP_Error|array |
| 386 | + */ |
| 387 | + protected function _save( $image, $filename = null, $mime_type = null ) { |
| 388 | + list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); |
| 389 | + |
| 390 | + if ( ! $filename ) |
| 391 | + $filename = $this->generate_filename( null, null, $extension ); |
| 392 | + |
| 393 | + $image->writeToFile($filename); |
| 394 | + |
| 395 | + /*if ( 'image/gif' == $mime_type ) { |
| 396 | + if ( ! $this->make_image( $filename, 'imagegif', array( $image, $filename ) ) ) |
| 397 | + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); |
| 398 | + } |
| 399 | + elseif ( 'image/png' == $mime_type ) { |
| 400 | + // convert from full colors to index colors, like original PNG. |
| 401 | + if ( function_exists('imageistruecolor') && ! imageistruecolor( $image ) ) |
| 402 | + imagetruecolortopalette( $image, false, imagecolorstotal( $image ) ); |
| 403 | +
|
| 404 | + if ( ! $this->make_image( $filename, 'imagepng', array( $image, $filename ) ) ) |
| 405 | + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); |
| 406 | + } |
| 407 | + elseif ( 'image/jpeg' == $mime_type ) { |
| 408 | + if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, $this->get_quality() ) ) ) |
| 409 | + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); |
| 410 | + } |
| 411 | + else { |
| 412 | + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); |
| 413 | + }*/ |
| 414 | + |
| 415 | + // Set correct file permissions |
| 416 | + $stat = stat( dirname( $filename ) ); |
| 417 | + $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits |
| 418 | + @ chmod( $filename, $perms ); |
| 419 | + |
| 420 | + /** |
| 421 | + * Filters the name of the saved image file. |
| 422 | + * |
| 423 | + * @since 2.6.0 |
| 424 | + * |
| 425 | + * @param string $filename Name of the file. |
| 426 | + */ |
| 427 | + return array( |
| 428 | + 'path' => $filename, |
| 429 | + 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), |
| 430 | + 'width' => $this->size['width'], |
| 431 | + 'height' => $this->size['height'], |
| 432 | + 'mime-type' => $mime_type, |
| 433 | + ); |
| 434 | + } |
| 435 | + |
| 436 | + /** |
| 437 | + * Returns stream of current image. |
| 438 | + * |
| 439 | + * @since 3.5.0 |
| 440 | + * |
| 441 | + * @param string $mime_type The mime type of the image. |
| 442 | + * @return bool True on success, false on failure. |
| 443 | + */ |
| 444 | + public function stream( $mime_type = null ) { |
| 445 | + list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type ); |
| 446 | + |
| 447 | + switch ( $mime_type ) { |
| 448 | + case 'image/png': |
| 449 | + header( 'Content-Type: image/png' ); |
| 450 | + return imagepng( $this->image ); |
| 451 | + case 'image/gif': |
| 452 | + header( 'Content-Type: image/gif' ); |
| 453 | + return imagegif( $this->image ); |
| 454 | + default: |
| 455 | + header( 'Content-Type: image/jpeg' ); |
| 456 | + return imagejpeg( $this->image, null, $this->get_quality() ); |
| 457 | + } |
| 458 | + } |
| 459 | + |
| 460 | + /** |
| 461 | + * Either calls editor's save function or handles file as a stream. |
| 462 | + * |
| 463 | + * @since 3.5.0 |
| 464 | + * |
| 465 | + * @param string|stream $filename |
| 466 | + * @param callable $function |
| 467 | + * @param array $arguments |
| 468 | + * @return bool |
| 469 | + */ |
| 470 | + protected function make_image( $filename, $function, $arguments ) { |
| 471 | + |
| 472 | + //if ( wp_is_stream( $filename ) ) |
| 473 | + // $arguments[1] = null; |
| 474 | + |
| 475 | + //return parent::make_image( $filename, $function, $arguments ); |
| 476 | + } |
| 477 | +} |
0 commit comments