Skip to content

Commit 471a619

Browse files
committed
Media: Automatically convert HEIC images to JPEG
Automatically create a JPEG version of uploaded HEIC images if the server has a version of Imagick that supports HEIC. Conversion is done silently through the existing `WP_Image_Editor` infrastructure that creates multiple sizes of uploaded images. This allows users to view HEIC images in WP Admin and use them in their posts and pages regardless of whether their browser supports HEIC. Browser support for HEIC is relatively low (only Safari) while the occurrence of HEIC images is relatively common. The original HEIC image can be downloaded via a link on the attachment page. Props adamsilverstein, noisysocks, swissspidy, spacedmonkey, peterwilsoncc. Fixes #53645. git-svn-id: https://develop.svn.wordpress.org/trunk@58849 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 0a12ad2 commit 471a619

File tree

13 files changed

+157
-35
lines changed

13 files changed

+157
-35
lines changed

src/js/media/controllers/library.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ Library = wp.media.controller.State.extend(/** @lends wp.media.controller.Librar
196196
isImageAttachment: function( attachment ) {
197197
// If uploading, we know the filename but not the mime type.
198198
if ( attachment.get('uploading') ) {
199-
return /\.(jpe?g|png|gif|webp|avif)$/i.test( attachment.get('filename') );
199+
return /\.(jpe?g|png|gif|webp|avif|heic)$/i.test( attachment.get('filename') );
200200
}
201201

202202
return attachment.get('type') === 'image';

src/wp-admin/includes/image.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ function wp_copy_parent_attachment_properties( $cropped, $parent_attachment_id,
543543
*
544544
* @since 2.1.0
545545
* @since 6.0.0 The `$filesize` value was added to the returned array.
546+
* @since 6.7.0 The 'image/heic' mime type is supported.
546547
*
547548
* @param int $attachment_id Attachment ID to process.
548549
* @param string $file Filepath of the attached image.
@@ -555,7 +556,7 @@ function wp_generate_attachment_metadata( $attachment_id, $file ) {
555556
$support = false;
556557
$mime_type = get_post_mime_type( $attachment );
557558

558-
if ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) {
559+
if ( 'image/heic' === $mime_type || ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) ) {
559560
// Make thumbnails and other intermediate sizes.
560561
$metadata = wp_create_image_subsizes( $file, $attachment_id );
561562
} elseif ( wp_attachment_is( 'video', $attachment ) ) {

src/wp-includes/class-wp-image-editor-imagick.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ public function set_quality( $quality = null ) {
219219
$this->image->setImageCompressionQuality( $quality );
220220
}
221221
break;
222-
case 'image/avif':
223222
default:
224223
$this->image->setImageCompressionQuality( $quality );
225224
}
@@ -258,10 +257,10 @@ protected function update_size( $width = null, $height = null ) {
258257
}
259258

260259
/*
261-
* If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF images
260+
* If we still don't have the image size, fall back to `wp_getimagesize`. This ensures AVIF and HEIC images
262261
* are properly sized without affecting previous `getImageGeometry` behavior.
263262
*/
264-
if ( ( ! $width || ! $height ) && 'image/avif' === $this->mime_type ) {
263+
if ( ( ! $width || ! $height ) && ( 'image/avif' === $this->mime_type || 'image/heic' === $this->mime_type ) ) {
265264
$size = wp_getimagesize( $this->file );
266265
$width = $size[0];
267266
$height = $size[1];

src/wp-includes/class-wp-image-editor.php

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,6 @@ protected function get_default_quality( $mime_type ) {
318318
$quality = 86;
319319
break;
320320
case 'image/jpeg':
321-
case 'image/avif':
322321
default:
323322
$quality = $this->default_quality;
324323
}
@@ -366,26 +365,7 @@ protected function get_output_format( $filename = null, $mime_type = null ) {
366365
$new_ext = $file_ext;
367366
}
368367

369-
/**
370-
* Filters the image editor output format mapping.
371-
*
372-
* Enables filtering the mime type used to save images. By default,
373-
* the mapping array is empty, so the mime type matches the source image.
374-
*
375-
* @see WP_Image_Editor::get_output_format()
376-
*
377-
* @since 5.8.0
378-
*
379-
* @param string[] $output_format {
380-
* An array of mime type mappings. Maps a source mime type to a new
381-
* destination mime type. Default empty array.
382-
*
383-
* @type string ...$0 The new mime type.
384-
* }
385-
* @param string $filename Path to the image.
386-
* @param string $mime_type The source image mime type.
387-
*/
388-
$output_format = apply_filters( 'image_editor_output_format', array(), $filename, $mime_type );
368+
$output_format = wp_get_image_editor_output_format( $filename, $mime_type );
389369

390370
if ( isset( $output_format[ $mime_type ] )
391371
&& $this->supports_mime_type( $output_format[ $mime_type ] )

src/wp-includes/compat.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,3 +549,8 @@ function str_ends_with( $haystack, $needle ) {
549549
if ( ! defined( 'IMG_AVIF' ) ) {
550550
define( 'IMG_AVIF', IMAGETYPE_AVIF );
551551
}
552+
553+
// IMAGETYPE_HEIC constant is not yet defined in PHP as of PHP 8.3.
554+
if ( ! defined( 'IMAGETYPE_HEIC' ) ) {
555+
define( 'IMAGETYPE_HEIC', 99 );
556+
}

src/wp-includes/functions.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2706,8 +2706,7 @@ function wp_unique_filename( $dir, $filename, $unique_filename_callback = null )
27062706
* when regenerated. If yes, ensure the new file name will be unique and will produce unique sub-sizes.
27072707
*/
27082708
if ( $is_image ) {
2709-
/** This filter is documented in wp-includes/class-wp-image-editor.php */
2710-
$output_formats = apply_filters( 'image_editor_output_format', array(), $_dir . $filename, $mime_type );
2709+
$output_formats = wp_get_image_editor_output_format( $_dir . $filename, $mime_type );
27112710
$alt_types = array();
27122711

27132712
if ( ! empty( $output_formats[ $mime_type ] ) ) {
@@ -3120,6 +3119,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
31203119
'image/tiff' => 'tif',
31213120
'image/webp' => 'webp',
31223121
'image/avif' => 'avif',
3122+
'image/heic' => 'heic',
31233123
)
31243124
);
31253125

@@ -3299,6 +3299,7 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
32993299
* @since 4.7.1
33003300
* @since 5.8.0 Added support for WebP images.
33013301
* @since 6.5.0 Added support for AVIF images.
3302+
* @since 6.7.0 Added support for HEIC images.
33023303
*
33033304
* @param string $file Full path to the file.
33043305
* @return string|false The actual mime type or false if the type cannot be determined.
@@ -3372,6 +3373,15 @@ function wp_get_image_mime( $file ) {
33723373
) {
33733374
$mime = 'image/avif';
33743375
}
3376+
3377+
if (
3378+
isset( $magic[1] ) &&
3379+
isset( $magic[2] ) &&
3380+
'ftyp' === hex2bin( $magic[1] ) &&
3381+
( 'heic' === hex2bin( $magic[2] ) || 'heif' === hex2bin( $magic[2] ) )
3382+
) {
3383+
$mime = 'image/heic';
3384+
}
33753385
} catch ( Exception $e ) {
33763386
$mime = false;
33773387
}

src/wp-includes/media.php

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4064,8 +4064,7 @@ function wp_get_image_editor( $path, $args = array() ) {
40644064

40654065
// Check and set the output mime type mapped to the input type.
40664066
if ( isset( $args['mime_type'] ) ) {
4067-
/** This filter is documented in wp-includes/class-wp-image-editor.php */
4068-
$output_format = apply_filters( 'image_editor_output_format', array(), $path, $args['mime_type'] );
4067+
$output_format = wp_get_image_editor_output_format( $path, $args['mime_type'] );
40694068
if ( isset( $output_format[ $args['mime_type'] ] ) ) {
40704069
$args['output_mime_type'] = $output_format[ $args['mime_type'] ];
40714070
}
@@ -4224,6 +4223,11 @@ function wp_plupload_default_settings() {
42244223
$defaults['avif_upload_error'] = true;
42254224
}
42264225

4226+
// Check if HEIC images can be edited.
4227+
if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
4228+
$defaults['heic_upload_error'] = true;
4229+
}
4230+
42274231
/**
42284232
* Filters the Plupload default settings.
42294233
*
@@ -5483,12 +5487,17 @@ function _wp_add_additional_image_sizes() {
54835487
* Callback to enable showing of the user error when uploading .heic images.
54845488
*
54855489
* @since 5.5.0
5490+
* @since 6.7.0 The default behavior is to enable heic uplooads as long as the server
5491+
* supports the format. The uploads are converted to JPEG's by default.
54865492
*
54875493
* @param array[] $plupload_settings The settings for Plupload.js.
54885494
* @return array[] Modified settings for Plupload.js.
54895495
*/
54905496
function wp_show_heic_upload_error( $plupload_settings ) {
5491-
$plupload_settings['heic_upload_error'] = true;
5497+
// Check if HEIC images can be edited.
5498+
if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) ) {
5499+
$plupload_init['heic_upload_error'] = true;
5500+
}
54925501
return $plupload_settings;
54935502
}
54945503

@@ -5586,6 +5595,29 @@ function wp_getimagesize( $filename, ?array &$image_info = null ) {
55865595
}
55875596
}
55885597

5598+
// For PHP versions that don't support HEIC images, extract the size info using Imagick when available.
5599+
if ( 'image/heic' === wp_get_image_mime( $filename ) ) {
5600+
$editor = wp_get_image_editor( $filename );
5601+
if ( is_wp_error( $editor ) ) {
5602+
return false;
5603+
}
5604+
// If the editor for HEICs is Imagick, use it to get the image size.
5605+
if ( $editor instanceof WP_Image_Editor_Imagick ) {
5606+
$size = $editor->get_size();
5607+
return array(
5608+
$size['width'],
5609+
$size['height'],
5610+
IMAGETYPE_HEIC,
5611+
sprintf(
5612+
'width="%d" height="%d"',
5613+
$size['width'],
5614+
$size['height']
5615+
),
5616+
'mime' => 'image/heic',
5617+
);
5618+
}
5619+
}
5620+
55895621
// The image could not be parsed.
55905622
return false;
55915623
}
@@ -6069,3 +6101,37 @@ function wp_high_priority_element_flag( $value = null ) {
60696101

60706102
return $high_priority_element;
60716103
}
6104+
6105+
/**
6106+
* Determines the output format for the image editor.
6107+
*
6108+
* @since 6.7.0
6109+
* @access private
6110+
*
6111+
* @param string $filename Path to the image.
6112+
* @param string $mime_type The source image mime type.
6113+
* @return string[] An array of mime type mappings.
6114+
*/
6115+
function wp_get_image_editor_output_format( $filename, $mime_type ) {
6116+
/**
6117+
* Filters the image editor output format mapping.
6118+
*
6119+
* Enables filtering the mime type used to save images. By default,
6120+
* the mapping array is empty, so the mime type matches the source image.
6121+
*
6122+
* @see WP_Image_Editor::get_output_format()
6123+
*
6124+
* @since 5.8.0
6125+
* @since 6.7.0 The default was changed from array() to array( 'image/heic' => 'image/jpeg' ).
6126+
*
6127+
* @param string[] $output_format {
6128+
* An array of mime type mappings. Maps a source mime type to a new
6129+
* destination mime type. Default maps uploaded HEIC images to JPEG output.
6130+
*
6131+
* @type string ...$0 The new mime type.
6132+
* }
6133+
* @param string $filename Path to the image.
6134+
* @param string $mime_type The source image mime type.
6135+
*/
6136+
return apply_filters( 'image_editor_output_format', array( 'image/heic' => 'image/jpeg' ), $filename, $mime_type );
6137+
}

src/wp-includes/post.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6829,7 +6829,7 @@ function wp_attachment_is( $type, $post = null ) {
68296829

68306830
switch ( $type ) {
68316831
case 'image':
6832-
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif' );
6832+
$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif', 'heic' );
68336833
return in_array( $ext, $image_exts, true );
68346834

68356835
case 'audio':

src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ public function edit_media_item( $request ) {
531531
);
532532
}
533533

534-
$supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' );
534+
$supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' );
535535
$mime_type = get_post_mime_type( $attachment_id );
536536
if ( ! in_array( $mime_type, $supported_types, true ) ) {
537537
return new WP_Error(
949 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)