Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
83 changes: 83 additions & 0 deletions src/wp-includes/class-wp-image-editor-imagick.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,89 @@ public static function supports_mime_type( $mime_type ) {
}
}

/**
* Checks to see if editor supports saving to the mime-type specified.
*
* @since 7.1.0
*
* @param string $mime_type
* @return bool
*/
public static function supports_output_mime_type( $mime_type ) {
if ( ! self::supports_mime_type( $mime_type ) ) {
return false;
}

if ( 0 !== strpos( $mime_type, 'image/' ) ) {
return true;
}

if ( 'image/avif' !== $mime_type ) {
return true;
}

return self::supports_encoding_mime_type( $mime_type );
}

/**
* Checks whether Imagick can encode a mime type.
*
* Some ImageMagick builds can decode image formats, but cannot encode them.
*
* @since 7.1.0
*
* @param string $mime_type Image mime type.
* @return bool Whether encoding is supported.
*/
private static function supports_encoding_mime_type( $mime_type ) {
static $supports_encoding = array();

if ( isset( $supports_encoding[ $mime_type ] ) ) {
return $supports_encoding[ $mime_type ];
}

$extension = strtoupper( self::get_extension( $mime_type ) );

if ( ! $extension ) {
$supports_encoding[ $mime_type ] = false;
return false;
}

$supports_encoding[ $mime_type ] = false;
$image = null;
$temp_file = tempnam( get_temp_dir(), 'wp-image-editor-' );

if ( ! $temp_file ) {
return false;
}

$test_file = $temp_file . '.' . strtolower( $extension );

try {
$image = new Imagick();
$image->newImage( 1, 1, new ImagickPixel( 'white' ) );
$image->setImageFormat( $extension );
$supports_encoding[ $mime_type ] = $image->writeImage( $test_file );
} catch ( Exception $e ) {
$supports_encoding[ $mime_type ] = false;
}

if ( $image instanceof Imagick ) {
$image->clear();
$image->destroy();
}

if ( file_exists( $temp_file ) ) {
unlink( $temp_file );
}

if ( file_exists( $test_file ) ) {
unlink( $test_file );
}

return $supports_encoding[ $mime_type ];
}

/**
* Loads image from $this->file into new Imagick Object.
*
Expand Down
16 changes: 14 additions & 2 deletions src/wp-includes/class-wp-image-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ public static function supports_mime_type( $mime_type ) {
return false;
}

/**
* Checks to see if editor supports saving to the mime-type specified.
*
* @since 7.1.0
*
* @param string $mime_type
* @return bool
*/
public static function supports_output_mime_type( $mime_type ) {
return static::supports_mime_type( $mime_type );
}

/**
* Loads image from $this->file into editor.
*
Expand Down Expand Up @@ -370,7 +382,7 @@ protected function get_output_format( $filename = null, $mime_type = null ) {
$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 ] )
&& $this->supports_output_mime_type( $output_format[ $mime_type ] )
) {
$mime_type = $output_format[ $mime_type ];
$new_ext = $this->get_extension( $mime_type );
Expand All @@ -380,7 +392,7 @@ protected function get_output_format( $filename = null, $mime_type = null ) {
* 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 ) ) {
if ( ! $this->supports_output_mime_type( $mime_type ) ) {
/**
* Filters default mime type prior to getting the file extension.
*
Expand Down
61 changes: 43 additions & 18 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -4322,6 +4322,15 @@ function _wp_image_editor_choose( $args = array() ) {
require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php';
require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php';
require_once ABSPATH . WPINC . '/class-avif-info.php';

if ( isset( $args['mime_type'] ) && ! isset( $args['output_mime_type'] ) ) {
$default_output_format = wp_get_image_editor_default_output_format();

if ( isset( $default_output_format[ $args['mime_type'] ] ) ) {
$args['output_mime_type'] = $default_output_format[ $args['mime_type'] ];
}
}

/**
* Filters the list of image editing library classes.
*
Expand Down Expand Up @@ -4369,18 +4378,22 @@ function _wp_image_editor_choose( $args = array() ) {
continue;
}

// Implementation should ideally support the output mime type as well if set and different than the passed type.
if (
isset( $args['mime_type'] ) &&
isset( $args['output_mime_type'] ) &&
$args['mime_type'] !== $args['output_mime_type'] &&
! call_user_func( array( $implementation, 'supports_mime_type' ), $args['output_mime_type'] )
) {
/*
* This implementation supports the input type but not the output type.
* Keep looking to see if we can find an implementation that supports both.
*/
$editor = $implementation;
// Implementation should support saving to the requested output mime type.
if ( isset( $args['mime_type'] ) ) {
$output_mime_type = isset( $args['output_mime_type'] ) ? $args['output_mime_type'] : $args['mime_type'];
$supports_output = is_callable( array( $implementation, 'supports_output_mime_type' ) ) ?
call_user_func( array( $implementation, 'supports_output_mime_type' ), $output_mime_type ) :
call_user_func( array( $implementation, 'supports_mime_type' ), $output_mime_type );
}

if ( isset( $args['mime_type'] ) && ! $supports_output ) {
if (
isset( $args['output_mime_type'] ) &&
$args['mime_type'] !== $args['output_mime_type']
) {
$editor = $implementation;
}

continue;
}

Expand Down Expand Up @@ -6416,22 +6429,34 @@ function wp_high_priority_element_flag( $value = null ): bool {
}

/**
* Determines the output format for the image editor.
* Retrieves the default output format mappings for the image editor.
*
* @since 6.7.0
* @since 7.1.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 ) {
$output_format = array(
function wp_get_image_editor_default_output_format() {
return array(
'image/heic' => 'image/jpeg',
'image/heif' => 'image/jpeg',
'image/heic-sequence' => 'image/jpeg',
'image/heif-sequence' => 'image/jpeg',
);
}

/**
* 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 ) {
$output_format = wp_get_image_editor_default_output_format();

/**
* Filters the image editor output format mapping.
Expand Down
27 changes: 20 additions & 7 deletions tests/phpunit/includes/mock-image-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

class WP_Image_Editor_Mock extends WP_Image_Editor {

public static $load_return = true;
public static $test_return = true;
public static $save_return = array();
public static $spy = array();
public static $edit_return = array();
public static $size_return = null;
public static $load_return = true;
public static $test_return = true;
public static $save_return = array();
public static $spy = array();
public static $edit_return = array();
public static $size_return = null;
public static $supports_mime_type_return = true;
public static $supports_output_mime_type_return = true;

// Allow testing of jpeg_quality filter.
public function set_mime_type( $mime_type = null ) {
Expand All @@ -23,7 +25,18 @@ public static function test( $args = array() ) {
return self::$test_return;
}
public static function supports_mime_type( $mime_type ) {
return true;
if ( is_array( self::$supports_mime_type_return ) ) {
return ! empty( self::$supports_mime_type_return[ $mime_type ] );
}

return self::$supports_mime_type_return;
}
public static function supports_output_mime_type( $mime_type ) {
if ( is_array( self::$supports_output_mime_type_return ) ) {
return ! empty( self::$supports_output_mime_type_return[ $mime_type ] );
}

return self::$supports_output_mime_type_return;
}
public function resize( $max_w, $max_h, $crop = false ) {
self::$spy[ __FUNCTION__ ][] = func_get_args();
Expand Down
95 changes: 95 additions & 0 deletions tests/phpunit/tests/image/editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ public function set_up() {
parent::set_up();
}

public function tear_down() {
WP_Image_Editor_Mock::$load_return = true;
WP_Image_Editor_Mock::$test_return = true;
WP_Image_Editor_Mock::$save_return = array();
WP_Image_Editor_Mock::$spy = array();
WP_Image_Editor_Mock::$edit_return = array();
WP_Image_Editor_Mock::$size_return = null;
WP_Image_Editor_Mock::$supports_mime_type_return = true;
WP_Image_Editor_Mock::$supports_output_mime_type_return = true;

wp_cache_delete( 'wp_image_editor_choose', 'image_editor' );

parent::tear_down();
}

/**
* Test wp_get_image_editor() where load returns true
*
Expand All @@ -49,6 +64,75 @@ public function test_get_editor_load_returns_false() {
WP_Image_Editor_Mock::$load_return = true;
}

/**
* @ticket 63932
*/
public function test_get_editor_requires_output_support_for_source_mime_type() {
WP_Image_Editor_Mock::$supports_mime_type_return = array(
'image/avif' => true,
);
WP_Image_Editor_Mock::$supports_output_mime_type_return = array(
'image/avif' => false,
);

wp_cache_delete( 'wp_image_editor_choose', 'image_editor' );

$this->assertFalse( wp_image_editor_supports( array( 'mime_type' => 'image/avif' ) ) );
}

/**
* @ticket 63932
*/
public function test_get_editor_allows_mapped_output_mime_type() {
WP_Image_Editor_Mock::$supports_mime_type_return = array(
'image/heic' => true,
);
WP_Image_Editor_Mock::$supports_output_mime_type_return = array(
'image/heic' => false,
'image/jpeg' => true,
);

wp_cache_delete( 'wp_image_editor_choose', 'image_editor' );

$this->assertTrue( wp_image_editor_supports( array( 'mime_type' => 'image/heic' ) ) );
}

/**
* @ticket 63932
*/
public function test_get_output_format_ignores_unsupported_output_mime_type() {
WP_Image_Editor_Mock::$supports_mime_type_return = array(
'image/jpeg' => true,
);
WP_Image_Editor_Mock::$supports_output_mime_type_return = array(
'image/jpeg' => true,
'image/avif' => false,
);

add_filter( 'image_editor_output_format', array( $this, 'image_editor_output_avif' ) );

try {
$editor = wp_get_image_editor( DIR_TESTDATA . '/images/canola.jpg' );
$this->assertInstanceOf( 'WP_Image_Editor_Mock', $editor );

$method = new ReflectionMethod( $editor, 'get_output_format' );
if ( PHP_VERSION_ID < 80100 ) {
$method->setAccessible( true );
}

$this->assertSame(
array(
DIR_TESTDATA . '/images/canola.jpg',
'jpg',
'image/jpeg',
),
$method->invoke( $editor, DIR_TESTDATA . '/images/canola.jpg', 'image/jpeg' )
);
} finally {
remove_filter( 'image_editor_output_format', array( $this, 'image_editor_output_avif' ) );
}
}

/**
* Return integer of 95 for testing.
*/
Expand Down Expand Up @@ -181,6 +265,17 @@ public function image_editor_output_formats( $formats ) {
return $formats;
}

/**
* Changes the output format for JPEG images to AVIF.
*
* @param array $formats
* @return array
*/
public function image_editor_output_avif( $formats ) {
$formats['image/jpeg'] = 'image/avif';
return $formats;
}

/**
* Changes the quality according to the mime-type.
*
Expand Down
7 changes: 2 additions & 5 deletions tests/phpunit/tests/image/editorImagick.php
Original file line number Diff line number Diff line change
Expand Up @@ -705,17 +705,14 @@ static function ( $value ) {
* Test filter `image_max_bit_depth` correctly sets the maximum bit depth of resized images.
*
* @ticket 62285
*
* Temporarily disabled until we can figure out why it fails on the Trixie based PHP container.
* See https://core.trac.wordpress.org/ticket/63932.
* @requires PHP < 8.3
* @ticket 63932
*/
public function test_image_max_bit_depth() {
$file = DIR_TESTDATA . '/images/colors_hdr_p3.avif';
$imagick_image_editor = new WP_Image_Editor_Imagick( $file );

// Skip if AVIF not supported.
if ( ! $imagick_image_editor->supports_mime_type( 'image/avif' ) ) {
if ( ! $imagick_image_editor->supports_output_mime_type( 'image/avif' ) ) {
$this->markTestSkipped( 'The image editor does not support the AVIF mime type.' );
}

Expand Down
Loading
Loading