Images: enable WebP support.

Add support for uploading, editing and saving WebP images when supported by the server.

Add 'image/webp' to supported mime types. Correctly identify WebP images and sizes even when PHP doesn't support WebP. Resize uploaded WebP files (when supported) and use for front end markup.

Props markoheijne, blobfolio, Clorith, joemcgill, atjn, desrosj, spacedmonkey, marylauc, mikeschroder, hellofromtonya, flixos90.
Fixes #35725.


Built from https://develop.svn.wordpress.org/trunk@50810


git-svn-id: http://core.svn.wordpress.org/trunk@50419 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Adam Silverstein 2021-05-04 14:44:58 +00:00
parent e7e4b84263
commit 524030edfa
16 changed files with 240 additions and 20 deletions

View File

@ -306,6 +306,12 @@ function wp_stream_image( $image, $mime_type, $attachment_id ) {
case 'image/gif':
header( 'Content-Type: image/gif' );
return imagegif( $image );
case 'image/webp':
if ( function_exists( 'imagewebp' ) ) {
header( 'Content-Type: image/webp' );
return imagewebp( $image, null, 90 );
}
return false;
default:
return false;
}
@ -391,6 +397,11 @@ function wp_save_image_file( $filename, $image, $mime_type, $post_id ) {
return imagepng( $image, $filename );
case 'image/gif':
return imagegif( $image, $filename );
case 'image/webp':
if ( function_exists( 'imagewebp' ) ) {
return imagewebp( $image, $filename );
}
return false;
default:
return false;
}

View File

@ -517,6 +517,9 @@ function wp_generate_attachment_metadata( $attachment_id, $file ) {
case 'image/png':
$ext = '.png';
break;
case 'image/webp':
$ext = '.webp';
break;
}
$basename = str_replace( '.', '-', wp_basename( $file ) ) . '-image' . $ext;
$uploaded = wp_upload_bits( $basename, '', $metadata['image']['data'] );
@ -913,7 +916,7 @@ function file_is_valid_image( $path ) {
* @return bool True if suitable, false if not suitable.
*/
function file_is_displayable_image( $path ) {
$displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO );
$displayable_image_types = array( IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_BMP, IMAGETYPE_ICO, IMAGETYPE_WEBP ); // phpcs:ignore PHPCompatibility.Constants.NewConstants.imagetype_webpFound
$info = wp_getimagesize( $path );
if ( empty( $info ) ) {
@ -963,6 +966,12 @@ function load_image_to_edit( $attachment_id, $mime_type, $size = 'full' ) {
case 'image/gif':
$image = imagecreatefromgif( $filepath );
break;
case 'image/webp':
$image = false;
if ( function_exists( 'imagecreatefromwebp' ) ) {
$image = imagecreatefromwebp( $filepath );
}
break;
default:
$image = false;
break;

View File

@ -993,7 +993,7 @@ function wp_media_upload_handler() {
function media_sideload_image( $file, $post_id = 0, $desc = null, $return = 'html' ) {
if ( ! empty( $file ) ) {
$allowed_extensions = array( 'jpg', 'jpeg', 'jpe', 'png', 'gif' );
$allowed_extensions = array( 'jpg', 'jpeg', 'jpe', 'png', 'gif', 'webp' );
/**
* Filters the list of allowed file extensions when sideloading an image from a URL.

View File

@ -1215,6 +1215,7 @@ We hope you enjoy your new site. Thanks!
'jpeg',
'png',
'gif',
'webp',
// Video.
'mov',
'avi',

View File

@ -69,6 +69,8 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
return ( $image_types & IMG_PNG ) != 0;
case 'image/gif':
return ( $image_types & IMG_GIF ) != 0;
case 'image/webp':
return ( $image_types & IMG_WEBP ) != 0; // phpcs:ignore PHPCompatibility.Constants.NewConstants.img_webpFound
}
return false;
@ -99,7 +101,15 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
return new WP_Error( 'error_loading_image', __( 'File doesn’t exist?' ), $this->file );
}
$this->image = @imagecreatefromstring( $file_contents );
// WebP may not work with imagecreatefromstring().
if (
function_exists( 'imagecreatefromwebp' ) &&
( 'image/webp' === wp_get_image_mime( $this->file ) )
) {
$this->image = @imagecreatefromwebp( $this->file );
} else {
$this->image = @imagecreatefromstring( $file_contents );
}
if ( ! is_gd_image( $this->image ) ) {
return new WP_Error( 'invalid_image', __( 'File is not an image.' ), $this->file );
@ -459,6 +469,10 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
if ( ! $this->make_image( $filename, 'imagejpeg', array( $image, $filename, $this->get_quality() ) ) ) {
return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) );
}
} elseif ( 'image/webp' == $mime_type ) {
if ( ! function_exists( 'imagewebp' ) || ! $this->make_image( $filename, 'imagewebp', array( $image, $filename, $this->get_quality() ) ) ) {
return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) );
}
} else {
return new WP_Error( 'image_save_error', __( 'Image Editor Save Failed' ) );
}
@ -502,6 +516,12 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
case 'image/gif':
header( 'Content-Type: image/gif' );
return imagegif( $this->image );
case 'image/webp':
if ( function_exists( 'imagewebp' ) ) {
header( 'Content-Type: image/webp' );
return imagewebp( $this->image, null, $this->get_quality() );
}
// Fall back to the default if webp isn't supported.
default:
header( 'Content-Type: image/jpeg' );
return imagejpeg( $this->image, null, $this->get_quality() );

View File

@ -197,19 +197,30 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
}
try {
if ( 'image/jpeg' === $this->mime_type ) {
$this->image->setImageCompressionQuality( $quality );
$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
} else {
$this->image->setImageCompressionQuality( $quality );
switch ( $this->mime_type ) {
case 'image/jpeg':
$this->image->setImageCompressionQuality( $quality );
$this->image->setImageCompression( imagick::COMPRESSION_JPEG );
break;
case 'image/webp':
if ( _wp_webp_is_lossy( $this->file ) ) {
$this->image->setImageCompressionQuality( $quality );
} else {
// Use WebP lossless settings.
$this->image->setImageCompressionQuality( 100 );
$this->image->setOption( 'webp:lossless', 'true' );
}
break;
default:
$this->image->setImageCompressionQuality( $quality );
}
} catch ( Exception $e ) {
return new WP_Error( 'image_quality_error', $e->getMessage() );
}
return true;
}
/**
* Sets or updates current image size.
*

View File

@ -1141,7 +1141,7 @@ final class WP_Theme implements ArrayAccess {
return false;
}
foreach ( array( 'png', 'gif', 'jpg', 'jpeg' ) as $ext ) {
foreach ( array( 'png', 'gif', 'jpg', 'jpeg', 'webp' ) as $ext ) {
if ( file_exists( $this->get_stylesheet_directory() . "/screenshot.$ext" ) ) {
$this->cache_add( 'screenshot', 'screenshot.' . $ext );
if ( 'relative' === $uri ) {

View File

@ -370,3 +370,11 @@ if ( ! function_exists( 'is_iterable' ) ) {
return ( is_array( $var ) || $var instanceof Traversable );
}
}
// WebP constants may not be defined, even in cases where the format is supported.
if ( ! defined( 'IMAGETYPE_WEBP' ) ) {
define( 'IMAGETYPE_WEBP', 18 );
}
if ( ! defined( 'IMG_WEBP' ) ) {
define( 'IMG_WEBP', IMAGETYPE_WEBP ); // phpcs:ignore PHPCompatibility.Constants.NewConstants.imagetype_webpFound
}

View File

@ -91,7 +91,7 @@ class WP_Customize_Media_Control extends WP_Customize_Control {
// Fake an attachment model - needs all fields used by template.
// Note that the default value must be a URL, NOT an attachment ID.
$ext = substr( $this->setting->default, -3 );
$type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp' ), true ) ? 'image' : 'document';
$type = in_array( $ext, array( 'jpg', 'png', 'gif', 'bmp', 'webp' ), true ) ? 'image' : 'document';
$default_attachment = array(
'id' => 1,

View File

@ -3340,6 +3340,8 @@ function gd_edit_image_support($mime_type) {
return (imagetypes() & IMG_PNG) != 0;
case 'image/gif':
return (imagetypes() & IMG_GIF) != 0;
case 'image/webp':
return (imagetypes() & IMG_WEBP) != 0; // phpcs:ignore PHPCompatibility.Constants.NewConstants.img_webpFound
}
} else {
switch( $mime_type ) {
@ -3349,6 +3351,8 @@ function gd_edit_image_support($mime_type) {
return function_exists('imagecreatefrompng');
case 'image/gif':
return function_exists('imagecreatefromgif');
case 'image/webp':
return function_exists('imagecreatefromwebp');
}
}
return false;

View File

@ -3318,7 +3318,7 @@ function translate_smiley( $matches ) {
$matches = array();
$ext = preg_match( '/\.([^.]+)$/', $img, $matches ) ? strtolower( $matches[1] ) : false;
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' );
// Don't convert smilies that aren't images - they're probably emoji.
if ( ! in_array( $ext, $image_exts, true ) ) {

View File

@ -2886,6 +2886,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
'image/gif' => 'gif',
'image/bmp' => 'bmp',
'image/tiff' => 'tif',
'image/webp' => 'webp',
)
);
@ -3063,6 +3064,35 @@ function wp_get_image_mime( $file ) {
} else {
$mime = false;
}
if ( false !== $mime ) {
return $mime;
}
$handle = fopen( $file, 'rb' );
if ( false === $handle ) {
return false;
}
$magic = fread( $handle, 12 );
if ( false === $magic ) {
return false;
}
// Add WebP fallback detection when image library doesn't support WebP.
// Note: detection values come from LibWebP, see
// https://github.com/webmproject/libwebp/blob/master/imageio/image_dec.c#L30
$magic = bin2hex( $magic );
if (
// RIFF.
( 0 === strpos( $magic, '52494646' ) ) &&
// WEBP.
( 16 === strpos( $magic, '57454250' ) )
) {
$mime = 'image/webp';
}
fclose( $handle );
} catch ( Exception $e ) {
$mime = false;
}
@ -3101,6 +3131,7 @@ function wp_get_mime_types() {
'png' => 'image/png',
'bmp' => 'image/bmp',
'tiff|tif' => 'image/tiff',
'webp' => 'image/webp',
'ico' => 'image/x-icon',
'heic' => 'image/heic',
// Video formats.
@ -3222,7 +3253,7 @@ function wp_get_ext_types() {
return apply_filters(
'ext2type',
array(
'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic' ),
'image' => array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'bmp', 'tif', 'tiff', 'ico', 'heic', 'webp' ),
'audio' => array( 'aac', 'ac3', 'aif', 'aiff', 'flac', 'm3a', 'm4a', 'm4b', 'mka', 'mp1', 'mp2', 'mp3', 'ogg', 'oga', 'ram', 'wav', 'wma' ),
'video' => array( '3g2', '3gp', '3gpp', 'asf', 'avi', 'divx', 'dv', 'flv', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'mpv', 'ogm', 'ogv', 'qt', 'rm', 'vob', 'wmv' ),
'document' => array( 'doc', 'docx', 'docm', 'dotm', 'odt', 'pages', 'pdf', 'xps', 'oxps', 'rtf', 'wp', 'wpd', 'psd', 'xcf' ),

View File

@ -4980,6 +4980,7 @@ function wp_show_heic_upload_error( $plupload_settings ) {
* Allows PHP's getimagesize() to be debuggable when necessary.
*
* @since 5.7.0
* @since 5.8.0 Added support for WebP images.
*
* @param string $filename The file path.
* @param array $image_info Optional. Extended image information (passed by reference).
@ -4994,9 +4995,9 @@ function wp_getimagesize( $filename, array &$image_info = null ) {
defined( 'WP_DEBUG' ) && WP_DEBUG
) {
if ( 2 === func_num_args() ) {
return getimagesize( $filename, $image_info );
return _wp_get_image_size( $filename, $image_info );
} else {
return getimagesize( $filename );
return _wp_get_image_size( $filename );
}
}
@ -5011,9 +5012,133 @@ function wp_getimagesize( $filename, array &$image_info = null ) {
*/
if ( 2 === func_num_args() ) {
// phpcs:ignore WordPress.PHP.NoSilencedErrors
return @getimagesize( $filename, $image_info );
return @_wp_get_image_size( $filename, $image_info );
} else {
// phpcs:ignore WordPress.PHP.NoSilencedErrors
return @getimagesize( $filename );
return @_wp_get_image_size( $filename );
}
}
/**
* Extracts meta information about a webp file: width, height and type.
*
* @since 5.8.0
*
* @param [type] $filename Path to a WebP file.
* @return array $webp_info {
* An array of WebP image information.
*
* @type array $size {
* @type int $width Image width.
* @type int $height Image height.
* @type bool $type The WebP type: one of 'lossy', 'lossless' or 'animated-alpha'.
* }
*/
function wp_get_webp_info( $filename ) {
$width = false;
$height = false;
$type = false;
if ( ! 'image/webp' === wp_get_image_mime( $filename ) ) {
return compact( 'width', 'height', 'type' );
}
try {
$handle = fopen( $filename, 'rb' );
if ( $handle ) {
$magic = fread( $handle, 40 );
fclose( $handle );
// Make sure we got enough bytes.
if ( strlen( $magic ) < 40 ) {
return compact( 'width', 'height', 'type' );
}
// The headers are a little different for each of the three formats.
// Header values based on WebP docs, see https://developers.google.com/speed/webp/docs/riff_container.
switch ( substr( $magic, 12, 4 ) ) {
// Lossy WebP.
case 'VP8 ':
$parts = unpack( 'v2', substr( $magic, 26, 4 ) );
$width = (int) ( $parts[1] & 0x3FFF );
$height = (int) ( $parts[2] & 0x3FFF );
$type = 'lossy';
break;
// Lossless WebP.
case 'VP8L':
$parts = unpack( 'C4', substr( $magic, 21, 4 ) );
$width = (int) ( $parts[1] | ( ( $parts[2] & 0x3F ) << 8 ) ) + 1;
$height = (int) ( ( ( $parts[2] & 0xC0 ) >> 6 ) | ( $parts[3] << 2 ) | ( ( $parts[4] & 0x03 ) << 10 ) ) + 1;
$type = 'lossless';
break;
// Animated/alpha WebP.
case 'VP8X':
// Pad 24-bit int.
$width = unpack( 'V', substr( $magic, 24, 3 ) . "\x00" );
$width = (int) ( $width[1] & 0xFFFFFF ) + 1;
// Pad 24-bit int.
$height = unpack( 'V', substr( $magic, 27, 3 ) . "\x00" );
$height = (int) ( $height[1] & 0xFFFFFF ) + 1;
$type = 'animated-alpha';
break;
}
}
} catch ( Exception $e ) {
}
return compact( 'width', 'height', 'type' );
}
/**
* Determines if a passed image is a lossy WebP image.
*
* @since 5.8.0
*
* @param string $filename The file path.
* @return bool Whether the file is a lossy WebP file.
*/
function _wp_webp_is_lossy( $filename ) {
$webp_info = wp_get_webp_info( $filename );
$type = $webp_info['type'];
return $type && 'lossy' === $type;
}
/**
* Gets the image size, with support for WebP images.
*
* @since 5.8.0
* @access private
*
* @param string $filename The file path.
* @param array $imageinfo Extended image information, passed by reference.
* @return array|false Array of image information or false on failure.
*/
function _wp_get_image_size( $filename, &$imageinfo = array() ) {
// Try getimagesize() first.
$info = getimagesize( $filename, $imageinfo );
if ( false !== $info ) {
return $info;
}
// For PHP versions that don't support WebP images, extract the image
// size info from the file headers.
if ( 'image/webp' === wp_get_image_mime( $filename ) ) {
$webp_info = wp_get_webp_info( $filename );
$width = $webp_info['width'];
$height = $webp_info['height'];
// Mimic the native return format.
if ( $width && $height ) {
return array(
$width,
$height,
IMAGETYPE_WEBP, // phpcs:ignore PHPCompatibility.Constants.NewConstants.imagetype_webpFound
sprintf(
'width="%d" height="%d"',
$width,
$height
),
'mime' => 'image/webp',
);
}
}
// The image could not be parsed.
return false;
}

View File

@ -6539,7 +6539,7 @@ function wp_attachment_is( $type, $post = null ) {
switch ( $type ) {
case 'image':
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp' );
return in_array( $ext, $image_exts, true );
case 'audio':

View File

@ -438,7 +438,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
);
}
$supported_types = array( 'image/jpeg', 'image/png', 'image/gif' );
$supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp' );
$mime_type = get_post_mime_type( $attachment_id );
if ( ! in_array( $mime_type, $supported_types, true ) ) {
return new WP_Error(

View File

@ -13,7 +13,7 @@
*
* @global string $wp_version
*/
$wp_version = '5.8-alpha-50809';
$wp_version = '5.8-alpha-50810';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.