-
Notifications
You must be signed in to change notification settings - Fork 139
Prevent transparency loss in AVIF by falling back to WebP on older ImageMagick #2245
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
base: trunk
Are you sure you want to change the base?
Changes from all commits
ae7f22d
a1aff66
be0817b
3fe6954
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| <?php | ||
| /** | ||
| * Helper functions used for checking Imagick AVIF transparency support. | ||
| * | ||
| * @package performance-lab | ||
| * @since n.e.x.t | ||
| */ | ||
|
|
||
| // @codeCoverageIgnoreStart | ||
| if ( ! defined( 'ABSPATH' ) ) { | ||
| exit; // Exit if accessed directly. | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
|
|
||
| /** | ||
| * Callback for webp_enabled test. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @return array{label: string, status: string, badge: array{label: string, color: string}, description: string, actions: string, test: string} Result. | ||
| */ | ||
| function perflab_imagick_avif_transparency_supported_test(): array { | ||
| $result = array( | ||
| 'label' => __( 'Your site supports AVIF image format transparency with ImageMagick', 'performance-lab' ), | ||
| 'status' => 'good', | ||
| 'badge' => array( | ||
| 'label' => __( 'Performance', 'performance-lab' ), | ||
| 'color' => 'blue', | ||
| ), | ||
| 'description' => sprintf( | ||
| '<p>%s</p>', | ||
| __( 'Older versions of ImageMagick do not support transparency in AVIF images, which can result in loss of transparency when uploading AVIF files.', 'performance-lab' ) | ||
| ), | ||
| 'actions' => sprintf( | ||
| '<p><strong>%s</strong></p>', | ||
| __( 'Your ImageMagick installation supports AVIF transparency.', 'performance-lab' ) | ||
| ), | ||
| 'test' => 'is_imagick_avif_transparency_supported_enabled', | ||
| ); | ||
|
|
||
| if ( ! perflab_imagick_avif_transparency_supported() ) { | ||
| $result['status'] = 'recommended'; | ||
| $result['label'] = __( 'Your site does not support AVIF transparency', 'performance-lab' ); | ||
| $result['actions'] = sprintf( | ||
| '<p>%s</p>', | ||
| __( 'Update ImageMagick to the latest version by contacting your hosting provider.', 'performance-lab' ) | ||
| ); | ||
| } | ||
|
|
||
| return $result; | ||
| } | ||
|
|
||
| /** | ||
| * Checks if Imagick has AVIF transparency support. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @return bool True if Imagick has AVIF transparency support, false otherwise. | ||
| */ | ||
| function perflab_imagick_avif_transparency_supported(): bool { | ||
| if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) { | ||
| $imagick_version = Imagick::getVersion(); | ||
| if ( (bool) preg_match( '/\d+(?:\.\d+)+(?:-\d+)?/', $imagick_version['versionString'], $matches ) ) { | ||
| $imagick_version = $matches[0]; | ||
| } else { | ||
| $imagick_version = $imagick_version['versionString']; | ||
| } | ||
| return version_compare( $imagick_version, '7.0.25', '>=' ); | ||
| } | ||
|
|
||
| return false; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <?php | ||
| /** | ||
| * Hook callbacks used for checking Imagick AVIF transparency support. | ||
| * | ||
| * @package performance-lab | ||
| * @since n.e.x.t | ||
| */ | ||
|
|
||
| // @codeCoverageIgnoreStart | ||
| if ( ! defined( 'ABSPATH' ) ) { | ||
| exit; // Exit if accessed directly. | ||
| } | ||
| // @codeCoverageIgnoreEnd | ||
|
|
||
| /** | ||
| * Adds tests to site health. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @param array{direct: array<string, array{label: string, test: string}>} $tests Site Health Tests. | ||
| * @return array{direct: array<string, array{label: string, test: string}>} Amended tests. | ||
| */ | ||
| function perflab_add_imagick_avif_transparency_supported_test( array $tests ): array { | ||
| $tests['direct']['imagick_avif_transparency_supported'] = array( | ||
| 'label' => __( 'Imagick AVIF Transparency Support', 'performance-lab' ), | ||
| 'test' => 'perflab_imagick_avif_transparency_supported_test', | ||
| ); | ||
| return $tests; | ||
| } | ||
| add_filter( 'site_status_tests', 'perflab_add_imagick_avif_transparency_supported_test' ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -966,3 +966,53 @@ function webp_uploads_convert_palette_png_to_truecolor( $file ): array { | |
| } | ||
| add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); | ||
| add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_convert_palette_png_to_truecolor' ); | ||
|
|
||
| /** | ||
| * Checks if an image has transparency when uploading AVIF images with Imagick. | ||
| * | ||
| * @since n.e.x.t | ||
| * | ||
| * @param array<string, mixed>|mixed $file The uploaded file data. | ||
| * @return array<string, mixed> The modified file data. | ||
| */ | ||
| function webp_uploads_check_image_transparency( $file ): array { | ||
| if ( 'avif' !== webp_uploads_get_image_output_format() || webp_uploads_imagick_avif_transparency_supported() ) { | ||
| return $file; | ||
| } | ||
|
|
||
| // Because plugins do bad things. | ||
| if ( ! is_array( $file ) ) { | ||
| $file = array(); | ||
| } | ||
| if ( ! isset( $file['tmp_name'], $file['name'] ) ) { | ||
| return $file; | ||
| } | ||
| if ( isset( $file['type'] ) && is_string( $file['type'] ) ) { | ||
| if ( ! str_starts_with( strtolower( $file['type'] ), 'image/' ) ) { | ||
| return $file; | ||
| } | ||
| } elseif ( ! str_starts_with( strtolower( (string) wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] )['type'] ), 'image/' ) ) { | ||
| return $file; | ||
| } | ||
|
|
||
| $editor = wp_get_image_editor( $file['tmp_name'] ); | ||
|
|
||
| if ( is_wp_error( $editor ) || ! $editor instanceof WP_Image_Editor_Imagick ) { | ||
| return $file; | ||
| } | ||
|
|
||
| $reflection = new ReflectionClass( $editor ); | ||
| $image_property = $reflection->getProperty( 'image' ); | ||
| if ( PHP_VERSION_ID < 80100 ) { | ||
| $image_property->setAccessible( true ); | ||
| } | ||
| $imagick = $image_property->getValue( $editor ); | ||
|
Comment on lines
+1004
to
+1009
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's too bad that the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did try to create an anonymous class and added a method to expose the |
||
|
|
||
| if ( $imagick instanceof Imagick ) { | ||
| wp_cache_set( 'webp_uploads_image_has_transparency', (bool) $imagick->getImageAlphaChannel(), 'webp-uploads' ); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this set here in the cache since this function runs first and then later
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it works but is definitely brittle. The other approach I can think of is using a global variable or a transient with a short expiration time to store the transparency status and hash of any uploaded file for the current request, and then using it in the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If these are both part of the same request, perhaps you could apply a filter inside the webp_uploads_filter_image_editor_output_format function, then add a filter here to set the value? |
||
| } | ||
|
|
||
| return $file; | ||
| } | ||
| add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_check_image_transparency' ); | ||
| add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_check_image_transparency' ); | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make more sense to move this Site Health test to the Modern Image Formats plugin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just seeing this comment. I think it makes sense to move all Site Health tests for images to the plugin. See #1781