Make WordPress Core

Changeset 58849


Ignore:
Timestamp:
08/05/2024 04:11:40 AM (4 months ago)
Author:
noisysocks
Message:

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.

Location:
trunk
Files:
1 added
12 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/media/controllers/library.js

    r57524 r58849  
    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
  • trunk/src/wp-admin/includes/image.php

    r57755 r58849  
    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.
     
    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 );
  • trunk/src/wp-includes/class-wp-image-editor-imagick.php

    r58417 r58849  
    220220                    }
    221221                    break;
    222                 case 'image/avif':
    223222                default:
    224223                    $this->image->setImageCompressionQuality( $quality );
     
    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];
  • trunk/src/wp-includes/class-wp-image-editor.php

    r57524 r58849  
    319319                break;
    320320            case 'image/jpeg':
    321             case 'image/avif':
    322321            default:
    323322                $quality = $this->default_quality;
     
    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 ] )
  • trunk/src/wp-includes/compat.php

    r58763 r58849  
    550550    define( 'IMG_AVIF', IMAGETYPE_AVIF );
    551551}
     552
     553// IMAGETYPE_HEIC constant is not yet defined in PHP as of PHP 8.3.
     554if ( ! defined( 'IMAGETYPE_HEIC' ) ) {
     555    define( 'IMAGETYPE_HEIC', 99 );
     556}
  • trunk/src/wp-includes/functions.php

    r58848 r58849  
    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
     
    31213120                    'image/webp' => 'webp',
    31223121                    'image/avif' => 'avif',
     3122                    'image/heic' => 'heic',
    31233123                )
    31243124            );
     
    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.
     
    33723373        ) {
    33733374            $mime = 'image/avif';
     3375        }
     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';
    33743384        }
    33753385    } catch ( Exception $e ) {
  • trunk/src/wp-includes/media.php

    r58773 r58849  
    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'] ];
     
    42234222    if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) ) {
    42244223        $defaults['avif_upload_error'] = true;
     4224    }
     4225
     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;
    42254229    }
    42264230
     
    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.
     
    54895495 */
    54905496function 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}
     
    55835592                ),
    55845593                'mime' => 'image/avif',
     5594            );
     5595        }
     5596    }
     5597
     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',
    55855617            );
    55865618        }
     
    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 */
     6115function 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}
  • trunk/src/wp-includes/post.php

    r58437 r58849  
    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
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php

    r58706 r58849  
    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 ) ) {
  • trunk/tests/phpunit/tests/functions.php

    r58570 r58849  
    13561356                'image/avif',
    13571357            ),
     1358            // HEIC.
     1359            array(
     1360                DIR_TESTDATA . '/images/test-image.heic',
     1361                'image/heic',
     1362            ),
    13581363        );
    13591364
     
    13851390
    13861391    /**
    1387      * Data profider for test_wp_getimagesize().
     1392     * Data provider for test_wp_getimagesize().
    13881393     */
    13891394    public function data_wp_getimagesize() {
     
    15411546        return $data;
    15421547    }
     1548
     1549    /**
     1550     * Tests that wp_getimagesize() correctly handles HEIC image files.
     1551     *
     1552     * @ticket 53645
     1553     */
     1554    public function test_wp_getimagesize_heic() {
     1555        if ( ! is_callable( 'exif_imagetype' ) && ! function_exists( 'getimagesize' ) ) {
     1556            $this->markTestSkipped( 'The exif PHP extension is not loaded.' );
     1557        }
     1558
     1559        $file = DIR_TESTDATA . '/images/test-image.heic';
     1560
     1561        $editor = wp_get_image_editor( $file );
     1562        if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/heic' ) ) {
     1563            $this->markTestSkipped( 'No HEIC support in the editor engine on this system.' );
     1564        }
     1565
     1566        $expected = array(
     1567            50,
     1568            50,
     1569            IMAGETYPE_HEIC,
     1570            'width="50" height="50"',
     1571            'mime' => 'image/heic',
     1572        );
     1573        $result   = wp_getimagesize( $file );
     1574        $this->assertSame( $expected, $result );
     1575    }
     1576
    15431577
    15441578    /**
  • trunk/tests/phpunit/tests/image/functions.php

    r57931 r58849  
    238238            'test-image.psd',
    239239            'test-image-zip.tiff',
     240            'test-image.heic',
    240241        );
    241242
  • trunk/tests/phpunit/tests/image/resize.php

    r57524 r58849  
    115115    }
    116116
     117    /**
     118     * Test resizing HEIC image.
     119     *
     120     * @ticket 53645
     121     */
     122    public function test_resize_heic() {
     123        $file   = DIR_TESTDATA . '/images/test-image.heic';
     124        $editor = wp_get_image_editor( $file );
     125
     126        // Check if the editor supports the HEIC mime type.
     127        if ( is_wp_error( $editor ) || ! $editor->supports_mime_type( 'image/heic' ) ) {
     128            $this->markTestSkipped( 'No HEIC support in the editor engine on this system.' );
     129        }
     130
     131        $image = $this->resize_helper( $file, 25, 25 );
     132
     133        list( $w, $h, $type ) = wp_getimagesize( $image );
     134
     135        unlink( $image );
     136
     137        $this->assertSame( 'test-image-25x25.jpg', wp_basename( $image ) );
     138        $this->assertSame( 25, $w );
     139        $this->assertSame( 25, $h );
     140        $this->assertSame( IMAGETYPE_JPEG, $type );
     141    }
     142
    117143    public function test_resize_larger() {
    118144        // image_resize() should refuse to make an image larger.
     
    236262        }
    237263
    238         return $dest_file;
     264        return $saved['path'];
    239265    }
    240266}
Note: See TracChangeset for help on using the changeset viewer.