Skip to content

Add support for HEIC uploading + JPEG conversion #7034

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bff5feb
Use setCompressionQuality when setting image quality
adamsilverstein Jul 9, 2024
619fb73
Add AVIF generated file size tests
adamsilverstein Jul 9, 2024
39c997a
Comment out setCompressionQuality calls to verify tests fail
adamsilverstein Jul 9, 2024
58b8c99
Improve AVIF tests
adamsilverstein Jul 9, 2024
ddf1b65
restore fixes
adamsilverstein Jul 10, 2024
722ddbf
reverse comparisons for assertLessThan
adamsilverstein Jul 11, 2024
ff5958f
space
adamsilverstein Jul 11, 2024
4719cf3
restore some text, separate out avif test
adamsilverstein Jul 12, 2024
0e5f559
restore previous test
adamsilverstein Jul 12, 2024
3db8e02
Add HEIC support
adamsilverstein Jul 15, 2024
b2df6ec
Add a default transform from HEIF to JPEG
adamsilverstein Jul 15, 2024
18f946c
revert tests/phpunit/tests/media.php to trunk
adamsilverstein Jul 16, 2024
81cc2f4
restore handlers.js from trunk
adamsilverstein Jul 16, 2024
a72ab72
update doc block
adamsilverstein Jul 16, 2024
230024d
revert changes from 61614
adamsilverstein Jul 16, 2024
db8ee37
remove additional 61614 changes
adamsilverstein Jul 16, 2024
9209a54
Update src/wp-includes/functions.php
adamsilverstein Jul 16, 2024
346d151
heif->heic
adamsilverstein Jul 16, 2024
683a076
Move heic upload support check to plupload_init filter
adamsilverstein Jul 16, 2024
9f5ead7
restore wp_show_heic_upload_error filtering
adamsilverstein Jul 16, 2024
bbbc2fe
heif->heic
adamsilverstein Jul 16, 2024
2314e65
Update src/wp-includes/media.php
adamsilverstein Jul 16, 2024
1fc680d
Update src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-c…
adamsilverstein Jul 16, 2024
694a747
one more heif->heic
adamsilverstein Jul 16, 2024
1de33cc
Shim PHP HEIC image size support with Imagick
adamsilverstein Jul 16, 2024
842e05d
Improve HEIC detection in wp_get_image_mime
adamsilverstein Jul 16, 2024
8b2009c
Ensure HEICs are enabled for wp_generate_attachment_metadata
adamsilverstein Jul 16, 2024
518500b
Merge branch 'trunk' into add/heic-support
adamsilverstein Jul 16, 2024
e1318bf
Yoda helps
adamsilverstein Jul 16, 2024
a2fc78c
Update src/wp-includes/media.php
adamsilverstein Jul 17, 2024
a65c937
One more HEIF->HEIC
adamsilverstein Jul 17, 2024
9d3fb9c
Merge branch 'add/heic-support' of github.com:adamsilverstein/wordpre…
adamsilverstein Jul 17, 2024
623593a
Add tests for wp_get_image_mime and wp_getimagesize
noisysocks Aug 1, 2024
f70771b
Add heic image to file_is_displayable_image test
noisysocks Aug 1, 2024
09078d1
Add test for resizing HEIC images
noisysocks Aug 1, 2024
5abb3a7
Split HEIC out of test_wp_getimagesize, skip when necessary
noisysocks Aug 1, 2024
ec4cb6a
Remove unnecessary foreach in test_wp_getimagesize_heic
noisysocks Aug 1, 2024
db78c27
De-duplicate call to image_editor_output_format filter
noisysocks Aug 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/js/media/controllers/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Librar
isImageAttachment: function( attachment ) {
// If uploading, we know the filename but not the mime type.
if ( attachment.get('uploading') ) {
return /\.(jpe?g|png|gif|webp|avif)$/i.test( attachment.get('filename') );
return /\.(jpe?g|png|gif|webp|avif|heic)$/i.test( attachment.get('filename') );
}

return attachment.get('type') === 'image';
Expand Down
3 changes: 2 additions & 1 deletion src/wp-admin/includes/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ function wp_copy_parent_attachment_properties( $cropped, $parent_attachment_id,
*
* @since 2.1.0
* @since 6.0.0 The `$filesize` value was added to the returned array.
* @since 6.7.0 The 'image/heic' mime type is supported.
*
* @param int $attachment_id Attachment ID to process.
* @param string $file Filepath of the attached image.
Expand All @@ -555,7 +556,7 @@ function wp_generate_attachment_metadata( $attachment_id, $file ) {
$support = false;
$mime_type = get_post_mime_type( $attachment );

if ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) {
if ( 'image/heic' === $mime_type || ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) ) {
// Make thumbnails and other intermediate sizes.
$metadata = wp_create_image_subsizes( $file, $attachment_id );
} elseif ( wp_attachment_is( 'video', $attachment ) ) {
Expand Down
5 changes: 2 additions & 3 deletions src/wp-includes/class-wp-image-editor-imagick.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ public function set_quality( $quality = null ) {
$this->image->setImageCompressionQuality( $quality );
}
break;
case 'image/avif':
default:
$this->image->setImageCompressionQuality( $quality );
}
Expand Down Expand Up @@ -258,10 +257,10 @@ protected function update_size( $width = null, $height = null ) {
}

/*
* If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images
* If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF and HEIC images
* are properly sized without affecting previous `getImageGeometry` behavior.
*/
if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) {
if ( ( ! $width || ! $height ) && ( 'image/avif' === $this->mime_type || 'image/heic' === $this->mime_type ) ) {
$size = wp_getimagesize( $this->file );
$width = $size[0];
$height = $size[1];
Expand Down
22 changes: 1 addition & 21 deletions src/wp-includes/class-wp-image-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ protected function get_default_quality( $mime_type ) {
$quality = 86;
break;
case 'image/jpeg':
case 'image/avif':
default:
$quality = $this->default_quality;
}
Expand Down Expand Up @@ -366,26 +365,7 @@ protected function get_output_format( $filename = null, $mime_type = null ) {
$new_ext = $file_ext;
}

/**
* Filters the image editor output format mapping.
*
* Enables filtering the mime type used to save images. By default,
* the mapping array is empty, so the mime type matches the source image.
*
* @see WP_Image_Editor::get_output_format()
*
* @since 5.8.0
*
* @param string[] $output_format {
* An array of mime type mappings. Maps a source mime type to a new
* destination mime type. Default empty array.
*
* @type string ...$0 The new mime type.
* }
* @param string $filename Path to the image.
* @param string $mime_type The source image mime type.
*/
$output_format = apply_filters( 'image_editor_output_format', array(), $filename, $mime_type );
$output_format = wp_get_image_editor_output_format( $filename, $mime_type );

if ( isset( $output_format[ $mime_type ] )
&& $this->supports_mime_type( $output_format[ $mime_type ] )
Expand Down
5 changes: 5 additions & 0 deletions src/wp-includes/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,8 @@ function str_ends_with( $haystack, $needle ) {
if ( ! defined( 'IMG_AVIF' ) ) {
define( 'IMG_AVIF', IMAGETYPE_AVIF );
}

// IMAGETYPE_HEIC constant is not yet defined in PHP as of PHP 8.3.
if ( ! defined( 'IMAGETYPE_HEIC' ) ) {
define( 'IMAGETYPE_HEIC', 99 );
}
14 changes: 12 additions & 2 deletions src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2706,8 +2706,7 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null )
* when regenerated. If yes, ensure the new file name will be unique and will produce unique sub-sizes.
*/
if ( $is_image ) {
/** This filter is documented in wp-includes/class-wp-image-editor.php */
$output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type );
$output_formats = wp_get_image_editor_output_format( $_dir . $filename, $mime_type );
$alt_types = array();

if ( ! empty( $output_formats[ $mime_type ] ) ) {
Expand Down Expand Up @@ -3120,6 +3119,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
'image/tiff' => 'tif',
'image/webp' => 'webp',
'image/avif' => 'avif',
'image/heic' => 'heic',
)
);

Expand Down Expand Up @@ -3299,6 +3299,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
* @since 4.7.1
* @since 5.8.0 Added support for WebP images.
* @since 6.5.0 Added support for AVIF images.
* @since 6.7.0 Added support for HEIC images.
*
* @param string $file Full path to the file.
* @return string|false The actual mime type or false if the type cannot be determined.
Expand Down Expand Up @@ -3372,6 +3373,15 @@ function wp_get_image_mime( $file ) {
) {
$mime = 'image/avif';
}

if (
isset( $magic[1] ) &&
isset( $magic[2] ) &&
'ftyp' === hex2bin( $magic[1] ) &&
( 'heic' === hex2bin( $magic[2] ) || 'heif' === hex2bin( $magic[2] ) )
) {
$mime = 'image/heic';
}
} catch ( Exception $e ) {
$mime = false;
}
Expand Down
72 changes: 69 additions & 3 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -4060,8 +4060,7 @@ function wp_get_image_editor( $path, $args = array() ) {

// Check and set the output mime type mapped to the input type.
if ( isset( $args['mime_type'] ) ) {
/** This filter is documented in wp-includes/class-wp-image-editor.php */
$output_format = apply_filters( 'image_editor_output_format', array(), $path, $args['mime_type'] );
$output_format = wp_get_image_editor_output_format( $path, $args['mime_type'] );
if ( isset( $output_format[ $args['mime_type'] ] ) ) {
$args['output_mime_type'] = $output_format[ $args['mime_type'] ];
}
Expand Down Expand Up @@ -4220,6 +4219,11 @@ function wp_plupload_default_settings() {
$defaults['avif_upload_error'] = true;
}

// Check if HEIC images can be edited.
if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
$defaults['heic_upload_error'] = true;
}

/**
* Filters the Plupload default settings.
*
Expand Down Expand Up @@ -5479,12 +5483,17 @@ function _wp_add_additional_image_sizes() {
* Callback to enable showing of the user error when uploading .heic images.
*
* @since 5.5.0
* @since 6.7.0 The default behavior is to enable heic uplooads as long as the server
* supports the format. The uploads are converted to JPEG's by default.
*
* @param array[] $plupload_settings The settings for Plupload.js.
* @return array[] Modified settings for Plupload.js.
*/
function wp_show_heic_upload_error( $plupload_settings ) {
$plupload_settings['heic_upload_error'] = true;
// Check if HEIC images can be edited.
if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
$plupload_init['heic_upload_error'] = true;
}
return $plupload_settings;
}

Expand Down Expand Up @@ -5582,6 +5591,29 @@ function wp_getimagesize( $filename, ?array &$image_info = null ) {
}
}

// For PHP versions that don't support HEIC images, extract the size info using Imagick when available.
if ( 'image/heic' === wp_get_image_mime( $filename ) ) {
$editor = wp_get_image_editor( $filename );
if ( is_wp_error( $editor ) ) {
return false;
}
// If the editor for HEICs is Imagick, use it to get the image size.
if ( $editor instanceof WP_Image_Editor_Imagick ) {
$size = $editor->get_size();
return array(
$size['width'],
$size['height'],
IMAGETYPE_HEIC,
sprintf(
'width="%d" height="%d"',
$size['width'],
$size['height']
),
'mime' => 'image/heic',
);
}
}

// The image could not be parsed.
return false;
}
Expand Down Expand Up @@ -6065,3 +6097,37 @@ function wp_high_priority_element_flag( $value = null ) {

return $high_priority_element;
}

/**
* Determines the output format for the image editor.
*
* @since 6.7.0
* @access private
*
* @param string $filename Path to the image.
* @param string $mime_type The source image mime type.
* @return string[] An array of mime type mappings.
*/
function wp_get_image_editor_output_format( $filename, $mime_type ) {
/**
* Filters the image editor output format mapping.
*
* Enables filtering the mime type used to save images. By default,
* the mapping array is empty, so the mime type matches the source image.
*
* @see WP_Image_Editor::get_output_format()
*
* @since 5.8.0
* @since 6.7.0 The default was changed from array() to array( 'image/heic' => 'image/jpeg' ).
*
* @param string[] $output_format {
* An array of mime type mappings. Maps a source mime type to a new
* destination mime type. Default maps uploaded HEIC images to JPEG output.
*
* @type string ...$0 The new mime type.
* }
* @param string $filename Path to the image.
* @param string $mime_type The source image mime type.
*/
return apply_filters( 'image_editor_output_format', array( 'image/heic' => 'image/jpeg' ), $filename, $mime_type );
}
2 changes: 1 addition & 1 deletion src/wp-includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -6829,7 +6829,7 @@ function wp_attachment_is( $type, $post = null ) {

switch ( $type ) {
case 'image':
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' );
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif', 'heic' );
return in_array( $ext, $image_exts, true );

case 'audio':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ public function edit_media_item( $request ) {
);
}

$supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' );
$supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' );
$mime_type = get_post_mime_type( $attachment_id );
if ( ! in_array( $mime_type, $supported_types, true ) ) {
return new WP_Error(
Expand Down
Binary file added tests/phpunit/data/images/test-image.heic
Binary file not shown.
36 changes: 35 additions & 1 deletion tests/phpunit/tests/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,11 @@ public function data_wp_get_image_mime() {
DIR_TESTDATA . '/images/avif-transparent.avif',
'image/avif',
),
// HEIC.
array(
DIR_TESTDATA . '/images/test-image.heic',
'image/heic',
),
);

return $data;
Expand Down Expand Up @@ -1384,7 +1389,7 @@ public function test_wp_getimagesize( $file, $expected ) {
}

/**
* Data profider for test_wp_getimagesize().
* Data provider for test_wp_getimagesize().
*/
public function data_wp_getimagesize() {
$data = array(
Expand Down Expand Up @@ -1541,6 +1546,35 @@ public function data_wp_getimagesize() {
return $data;
}

/**
* Tests that wp_getimagesize() correctly handles HEIC image files.
*
* @ticket 53645
*/
public function test_wp_getimagesize_heic() {
if ( ! is_callable( 'exif_imagetype' ) && ! function_exists( 'getimagesize' ) ) {
$this->markTestSkipped( 'The exif PHP extension is not loaded.' );
}

$file = DIR_TESTDATA . '/images/test-image.heic';

$editor = wp_get_image_editor( $file );
if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/heic' ) ) {
$this->markTestSkipped( 'No HEIC support in the editor engine on this system.' );
}

$expected = array(
50,
50,
IMAGETYPE_HEIC,
'width="50" height="50"',
'mime' => 'image/heic',
);
$result = wp_getimagesize( $file );
$this->assertSame( $expected, $result );
}


/**
* @ticket 39550
* @dataProvider data_wp_check_filetype_and_ext
Expand Down
1 change: 1 addition & 0 deletions tests/phpunit/tests/image/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ public function data_file_is_displayable_image_negative() {
'test-image.jp2',
'test-image.psd',
'test-image-zip.tiff',
'test-image.heic',
);

return $this->text_array_to_dataprovider( $files );
Expand Down
28 changes: 27 additions & 1 deletion tests/phpunit/tests/image/resize.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,32 @@ public function test_resize_avif() {
$this->assertSame( IMAGETYPE_AVIF, $type );
}

/**
* Test resizing HEIC image.
*
* @ticket 53645
*/
public function test_resize_heic() {
$file = DIR_TESTDATA . '/images/test-image.heic';
$editor = wp_get_image_editor( $file );

// Check if the editor supports the HEIC mime type.
if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/heic' ) ) {
$this->markTestSkipped( 'No HEIC support in the editor engine on this system.' );
}

$image = $this->resize_helper( $file, 25, 25 );

list( $w, $h, $type ) = wp_getimagesize( $image );

unlink( $image );

$this->assertSame( 'test-image-25x25.jpg', wp_basename( $image ) );
$this->assertSame( 25, $w );
$this->assertSame( 25, $h );
$this->assertSame( IMAGETYPE_JPEG, $type );
}

public function test_resize_larger() {
// image_resize() should refuse to make an image larger.
$image = $this->resize_helper( DIR_TESTDATA . '/images/test-image.jpg', 100, 100 );
Expand Down Expand Up @@ -235,6 +261,6 @@ protected function resize_helper( $file, $width, $height, $crop = false ) {
return $saved;
}

return $dest_file;
return $saved['path'];
}
}
Loading