diff --git a/wp-admin/includes/image-edit.php b/wp-admin/includes/image-edit.php index 8185c20e7c..f390b5e418 100644 --- a/wp-admin/includes/image-edit.php +++ b/wp-admin/includes/image-edit.php @@ -197,39 +197,82 @@ function wp_image_editor($post_id, $msg = false) { stream( $mime_type ) ) ) return false; + + return true; + } else { + _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) ); + + $image = apply_filters('image_save_pre', $image, $post_id); + + switch ( $mime_type ) { + case 'image/jpeg': + header( 'Content-Type: image/jpeg' ); + return imagejpeg( $image, null, 90 ); + case 'image/png': + header( 'Content-Type: image/png' ); + return imagepng( $image ); + case 'image/gif': + header( 'Content-Type: image/gif' ); + return imagegif( $image ); + default: + return false; + } } } -function wp_save_image_file($filename, $image, $mime_type, $post_id) { - $image = apply_filters('image_save_pre', $image, $post_id); - $saved = apply_filters('wp_save_image_file', null, $filename, $image, $mime_type, $post_id); - if ( null !== $saved ) - return $saved; +/** + * Saves Image to File + * @TODO: Add mime_type support to WP_Image_Editor + * + * @param string $filename + * @param WP_Image_Editor $image + * @param string $mime_type + * @param int $post_id + * @return boolean + */ +function wp_save_image_file( $filename, $image, $mime_type, $post_id ) { + if ( $image instanceof WP_Image_Editor ) { + $image = apply_filters('image_editor_save_pre', $image, $post_id); + $saved = apply_filters('wp_save_image_editor_file', null, $filename, $image, $mime_type, $post_id); - switch ( $mime_type ) { - case 'image/jpeg': - return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) ); - case 'image/png': - return imagepng($image, $filename); - case 'image/gif': - return imagegif($image, $filename); - default: - return false; + if ( null !== $saved ) + return $saved; + + return $image->save( $filename, $mime_type ); + } else { + _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) ); + + $image = apply_filters('image_save_pre', $image, $post_id); + $saved = apply_filters('wp_save_image_file', null, $filename, $image, $mime_type, $post_id); + + if ( null !== $saved ) + return $saved; + + switch ( $mime_type ) { + case 'image/jpeg': + return imagejpeg( $image, $filename, apply_filters( 'jpeg_quality', 90, 'edit_image' ) ); + case 'image/png': + return imagepng( $image, $filename ); + case 'image/gif': + return imagegif( $image, $filename ); + default: + return false; + } } } @@ -238,7 +281,9 @@ function _image_get_preview_ratio($w, $h) { return $max > 400 ? (400 / $max) : 1; } +// @TODO: Returns GD resource, but is NOT public function _rotate_image_resource($img, $angle) { + _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::rotate' ) ); if ( function_exists('imagerotate') ) { $rotated = imagerotate($img, $angle, 0); if ( is_resource($rotated) ) { @@ -249,7 +294,18 @@ function _rotate_image_resource($img, $angle) { return $img; } +/** + * @TODO: Only used within image_edit_apply_changes + * and receives/returns GD Resource. + * Consider removal. + * + * @param GD_Resource $img + * @param boolean $horz + * @param boolean $vert + * @return GD_Resource + */ function _flip_image_resource($img, $horz, $vert) { + _deprecated_function( __FUNCTION__, '3.5', __( 'Use WP_Image_Editor::flip' ) ); $w = imagesx($img); $h = imagesy($img); $dst = wp_imagecreatetruecolor($w, $h); @@ -267,6 +323,18 @@ function _flip_image_resource($img, $horz, $vert) { return $img; } +/** + * @TODO: Only used within image_edit_apply_changes + * and receives/returns GD Resource. + * Consider removal. + * + * @param GD_Resource $img + * @param float $x + * @param float $y + * @param float $w + * @param float $h + * @return GD_Resource + */ function _crop_image_resource($img, $x, $y, $w, $h) { $dst = wp_imagecreatetruecolor($w, $h); if ( is_resource($dst) ) { @@ -278,10 +346,19 @@ function _crop_image_resource($img, $x, $y, $w, $h) { return $img; } -function image_edit_apply_changes($img, $changes) { +/** + * Performs group of changes on Editor specified. + * + * @param WP_Image_Editor $image + * @param type $changes + * @return WP_Image_Editor + */ +function image_edit_apply_changes( $image, $changes ) { + if ( is_resource( $image ) ) + _deprecated_argument( __FUNCTION__, '3.5', __( '$image needs to be an WP_Image_Editor object' ) ); if ( !is_array($changes) ) - return $img; + return $image; // expand change operations foreach ( $changes as $key => $obj ) { @@ -326,55 +403,83 @@ function image_edit_apply_changes($img, $changes) { } // image resource before applying the changes - $img = apply_filters('image_edit_before_change', $img, $changes); + if ( $image instanceof WP_Image_Editor ) + $image = apply_filters('wp_image_editor_before_change', $image, $changes); + elseif ( is_resource( $image ) ) + $image = apply_filters('image_edit_before_change', $image, $changes); foreach ( $changes as $operation ) { switch ( $operation->type ) { case 'rotate': - if ( $operation->angle != 0 ) - $img = _rotate_image_resource($img, $operation->angle); + if ( $operation->angle != 0 ) { + if ( $image instanceof WP_Image_Editor ) + $image->rotate( $operation->angle ); + else + $image = _rotate_image_resource( $image, $operation->angle ); + } break; case 'flip': if ( $operation->axis != 0 ) - $img = _flip_image_resource($img, ($operation->axis & 1) != 0, ($operation->axis & 2) != 0); + if ( $image instanceof WP_Image_Editor ) + $image->flip( ($operation->axis & 1) != 0, ($operation->axis & 2) != 0 ); + else + $image = _flip_image_resource( $image, ( $operation->axis & 1 ) != 0, ( $operation->axis & 2 ) != 0 ); break; case 'crop': $sel = $operation->sel; - $scale = 1 / _image_get_preview_ratio( imagesx($img), imagesy($img) ); // discard preview scaling - $img = _crop_image_resource($img, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale); + + if ( $image instanceof WP_Image_Editor ) { + $size = $image->get_size(); + $w = $size['width']; + $h = $size['height']; + + $scale = 1 / _image_get_preview_ratio( $w, $h ); // discard preview scaling + $image->crop( $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale ); + } else { + $scale = 1 / _image_get_preview_ratio( imagesx( $image ), imagesy( $image ) ); // discard preview scaling + $image = _crop_image_resource( $image, $sel->x * $scale, $sel->y * $scale, $sel->w * $scale, $sel->h * $scale ); + } break; } } - return $img; + return $image; } -function stream_preview_image($post_id) { - $post = get_post($post_id); - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); - $img = load_image_to_edit( $post_id, $post->post_mime_type, array(400, 400) ); - if ( !is_resource($img) ) - return false; +/** + * Streams image in post to browser, along with enqueued changes + * in $_REQUEST['history'] + * + * @param int $post_id + * @return boolean + */ +function stream_preview_image( $post_id ) { + $post = get_post( $post_id ); + @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); + + $img = WP_Image_Editor::get_instance( _load_image_to_edit_path( $post_id ) ); + + if ( is_wp_error( $img ) ) + return false; $changes = !empty($_REQUEST['history']) ? json_decode( stripslashes($_REQUEST['history']) ) : null; if ( $changes ) - $img = image_edit_apply_changes($img, $changes); + $img = image_edit_apply_changes( $img, $changes ); // scale the image - $w = imagesx($img); - $h = imagesy($img); - $ratio = _image_get_preview_ratio($w, $h); + $size = $img->get_size(); + $w = $size['width']; + $h = $size['height']; + + $ratio = _image_get_preview_ratio( $w, $h ); $w2 = $w * $ratio; $h2 = $h * $ratio; - $preview = wp_imagecreatetruecolor($w2, $h2); - imagecopyresampled( $preview, $img, 0, 0, 0, 0, $w2, $h2, $w, $h ); - wp_stream_image($preview, $post->post_mime_type, $post_id); + if ( is_wp_error( $img->resize( $w2, $h2 ) ) ) + return false; - imagedestroy($preview); - imagedestroy($img); - return true; + return wp_stream_image( $img, $post->post_mime_type, $post_id ); } function wp_restore_image($post_id) { @@ -450,14 +555,20 @@ function wp_restore_image($post_id) { return $msg; } -function wp_save_image($post_id) { +/** + * Saves image to post along with enqueued changes + * in $_REQUEST['history'] + * + * @param int $post_id + * @return \stdClass + */ +function wp_save_image( $post_id ) { $return = new stdClass; $success = $delete = $scaled = $nocrop = false; - $post = get_post($post_id); - @ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) ); - $img = load_image_to_edit($post_id, $post->post_mime_type); + $post = get_post( $post_id ); - if ( !is_resource($img) ) { + $img = WP_Image_Editor::get_instance( _load_image_to_edit_path( $post_id, 'full' ) ); + if ( !$img ) { $return->error = esc_js( __('Unable to create new image.') ); return $return; } @@ -468,19 +579,16 @@ function wp_save_image($post_id) { $scale = !empty($_REQUEST['do']) && 'scale' == $_REQUEST['do']; if ( $scale && $fwidth > 0 && $fheight > 0 ) { - $sX = imagesx($img); - $sY = imagesy($img); + $size = $img->get_size(); + $sX = $size['width']; + $sY = $size['height']; // check if it has roughly the same w / h ratio $diff = round($sX / $sY, 2) - round($fwidth / $fheight, 2); if ( -0.1 < $diff && $diff < 0.1 ) { // scale the full size image - $dst = wp_imagecreatetruecolor($fwidth, $fheight); - if ( imagecopyresampled( $dst, $img, 0, 0, 0, 0, $fwidth, $fheight, $sX, $sY ) ) { - imagedestroy($img); - $img = $dst; + if ( $img->resize( $fwidth, $fheight ) ) $scaled = true; - } } if ( !$scaled ) { @@ -551,11 +659,13 @@ function wp_save_image($post_id) { if ( $tag ) $backup_sizes[$tag] = array('width' => $meta['width'], 'height' => $meta['height'], 'file' => $path_parts['basename']); - $success = update_attached_file($post_id, $new_path); + $success = update_attached_file( $post_id, $new_path ); - $meta['file'] = _wp_relative_upload_path($new_path); - $meta['width'] = imagesx($img); - $meta['height'] = imagesy($img); + $meta['file'] = _wp_relative_upload_path( $new_path ); + + $size = $img->get_size(); + $meta['width'] = $size['width']; + $meta['height'] = $size['height']; if ( $success && ('nothumb' == $target || 'all' == $target) ) { $sizes = get_intermediate_image_sizes(); @@ -570,10 +680,12 @@ function wp_save_image($post_id) { $success = $delete = $nocrop = true; } - if ( isset($sizes) ) { + if ( isset( $sizes ) ) { + $_sizes = array(); + foreach ( $sizes as $size ) { $tag = false; - if ( isset($meta['sizes'][$size]) ) { + if ( isset( $meta['sizes'][$size] ) ) { if ( isset($backup_sizes["$size-orig"]) ) { if ( ( !defined('IMAGE_EDIT_OVERWRITE') || !IMAGE_EDIT_OVERWRITE ) && $backup_sizes["$size-orig"]['file'] != $meta['sizes'][$size]['file'] ) $tag = "$size-$suffix"; @@ -586,17 +698,16 @@ function wp_save_image($post_id) { } $crop = $nocrop ? false : get_option("{$size}_crop"); - $resized = image_make_intermediate_size($new_path, get_option("{$size}_size_w"), get_option("{$size}_size_h"), $crop ); - - if ( $resized ) - $meta['sizes'][$size] = $resized; - else - unset($meta['sizes'][$size]); + $_sizes[ $size ] = array( 'width' => get_option("{$size}_size_w"), 'height' => get_option("{$size}_size_h"), 'crop' => $crop ); } + + $meta['sizes'] = $img->multi_resize( $_sizes ); } + unset( $img ); + if ( $success ) { - wp_update_attachment_metadata($post_id, $meta); + wp_update_attachment_metadata( $post_id, $meta ); update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes); if ( $target == 'thumbnail' || $target == 'all' || $target == 'full' ) { @@ -612,11 +723,9 @@ function wp_save_image($post_id) { if ( $delete ) { $delpath = apply_filters('wp_delete_file', $new_path); - @unlink($delpath); + @unlink( $delpath ); } - imagedestroy($img); - $return->msg = esc_js( __('Image saved') ); return $return; } diff --git a/wp-admin/includes/image.php b/wp-admin/includes/image.php index c7638cdbae..2a5123e54d 100644 --- a/wp-admin/includes/image.php +++ b/wp-admin/includes/image.php @@ -22,61 +22,33 @@ * @param string $dst_file Optional. The destination file to write to. * @return string|WP_Error|false New filepath on success, WP_Error or false on failure. */ -function wp_crop_image( $src, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) { - if ( is_numeric( $src ) ) { // Handle int as attachment ID - $src_file = get_attached_file( $src ); +function wp_crop_image( $src_file, $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs = false, $dst_file = false ) { + if ( is_numeric( $src_file ) ) { // Handle int as attachment ID + $src_file = get_attached_file( $src_file ); if ( ! file_exists( $src_file ) ) { // If the file doesn't exist, attempt a url fopen on the src link. // This can occur with certain file replication plugins. - $post = get_post( $src ); - $image_type = $post->post_mime_type; - $src = load_image_to_edit( $src, $post->post_mime_type, 'full' ); - } else { - $size = @getimagesize( $src_file ); - $image_type = ( $size ) ? $size['mime'] : ''; - $src = wp_load_image( $src_file ); + $src_file = _load_image_to_edit_path( $src_file, 'full' ); } - } else { - $size = @getimagesize( $src ); - $image_type = ( $size ) ? $size['mime'] : ''; - $src = wp_load_image( $src ); } - if ( ! is_resource( $src ) ) - return new WP_Error( 'error_loading_image', $src, $src_file ); + $editor = WP_Image_Editor::get_instance( $src_file ); + $src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs ); - $dst = wp_imagecreatetruecolor( $dst_w, $dst_h ); - - if ( $src_abs ) { - $src_w -= $src_x; - $src_h -= $src_y; - } - - if ( function_exists( 'imageantialias' ) ) - imageantialias( $dst, true ); - - imagecopyresampled( $dst, $src, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); - - imagedestroy( $src ); // Free up memory + if ( is_wp_error( $src ) ) + return $src; if ( ! $dst_file ) $dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file ); - if ( 'image/png' != $image_type ) - $dst_file = preg_replace( '/\\.[^\\.]+$/', '.jpg', $dst_file ); - // The directory containing the original file may no longer exist when // using a replication plugin. wp_mkdir_p( dirname( $dst_file ) ); $dst_file = dirname( $dst_file ) . '/' . wp_unique_filename( dirname( $dst_file ), basename( $dst_file ) ); - if ( 'image/png' == $image_type && imagepng( $dst, $dst_file ) ) - return $dst_file; - elseif ( imagejpeg( $dst, $dst_file, apply_filters( 'jpeg_quality', 90, 'wp_crop_image' ) ) ) - return $dst_file; - else - return false; + $result = $editor->save( $dst_file ); + return $dst_file; } /** @@ -121,11 +93,8 @@ function wp_generate_attachment_metadata( $attachment_id, $file ) { $sizes = apply_filters( 'intermediate_image_sizes_advanced', $sizes ); - foreach ($sizes as $size => $size_data ) { - $resized = image_make_intermediate_size( $file, $size_data['width'], $size_data['height'], $size_data['crop'] ); - if ( $resized ) - $metadata['sizes'][$size] = $resized; - } + $editor = WP_Image_Editor::get_instance( $file ); + $metadata['sizes'] = $editor->multi_resize( $sizes ); // fetch additional metadata from exif/iptc $image_meta = wp_read_image_metadata( $file ); diff --git a/wp-includes/class-wp-image-editor-gd.php b/wp-includes/class-wp-image-editor-gd.php new file mode 100644 index 0000000000..94c2eecdc9 --- /dev/null +++ b/wp-includes/class-wp-image-editor-gd.php @@ -0,0 +1,357 @@ +image ) { + // we don't need the original in memory anymore + imagedestroy( $this->image ); + } + } + + /** + * Checks to see if current environment supports GD + * + * @since 3.5.0 + * @access protected + * + * @return boolean + */ + public static function test() { + if ( ! extension_loaded('gd') || ! function_exists('gd_info') ) + return false; + + return true; + } + + /** + * Loads image from $this->file into new GD Resource + * + * @since 3.5 + * @access protected + * + * @return boolean|\WP_Error + */ + protected function load() { + if ( $this->image ) + return true; + + if ( ! file_exists( $this->file ) ) + return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); + + // Set artificially high because GD uses uncompressed images in memory + @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) ); + $this->image = @imagecreatefromstring( file_get_contents( $this->file ) ); + + if ( ! is_resource( $this->image ) ) + return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file ); + + $size = @getimagesize( $this->file ); + if ( ! $size ) + return new WP_Error( 'invalid_image', __('Could not read image size.'), $this->file ); + + $this->update_size( $size[0], $size[1] ); + $this->mime_type = $size['mime']; + + return true; + } + + /** + * Sets or updates current image size + * + * @since 3.5.0 + * @access protected + * + * @param int $width + * @param int $height + */ + protected function update_size( $width = false, $height = false ) { + if ( ! $width ) + $width = imagesx( $this->image ); + + if ( ! $height ) + $height = imagesy( $this->image ); + + return parent::update_size( $width, $height ); + } + + /** + * Checks to see if editor supports mime-type specified + * + * @since 3.5.0 + * @access public + * + * @param string $mime_type + * @return boolean + */ + public static function supports_mime_type( $mime_type ) { + $allowed_mime_types = array( 'image/gif', 'image/png', 'image/jpeg' ); + + return in_array( $mime_type, $allowed_mime_types ); + } + + /** + * Resizes current image. + * Wrapper around _resize, since _resize returns a GD Resource + * + * @param int $max_w + * @param int $max_h + * @param boolean $crop + * @return boolean|WP_Error + */ + public function resize( $max_w, $max_h, $crop = false ) { + if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) + return true; + + $resized = $this->_resize( $max_w, $max_h, $crop ); + + if ( is_resource( $resized ) ) { + imagedestroy( $this->image ); + $this->image = $resized; + return true; + + } elseif ( is_wp_error( $resized ) ) + return $resized; + + return new WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file ); + } + + protected function _resize( $max_w, $max_h, $crop = false ) { + $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); + if ( ! $dims ) { + return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions'), $this->file ); + } + list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; + + $resized = wp_imagecreatetruecolor( $dst_w, $dst_h ); + imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); + + if ( is_resource( $resized ) ) { + $this->update_size( $dst_w, $dst_h ); + return $resized; + } + + return WP_Error( 'image_resize_error', __('Image resize failed.'), $this->file ); + } + + /** + * Processes current image and saves to disk + * multiple sizes from single source. + * + * @param array $sizes { {width, height}, ... } + * @return array + */ + public function multi_resize( $sizes ) { + $metadata = array(); + $orig_size = $this->size; + + foreach ( $sizes as $size => $size_data ) { + $image = $this->_resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); + + if( ! is_wp_error( $image ) ) { + $resized = $this->_save( $image ); + + imagedestroy( $image ); + unset( $resized['path'] ); + + if ( ! is_wp_error( $resized ) && $resized ) + $metadata[$size] = $resized; + } + + $this->size = $orig_size; + } + + return $metadata; + } + + /** + * Crops Image. + * + * @since 3.5.0 + * @access public + * + * @param string|int $src The source file or Attachment ID. + * @param int $src_x The start x position to crop from. + * @param int $src_y The start y position to crop from. + * @param int $src_w The width to crop. + * @param int $src_h The height to crop. + * @param int $dst_w Optional. The destination width. + * @param int $dst_h Optional. The destination height. + * @param int $src_abs Optional. If the source crop points are absolute. + * @return boolean|WP_Error + */ + public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { + // If destination width/height isn't specified, use same as + // width/height from source. + if ( ! $dst_w ) + $dst_w = $src_w; + if ( ! $dst_h ) + $dst_h = $src_h; + + $dst = wp_imagecreatetruecolor( $dst_w, $dst_h ); + + if ( $src_abs ) { + $src_w -= $src_x; + $src_h -= $src_y; + } + + if ( function_exists( 'imageantialias' ) ) + imageantialias( $dst, true ); + + imagecopyresampled( $dst, $this->image, 0, 0, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ); + + if ( is_resource( $dst ) ) { + imagedestroy( $this->image ); + $this->image = $dst; + $this->update_size( $dst_w, $dst_h ); + return true; + } + + return WP_Error( 'image_crop_error', __('Image crop failed.'), $this->file ); + } + + /** + * Rotates current image counter-clockwise by $angle. + * Ported from image-edit.php + * + * @since 3.5.0 + * @access public + * + * @param float $angle + * @return boolean|WP_Error + */ + public function rotate( $angle ) { + if ( function_exists('imagerotate') ) { + $rotated = imagerotate( $this->image, $angle, 0 ); + + if ( is_resource( $rotated ) ) { + imagedestroy( $this->image ); + $this->image = $rotated; + $this->update_size(); + return true; + } + } + return WP_Error( 'image_rotate_error', __('Image rotate failed.'), $this->file ); + } + + /** + * Flips current image + * + * @param boolean $horz Horizonal Flip + * @param boolean $vert Vertical Flip + * @returns boolean|WP_Error + */ + public function flip( $horz, $vert ) { + $w = $this->size['width']; + $h = $this->size['height']; + $dst = wp_imagecreatetruecolor( $w, $h ); + + if ( is_resource( $dst ) ) { + $sx = $vert ? ($w - 1) : 0; + $sy = $horz ? ($h - 1) : 0; + $sw = $vert ? -$w : $w; + $sh = $horz ? -$h : $h; + + if ( imagecopyresampled( $dst, $this->image, 0, 0, $sx, $sy, $w, $h, $sw, $sh ) ) { + imagedestroy( $this->image ); + $this->image = $dst; + return true; + } + } + return WP_Error( 'image_flip_error', __('Image flip failed.'), $this->file ); + } + + /** + * Saves current in-memory image to file + * + * @param string $destfilename + * @param string $mime_type + * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string} + */ + public function save( $filename = null, $mime_type = null ) { + $saved = $this->_save( $this->image, $filename, $mime_type ); + + if ( ! is_wp_error( $saved ) ) { + $this->file = $saved['path']; + $this->mime_type = $saved['mime-type']; + } + + return $saved; + } + + protected function _save( $image, $filename = null, $mime_type = null ) { + list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); + + if ( ! $filename ) + $filename = $this->generate_filename( null, null, $extension ); + + if ( 'image/gif' == $mime_type ) { + if ( ! $this->make_image( $filename, 'imagegif', array( $image, $filename ) ) ) + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); + } + elseif ( 'image/png' == $mime_type ) { + // convert from full colors to index colors, like original PNG. + if ( function_exists('imageistruecolor') && ! imageistruecolor( $image ) ) + imagetruecolortopalette( $image, false, imagecolorstotal( $image ) ); + + if ( ! $this->make_image( $filename, 'imagepng', array( $image, $filename ) ) ) + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); + } + elseif ( 'image/jpeg' == $mime_type ) { + if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, apply_filters( 'jpeg_quality', $this->quality, 'image_resize' ) ) ) ) + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); + } + else { + return new WP_Error( 'image_save_error', __('Image Editor Save Failed') ); + } + + // Set correct file permissions + $stat = stat( dirname( $filename ) ); + $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits + @ chmod( $filename, $perms ); + + return array( + 'path' => $filename, + 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), + 'width' => $this->size['width'], + 'height' => $this->size['height'], + 'mime-type'=> $mime_type, + ); + } + + /** + * Returns stream of current image + * + * @param string $mime_type + */ + public function stream( $mime_type = null ) { + list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type ); + + switch ( $mime_type ) { + case 'image/png': + header( 'Content-Type: image/png' ); + return imagepng( $this->image ); + case 'image/gif': + header( 'Content-Type: image/gif' ); + return imagegif( $this->image ); + default: + header( 'Content-Type: image/jpeg' ); + return imagejpeg( $this->image, null, $this->quality ); + } + } +} \ No newline at end of file diff --git a/wp-includes/class-wp-image-editor-imagick.php b/wp-includes/class-wp-image-editor-imagick.php new file mode 100644 index 0000000000..6370a53c2a --- /dev/null +++ b/wp-includes/class-wp-image-editor-imagick.php @@ -0,0 +1,410 @@ +image ) { + // we don't need the original in memory anymore + $this->image->clear(); + $this->image->destroy(); + } + } + + /** + * Checks to see if current environment supports Imagick + * + * @since 3.5.0 + * @access protected + * + * @return boolean + */ + public static function test() { + if ( ! extension_loaded( 'imagick' ) ) + return false; + + return true; + } + + /** + * Loads image from $this->file into new Imagick Object + * + * @since 3.5.0 + * @access protected + * + * @return boolean|WP_Error True if loaded; WP_Error on failure. + */ + protected function load() { + if ( $this->image ) + return true; + + if ( ! file_exists( $this->file ) ) + return new WP_Error( 'error_loading_image', __('File doesn’t exist?'), $this->file ); + + try { + $this->image = new Imagick( $this->file ); + + if( ! $this->image->valid() ) + return new WP_Error( 'invalid_image', __('File is not an image.'), $this->file); + + // Select the first frame to handle animated GIFs properly + $this->image->setIteratorIndex(0); + $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() ); + } + catch ( Exception $e ) { + return new WP_Error( 'invalid_image', $e->getMessage(), $this->file ); + } + + $updated_size = $this->update_size(); + if ( is_wp_error( $updated_size ) ) + return $updated_size; + + return $this->set_quality(); + } + + /** + * Sets Image Compression quality on a 1-100% scale. + * + * @since 3.5.0 + * @access public + * + * @param int $quality Compression Quality. Range: [1,100] + * @return boolean|WP_Error + */ + public function set_quality( $quality = null ) { + if ( !$quality ) + $quality = $this->quality; + + try { + if( 'image/jpeg' == $this->mime_type ) { + $this->image->setImageCompressionQuality( apply_filters( 'jpeg_quality', $quality, 'image_resize' ) ); + $this->image->setImageCompression( imagick::COMPRESSION_JPEG ); + } + else { + $this->image->setImageCompressionQuality( $quality ); + } + } + catch ( Exception $e ) { + return new WP_Error( 'image_quality_error', $e->getMessage() ); + } + + return parent::set_quality( $quality ); + } + + /** + * Sets or updates current image size + * + * @since 3.5.0 + * @access protected + * + * @param int $width + * @param int $height + */ + protected function update_size( $width = null, $height = null ) { + $size = null; + if ( !$width || !$height ) { + try { + $size = $this->image->getImageGeometry(); + } + catch ( Exception $e ) { + return new WP_Error( 'invalid_image', __('Could not read image size'), $this->file ); + } + } + + if ( ! $width ) + $width = $size['width']; + + if ( ! $height ) + $height = $size['height']; + + return parent::update_size( $width, $height ); + } + + /** + * Checks to see if editor supports mime-type specified + * + * @since 3.5.0 + * @access public + * + * @param string $mime_type + * @return boolean + */ + public static function supports_mime_type( $mime_type = null ) { + if ( ! $mime_type ) + return false; + + $imagick_extension = strtoupper( self::get_extension( $mime_type ) ); + + try { + return ( (bool) Imagick::queryFormats( $imagick_extension ) ); + } + catch ( Exception $e ) { + return false; + } + } + + /** + * Resizes current image. + * + * @param int $max_w + * @param int $max_h + * @param boolean $crop + * @return boolean|WP_Error + */ + public function resize( $max_w, $max_h, $crop = false ) { + if ( ( $this->size['width'] == $max_w ) && ( $this->size['height'] == $max_h ) ) + return true; + + $dims = image_resize_dimensions( $this->size['width'], $this->size['height'], $max_w, $max_h, $crop ); + if ( ! $dims ) + return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') ); + list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims; + + if ( $crop ) { + return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h ); + } + + try { + /** + * @TODO: Thumbnail is more efficient, given a newer version of Imagemagick. + * $this->image->thumbnailImage( $dst_w, $dst_h ); + */ + $this->image->scaleImage( $dst_w, $dst_h ); + } + catch ( Exception $e ) { + return new WP_Error( 'image_resize_error', $e->getMessage() ); + } + + return $this->update_size( $dst_w, $dst_h ); + } + + /** + * Processes current image and saves to disk + * multiple sizes from single source. + * + * @param array $sizes + * @return array + */ + public function multi_resize( $sizes ) { + $metadata = array(); + $orig_size = $this->size; + $orig_image = $this->image->getImage(); + + foreach ( $sizes as $size => $size_data ) { + if ( ! $this->image ) + $this->image = $orig_image->getImage(); + + $resize_result = $this->resize( $size_data['width'], $size_data['height'], $size_data['crop'] ); + + if( ! is_wp_error( $resize_result ) ) { + $resized = $this->_save( $this->image ); + + $this->image->clear(); + $this->image->destroy(); + $this->image = null; + unset( $resized['path'] ); + + if ( ! is_wp_error( $resized ) && $resized ) + $metadata[$size] = $resized; + } + + $this->size = $orig_size; + } + + $this->image = $orig_image; + + return $metadata; + } + + /** + * Crops Image. + * + * @since 3.5.0 + * @access public + * + * @param string|int $src The source file or Attachment ID. + * @param int $src_x The start x position to crop from. + * @param int $src_y The start y position to crop from. + * @param int $src_w The width to crop. + * @param int $src_h The height to crop. + * @param int $dst_w Optional. The destination width. + * @param int $dst_h Optional. The destination height. + * @param int $src_abs Optional. If the source crop points are absolute. + * @return boolean|WP_Error + */ + public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ) { + // Not sure this is compatible. + if ( $src_abs ) { + $src_w -= $src_x; + $src_h -= $src_y; + } + + try { + $this->image->cropImage( $src_w, $src_h, $src_x, $src_y ); + $this->image->setImagePage( $src_w, $src_h, 0, 0); + + if ( $dst_w || $dst_h ) { + // If destination width/height isn't specified, use same as + // width/height from source. + if ( ! $dst_w ) + $dst_w = $src_w; + if ( ! $dst_h ) + $dst_h = $src_h; + + $this->image->scaleImage( $dst_w, $dst_h ); + return $this->update_size( $dst_w, $dst_h ); + } + } + catch ( Exception $e ) { + return new WP_Error( 'image_crop_error', $e->getMessage() ); + } + return $this->update_size( $src_w, $src_h ); + } + + /** + * Rotates current image counter-clockwise by $angle. + * + * @since 3.5.0 + * @access public + * + * @param float $angle + * @return boolean|WP_Error + */ + public function rotate( $angle ) { + /** + * $angle is 360-$angle because Imagick rotates clockwise + * (GD rotates counter-clockwise) + */ + try { + $this->image->rotateImage( new ImagickPixel('none'), 360-$angle ); + } + catch ( Exception $e ) { + return new WP_Error( 'image_rotate_error', $e->getMessage() ); + } + return $this->update_size(); + } + + /** + * Flips current image + * + * @since 3.5.0 + * @access public + * + * @param boolean $horz Horizontal Flip + * @param boolean $vert Vertical Flip + * @returns boolean + */ + public function flip( $horz, $vert ) { + try { + if ( $horz ) + $this->image->flipImage(); + + if ( $vert ) + $this->image->flopImage(); + } + catch ( Exception $e ) { + return new WP_Error( 'image_flip_error', $e->getMessage() ); + } + return true; + } + + /** + * Saves current image to file + * + * @param string $destfilename + * @param string $mime_type + * @return array|WP_Error {'path'=>string, 'file'=>string, 'width'=>int, 'height'=>int, 'mime-type'=>string} + */ + public function save( $destfilename = null, $mime_type = null ) { + $saved = $this->_save( $this->image, $destfilename, $mime_type ); + + if ( ! is_wp_error( $saved ) ) { + $this->file = $saved['path']; + $this->mime_type = $saved['mime-type']; + + try { + $this->image->setImageFormat( strtoupper( $this->get_extension( $this->mime_type ) ) ); + } + catch ( Exception $e ) { + return new WP_Error( 'image_save_error', $e->getMessage(), $this->file ); + } + } + + return $saved; + } + + protected function _save( $image, $filename = null, $mime_type = null ) { + list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); + + if ( ! $filename ) + $filename = $this->generate_filename( null, null, $extension ); + + try { + // Store initial Format + $orig_format = $this->image->getImageFormat(); + + $this->image->setImageFormat( strtoupper( $this->get_extension( $mime_type ) ) ); + $this->make_image( $filename, array( $image, 'writeImage' ), array( $filename ) ); + + // Reset original Format + $this->image->setImageFormat( $orig_format ); + } + catch ( Exception $e ) { + return new WP_Error( 'image_save_error', $e->getMessage(), $filename ); + } + + // Set correct file permissions + $stat = stat( dirname( $filename ) ); + $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits + @ chmod( $filename, $perms ); + + return array( + 'path' => $filename, + 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), + 'width' => $this->size['width'], + 'height' => $this->size['height'], + 'mime-type' => $mime_type, + ); + } + + /** + * Streams current image to browser + * + * @param string $mime_type + * @return boolean|WP_Error + */ + public function stream( $mime_type = null ) { + list( $filename, $extension, $mime_type ) = $this->get_output_format( null, $mime_type ); + + try { + // Temporarily change format for stream + $this->image->setImageFormat( strtoupper( $extension ) ); + + // Output stream of image content + header( "Content-Type: $mime_type" ); + print $this->image->getImageBlob(); + + // Reset Image to original Format + $this->image->setImageFormat( $this->get_extension( $this->mime_type ) ); + } + catch ( Exception $e ) { + return new WP_Error( 'image_stream_error', $e->getMessage() ); + } + + return true; + } +} \ No newline at end of file diff --git a/wp-includes/class-wp-image-editor.php b/wp-includes/class-wp-image-editor.php new file mode 100644 index 0000000000..27df9195c7 --- /dev/null +++ b/wp-includes/class-wp-image-editor.php @@ -0,0 +1,324 @@ +file = $filename; + } + + /** + * Returns a WP_Image_Editor instance and loads file into it. + * + * @since 3.5.0 + * @access public + * + * @param string $path Path to File to Load + * @return WP_Image_Editor|WP_Error|boolean + */ + public final static function get_instance( $path = null ) { + $implementation = apply_filters( 'image_editor_class', self::choose_implementation(), $path ); + + if ( $implementation ) { + $editor = new $implementation( $path ); + $loaded = $editor->load(); + + if ( is_wp_error ( $loaded ) ) + return $loaded; + + return $editor; + } + + return false; + } + + /** + * Tests which editors are capable of supporting the request. + * + * @since 3.5.0 + * @access private + * + * @return string|bool Class name for the first editor that claims to support the request. False if no editor claims to support the request. + */ + private final static function choose_implementation() { + + if ( null === self::$implementation ) { + $request_order = apply_filters( 'wp_editors', array( 'imagick', 'gd' ) ); + + // Loop over each editor on each request looking for one which will serve this request's needs + foreach ( $request_order as $editor ) { + $class = 'WP_Image_Editor_' . $editor; + + // Check to see if this editor is a possibility, calls the editor statically + if ( ! call_user_func( array( $class, 'test' ) ) ) + continue; + + self::$implementation = $class; + break; + } + } + return self::$implementation; + } + + abstract public static function test(); // returns bool + abstract protected function load(); // returns bool|WP_Error + abstract public static function supports_mime_type( $mime_type ); // returns bool + abstract public function resize( $max_w, $max_h, $crop = false ); + abstract public function multi_resize( $sizes ); + abstract public function crop( $src_x, $src_y, $src_w, $src_h, $dst_w = null, $dst_h = null, $src_abs = false ); + abstract public function rotate( $angle ); + abstract public function flip( $horz, $vert ); + abstract public function save( $destfilename = null, $mime_type = null ); + abstract public function stream( $mime_type = null ); + + /** + * Gets dimensions of image + * + * @since 3.5.0 + * @access public + * + * @return array {'width'=>int, 'height'=>int} + */ + public function get_size() { + return $this->size; + } + + /** + * Sets current image size + * + * @since 3.5.0 + * @access protected + * + * @param int $width + * @param int $height + */ + protected function update_size( $width = null, $height = null ) { + $this->size = array( + 'width' => $width, + 'height' => $height + ); + return true; + } + + /** + * Sets Image Compression quality on a 1-100% scale. + * + * @since 3.5.0 + * @access public + * + * @param int $quality Compression Quality. Range: [1,100] + * @return boolean + */ + public function set_quality( $quality ) { + $this->quality = apply_filters( 'wp_editor_set_quality', $quality ); + + return ( (bool) $this->quality ); + } + + /** + * Returns preferred mime-type and extension based on provided + * file's extension and mime, or current file's extension and mime. + * + * Will default to $this->default_mime_type if requested is not supported. + * + * Provides corrected filename only if filename is provided. + * + * @since 3.5.0 + * @access protected + * + * @param string $filename + * @param type $mime_type + * @return array { filename|null, extension, mime-type } + */ + protected function get_output_format( $filename = null, $mime_type = null ) { + $new_ext = $file_ext = null; + $file_mime = null; + + // By default, assume specified type takes priority + if ( $mime_type ) { + $new_ext = $this->get_extension( $mime_type ); + } + + if ( $filename ) { + $file_ext = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ); + $file_mime = $this->get_mime_type( $file_ext ); + } + else { + // If no file specified, grab editor's current extension and mime-type. + $file_ext = strtolower( pathinfo( $this->file, PATHINFO_EXTENSION ) ); + $file_mime = $this->mime_type; + } + + // Check to see if specified mime-type is the same as type implied by + // file extension. If so, prefer extension from file. + if ( ! $mime_type || ( $file_mime == $mime_type ) ) { + $mime_type = $file_mime; + $new_ext = $file_ext; + } + + // Double-check that the mime-type selected is supported by the editor. + // If not, choose a default instead. + if ( ! $this->supports_mime_type( $mime_type ) ) { + $mime_type = apply_filters( 'image_editor_default_mime_type', $this->default_mime_type ); + $new_ext = $this->get_extension( $mime_type ); + } + + if ( $filename ) { + $ext = ''; + $info = pathinfo( $filename ); + $dir = $info['dirname']; + + if( isset( $info['extension'] ) ) + $ext = $info['extension']; + + $filename = trailingslashit( $dir ) . wp_basename( $filename, ".$ext" ) . ".{$new_ext}"; + } + + return array( $filename, $new_ext, $mime_type ); + } + + /** + * Builds an output filename based on current file, and adding proper suffix + * + * @since 3.5.0 + * @access public + * + * @param string $suffix + * @param string $dest_path + * @param string $extension + * @return string filename + */ + public function generate_filename( $suffix = null, $dest_path = null, $extension = null ) { + // $suffix will be appended to the destination filename, just before the extension + if ( ! $suffix ) + $suffix = $this->get_suffix(); + + $info = pathinfo( $this->file ); + $dir = $info['dirname']; + $ext = $info['extension']; + + $name = wp_basename( $this->file, ".$ext" ); + $new_ext = strtolower( $extension ? $extension : $ext ); + + if ( ! is_null( $dest_path ) && $_dest_path = realpath( $dest_path ) ) + $dir = $_dest_path; + + return trailingslashit( $dir ) . "{$name}-{$suffix}.{$new_ext}"; + } + + /** + * Builds and returns proper suffix for file based on height and width. + * + * @since 3.5.0 + * @access public + * + * @return string suffix + */ + public function get_suffix() { + if ( ! $this->get_size() ) + return false; + + return "{$this->size['width']}x{$this->size['height']}"; + } + + /** + * Either calls editor's save function or handles file as a stream. + * + * @since 3.5.0 + * @access protected + * + * @param string|stream $filename + * @param callable $function + * @param array $arguments + * @return boolean + */ + protected function make_image( $filename, $function, $arguments ) { + $dst_file = $filename; + + if ( $stream = wp_is_stream( $filename ) ) { + $filename = null; + ob_start(); + } + + $result = call_user_func_array( $function, $arguments ); + + if( $result && $stream ) { + $contents = ob_get_contents(); + + $fp = fopen( $dst_file, 'w' ); + + if( ! $fp ) + return false; + + fwrite( $fp, $contents ); + fclose( $fp ); + } + + if( $stream ) { + ob_end_clean(); + } + + return $result; + } + + /** + * Returns first matched mime-type from extension, + * as mapped from wp_get_mime_types() + * + * @since 3.5.0 + * @access protected + * + * @param string $extension + * @return string|boolean + */ + protected static function get_mime_type( $extension = null ) { + if ( ! $extension ) + return false; + + $mime_types = wp_get_mime_types(); + $extensions = array_keys( $mime_types ); + + foreach( $extensions as $_extension ) { + if( preg_match("/{$extension}/i", $_extension ) ) { + return $mime_types[ $_extension ]; + } + } + + return false; + } + + /** + * Returns first matched extension from Mime-type, + * as mapped from wp_get_mime_types() + * + * @since 3.5.0 + * @access protected + * + * @param string $mime_type + * @return string|boolean + */ + protected static function get_extension( $mime_type = null ) { + $extensions = explode( '|', array_search( $mime_type, wp_get_mime_types() ) ); + + if ( empty( $extensions[0] ) ) + return false; + + return $extensions[0]; + } +} \ No newline at end of file diff --git a/wp-includes/deprecated.php b/wp-includes/deprecated.php index f5890179a3..a9744e89aa 100644 --- a/wp-includes/deprecated.php +++ b/wp-includes/deprecated.php @@ -3205,6 +3205,83 @@ function _get_post_ancestors( &$post ) { _deprecated_function( __FUNCTION__, '3.5' ); } +/** + * Load an image from a string, if PHP supports it. + * + * @since 2.1.0 + * @deprecated 3.5.0 + * @see WP_Image_Editor + * + * @param string $file Filename of the image to load. + * @return resource The resulting image resource on success, Error string on failure. + */ +function wp_load_image( $file ) { + _deprecated_function( __FUNCTION__, '3.5', 'WP_Image_Editor' ); + + if ( is_numeric( $file ) ) + $file = get_attached_file( $file ); + + if ( ! file_exists( $file ) ) + return sprintf(__('File “%s” doesn’t exist?'), $file); + + if ( ! function_exists('imagecreatefromstring') ) + return __('The GD image library is not installed.'); + + // Set artificially high because GD uses uncompressed images in memory + @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) ); + $image = imagecreatefromstring( file_get_contents( $file ) ); + + if ( !is_resource( $image ) ) + return sprintf(__('File “%s” is not an image.'), $file); + + return $image; +} + +/** + * Scale down an image to fit a particular size and save a new copy of the image. + * + * The PNG transparency will be preserved using the function, as well as the + * image type. If the file going in is PNG, then the resized image is going to + * be PNG. The only supported image types are PNG, GIF, and JPEG. + * + * Some functionality requires API to exist, so some PHP version may lose out + * support. This is not the fault of WordPress (where functionality is + * downgraded, not actual defects), but of your PHP version. + * + * @since 2.5.0 + * @deprecated 3.5.0 + * @see WP_Image_Editor + * + * @param string $file Image file path. + * @param int $max_w Maximum width to resize to. + * @param int $max_h Maximum height to resize to. + * @param bool $crop Optional. Whether to crop image or resize. + * @param string $suffix Optional. File suffix. + * @param string $dest_path Optional. New image file path. + * @param int $jpeg_quality Optional, default is 90. Image quality percentage. + * @return mixed WP_Error on failure. String with new destination path. + */ +function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 90 ) { + _deprecated_function( __FUNCTION__, '3.5', 'WP_Image_Editor' ); + + $editor = WP_Image_Editor::get_instance( $file ); + if ( is_wp_error( $editor ) ) + return $editor; + $editor->set_quality( $jpeg_quality ); + + $resized = $editor->resize( $max_w, $max_h, $crop ); + if ( is_wp_error( $resized ) ) + return $resized; + + $dest_file = $editor->generate_filename( $suffix, $dest_path ); + $saved = $editor->save( $dest_file ); + + if ( is_wp_error( $saved ) ) + return $saved; + + return $dest_file; +} + /** * Retrieve a single post, based on post ID. * diff --git a/wp-includes/functions.php b/wp-includes/functions.php index 98f52bcf9d..91700a07ed 100644 --- a/wp-includes/functions.php +++ b/wp-includes/functions.php @@ -1295,9 +1295,21 @@ function wp_get_original_referer() { * @return bool Whether the path was created. True if path already exists. */ function wp_mkdir_p( $target ) { + $wrapper = null; + + // strip the protocol + if( wp_is_stream( $target ) ) { + list( $wrapper, $target ) = explode( '://', $target, 2 ); + } + // from php.net/mkdir user contributed notes $target = str_replace( '//', '/', $target ); + // put the wrapper back on the target + if( $wrapper !== null ) { + $target = $wrapper . '://' . $target; + } + // safe mode fails with a trailing slash under certain PHP versions. $target = rtrim($target, '/'); // Use rtrim() instead of untrailingslashit to avoid formatting.php dependency. if ( empty($target) ) @@ -3749,6 +3761,19 @@ function _device_can_upload() { return true; } +/** + * Test if a given path is a stream URL + * + * @param string $path The resource path or URL + * @return bool True if the path is a stream URL + */ +function wp_is_stream( $path ) { + $wrappers = stream_get_wrappers(); + $wrappers_re = '(' . join('|', $wrappers) . ')'; + + return preg_match( "!^$wrappers_re://!", $path ) === 1; +} + /** * Test if the supplied date is valid for the Gregorian calendar * diff --git a/wp-includes/media.php b/wp-includes/media.php index f36c81c5b8..1fc438385c 100644 --- a/wp-includes/media.php +++ b/wp-includes/media.php @@ -235,34 +235,6 @@ function get_image_tag($id, $alt, $title, $align, $size='medium') { return $html; } -/** - * Load an image from a string, if PHP supports it. - * - * @since 2.1.0 - * - * @param string $file Filename of the image to load. - * @return resource The resulting image resource on success, Error string on failure. - */ -function wp_load_image( $file ) { - if ( is_numeric( $file ) ) - $file = get_attached_file( $file ); - - if ( ! file_exists( $file ) ) - return sprintf(__('File “%s” doesn’t exist?'), $file); - - if ( ! function_exists('imagecreatefromstring') ) - return __('The GD image library is not installed.'); - - // Set artificially high because GD uses uncompressed images in memory - @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) ); - $image = imagecreatefromstring( file_get_contents( $file ) ); - - if ( !is_resource( $image ) ) - return sprintf(__('File “%s” is not an image.'), $file); - - return $image; -} - /** * Calculates the new dimensions for a downsampled image. * @@ -392,92 +364,6 @@ function image_resize_dimensions($orig_w, $orig_h, $dest_w, $dest_h, $crop = fal } -/** - * Scale down an image to fit a particular size and save a new copy of the image. - * - * The PNG transparency will be preserved using the function, as well as the - * image type. If the file going in is PNG, then the resized image is going to - * be PNG. The only supported image types are PNG, GIF, and JPEG. - * - * Some functionality requires API to exist, so some PHP version may lose out - * support. This is not the fault of WordPress (where functionality is - * downgraded, not actual defects), but of your PHP version. - * - * @since 2.5.0 - * - * @param string $file Image file path. - * @param int $max_w Maximum width to resize to. - * @param int $max_h Maximum height to resize to. - * @param bool $crop Optional. Whether to crop image or resize. - * @param string $suffix Optional. File suffix. - * @param string $dest_path Optional. New image file path. - * @param int $jpeg_quality Optional, default is 90. Image quality percentage. - * @return mixed WP_Error on failure. String with new destination path. - */ -function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $dest_path = null, $jpeg_quality = 90 ) { - - $image = wp_load_image( $file ); - if ( !is_resource( $image ) ) - return new WP_Error( 'error_loading_image', $image, $file ); - - $size = @getimagesize( $file ); - if ( !$size ) - return new WP_Error('invalid_image', __('Could not read image size'), $file); - list($orig_w, $orig_h, $orig_type) = $size; - - $dims = image_resize_dimensions($orig_w, $orig_h, $max_w, $max_h, $crop); - if ( !$dims ) - return new WP_Error( 'error_getting_dimensions', __('Could not calculate resized image dimensions') ); - list($dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h) = $dims; - - $newimage = wp_imagecreatetruecolor( $dst_w, $dst_h ); - - imagecopyresampled( $newimage, $image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h); - - // convert from full colors to index colors, like original PNG. - if ( IMAGETYPE_PNG == $orig_type && function_exists('imageistruecolor') && !imageistruecolor( $image ) ) - imagetruecolortopalette( $newimage, false, imagecolorstotal( $image ) ); - - // we don't need the original in memory anymore - imagedestroy( $image ); - - // $suffix will be appended to the destination filename, just before the extension - if ( !$suffix ) - $suffix = "{$dst_w}x{$dst_h}"; - - $info = pathinfo($file); - $dir = $info['dirname']; - $ext = $info['extension']; - $name = wp_basename($file, ".$ext"); - - if ( !is_null($dest_path) and $_dest_path = realpath($dest_path) ) - $dir = $_dest_path; - $destfilename = "{$dir}/{$name}-{$suffix}.{$ext}"; - - if ( IMAGETYPE_GIF == $orig_type ) { - if ( !imagegif( $newimage, $destfilename ) ) - return new WP_Error('resize_path_invalid', __( 'Resize path invalid' )); - } elseif ( IMAGETYPE_PNG == $orig_type ) { - if ( !imagepng( $newimage, $destfilename ) ) - return new WP_Error('resize_path_invalid', __( 'Resize path invalid' )); - } else { - // all other formats are converted to jpg - if ( 'jpg' != $ext && 'jpeg' != $ext ) - $destfilename = "{$dir}/{$name}-{$suffix}.jpg"; - if ( !imagejpeg( $newimage, $destfilename, apply_filters( 'jpeg_quality', $jpeg_quality, 'image_resize' ) ) ) - return new WP_Error('resize_path_invalid', __( 'Resize path invalid' )); - } - - imagedestroy( $newimage ); - - // Set correct file permissions - $stat = stat( dirname( $destfilename )); - $perms = $stat['mode'] & 0000666; //same permissions as parent folder, strip off the executable bits - @ chmod( $destfilename, $perms ); - - return $destfilename; -} - /** * Resize an image to make a thumbnail or intermediate size. * @@ -493,16 +379,18 @@ function image_resize( $file, $max_w, $max_h, $crop = false, $suffix = null, $de * @param bool $crop Optional, default is false. Whether to crop image to specified height and width or resize. * @return bool|array False, if no image was created. Metadata array on success. */ -function image_make_intermediate_size($file, $width, $height, $crop=false) { +function image_make_intermediate_size( $file, $width, $height, $crop = false ) { if ( $width || $height ) { - $resized_file = image_resize($file, $width, $height, $crop); - if ( !is_wp_error($resized_file) && $resized_file && $info = getimagesize($resized_file) ) { - $resized_file = apply_filters('image_make_intermediate_size', $resized_file); - return array( - 'file' => wp_basename( $resized_file ), - 'width' => $info[0], - 'height' => $info[1], - ); + $editor = WP_Image_Editor::get_instance( $file ); + + if ( is_wp_error( $editor->resize( $width, $height, $crop ) ) ); + return false; + + $resized_file = $editor->save(); + + if ( ! is_wp_error( $resized_file ) && $resized_file ) { + unset( $resized_file['path'] ); + return $resized_file; } } return false; @@ -1047,6 +935,7 @@ function gd_edit_image_support($mime_type) { /** * Create new GD image resource with transparency support + * @TODO: Deprecate if possible. * * @since 2.9.0 * diff --git a/wp-settings.php b/wp-settings.php index 0b81e4d883..470ed9a77a 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -143,6 +143,10 @@ require( ABSPATH . WPINC . '/nav-menu.php' ); require( ABSPATH . WPINC . '/nav-menu-template.php' ); require( ABSPATH . WPINC . '/admin-bar.php' ); +require( ABSPATH . WPINC . '/class-wp-image-editor.php' ); +require( ABSPATH . WPINC . '/class-wp-image-editor-gd.php' ); +require( ABSPATH . WPINC . '/class-wp-image-editor-imagick.php' ); + // Load multisite-specific files. if ( is_multisite() ) { require( ABSPATH . WPINC . '/ms-functions.php' );