Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
ae7f22d
Add support for AVIF transparency checks during image uploads
b1ink0 Oct 30, 2025
a1aff66
Improve Imagick version string regex validation
b1ink0 Nov 2, 2025
be0817b
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Nov 13, 2025
3fe6954
Add site health checks for Imagick AVIF transparency support
b1ink0 Nov 14, 2025
2247b61
Add custom image editor class to help with AVIF transparency detection
b1ink0 Dec 17, 2025
1a58d1c
Move Imagick AVIF transparency support site health test
b1ink0 Dec 26, 2025
fdf65ec
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Dec 26, 2025
50f2be1
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Jan 13, 2026
f8af1e9
Allow `webp_uploads_get_upload_image_mime_transforms()` to omit the f…
b1ink0 Jan 13, 2026
991d7c9
Add explicit return value check for `getImageAlphaChannel` method
b1ink0 Jan 15, 2026
fece741
Add transparency check caching to custom image editor class
b1ink0 Jan 20, 2026
9020b24
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Jan 20, 2026
435f5ff
Refactor to use filename instead of file hash for transparency check
b1ink0 Jan 20, 2026
4da1758
Fix transparency detection to exclude fully opaque pixels
b1ink0 Jan 20, 2026
717e768
Fix image editor confilict with Dominant Color Images
b1ink0 Jan 21, 2026
c033c44
Dynimacally extend the image editor class
b1ink0 Jan 21, 2026
b28885e
Fix parameters order in `is_subclass_of`
b1ink0 Jan 22, 2026
6004d18
Simplify conditional checks
b1ink0 Jan 22, 2026
d218d0f
Fix transparency detection for fully transparent images
b1ink0 Jan 22, 2026
41f94e1
Improve doc comments
b1ink0 Jan 22, 2026
28f2583
Skip unnecessary transparency check when image format is already WebP
b1ink0 Jan 22, 2026
b421017
Improve doc comments
b1ink0 Jan 22, 2026
433ea1c
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Jan 29, 2026
b00d2d0
Add unit tests for AVIF transparency detection
adamsilverstein Jan 29, 2026
28d6020
Enhance transparency detection logic
b1ink0 Jan 29, 2026
4cf8302
Improve image editor selection logic
b1ink0 Jan 30, 2026
a28415f
Merge pull request #1 from WordPress/fix/imagemagick-old-version-avif…
b1ink0 Feb 2, 2026
99a2704
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Feb 2, 2026
92f6528
Ensure helper class is loaded
b1ink0 Feb 2, 2026
23f528b
Add filter to transparency check function
b1ink0 Feb 9, 2026
06f67f7
Improve test code coverage
b1ink0 Feb 9, 2026
4e1ac64
Update test plugin GitHub action to use `tests-wordpress` instead of …
b1ink0 Feb 12, 2026
fec8109
Update commands to use `tests-wordpress` instead of `tests-cli`
b1ink0 Feb 12, 2026
50e74ad
Update all commands to usee `tests-wordpress`
b1ink0 Feb 12, 2026
d8acc81
Fix failing tests
b1ink0 Feb 12, 2026
0e0ee09
Fix failing tests on PHP 7.2
b1ink0 Feb 13, 2026
23e12d5
Fix failing tests on PHP 8.1
b1ink0 Feb 13, 2026
1045006
Finally fix failing tests on PHP 8.1
b1ink0 Feb 13, 2026
789906c
Use `method_exists` instead of `is_callable`
b1ink0 Feb 16, 2026
8140089
Return null when file property does not exist
b1ink0 Feb 16, 2026
2c50ed7
Improve conditional checks
b1ink0 Feb 16, 2026
d01f2d6
Narrow down types
b1ink0 Feb 16, 2026
315d6c3
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Feb 16, 2026
3190c5b
Use fallback for more failure conditions
b1ink0 Feb 17, 2026
f0beb09
Improve test code coverage
b1ink0 Feb 25, 2026
346a61e
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Feb 25, 2026
3f55c70
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Feb 27, 2026
59d39dc
Only consider default channels for mean calculation
b1ink0 Mar 4, 2026
be178ec
Merge branch 'trunk' into fix/imagemagick-old-version-avif-transparen…
b1ink0 Apr 1, 2026
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
26 changes: 26 additions & 0 deletions plugins/webp-uploads/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@ function webp_uploads_mime_type_supported( string $mime_type ): bool {
* @return string The image output format. One of 'webp' or 'avif'.
*/
function webp_uploads_get_image_output_format(): string {
if ( ! webp_uploads_imagick_avif_transparency_supported() && (bool) wp_cache_get( 'webp_uploads_image_has_transparency', 'webp-uploads' ) ) {
wp_cache_delete( 'webp_uploads_image_has_transparency', 'webp-uploads' );
return 'webp';
}

$image_format = get_option( 'perflab_modern_image_format' );
return webp_uploads_sanitize_image_format( $image_format );
}
Expand Down Expand Up @@ -512,3 +517,24 @@ function webp_uploads_get_attachment_file_mime_type( int $attachment_id, string
$mime_type = $filetype['type'] ?? get_post_mime_type( $attachment_id );
return is_string( $mime_type ) ? $mime_type : '';
}

/**
* Checks if Imagick has AVIF transparency support.
*
* @since n.e.x.t
*
* @return bool True if Imagick has AVIF transparency support, false otherwise.
*/
function webp_uploads_imagick_avif_transparency_supported(): bool {
if ( extension_loaded( 'imagick' ) && class_exists( 'Imagick' ) ) {
$imagick_version = Imagick::getVersion();
if ( (bool) preg_match( '/((?:[0-9]+\\.?)+)/', $imagick_version['versionString'], $matches ) ) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ( (bool) preg_match( '/((?:[0-9]+\\.?)+)/', $imagick_version['versionString'], $matches ) ) {
if ( (bool) preg_match( '/^\d+(?:\.\d+)+$/', $imagick_version['versionString'], $matches ) ) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated regex to /\d+(?:\.\d+)+(?:-\d+)?/ for handling version string like this:

ImageMagick 7.1.1-15 Q16 aarch64 98eceff6a:20230729 https://imagemagick.org

$imagick_version = $matches[0];
} else {
$imagick_version = $imagick_version['versionString'];
}
return version_compare( $imagick_version, '7.0.25', '>=' );
}

return false;
}
50 changes: 50 additions & 0 deletions plugins/webp-uploads/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's too bad that the image property is protected. Note how in the Image Placeholder plugin it actually extends WP_Image_Editor_Imagick with a Dominant_Color_Image_Editor_Imagick which it then uses. This is problematic though since multiple plugins can't each register their own editor classes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 image property, but the Reflection API seemed better.


if ( $imagick instanceof Imagick ) {
wp_cache_set( 'webp_uploads_image_has_transparency', (bool) $imagick->getImageAlphaChannel(), 'webp-uploads' );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 webp_uploads_get_image_output_format() runs to then read the value from the cache? This seems perhaps brittle. Normally setting and getting a cache value would happen in the context of the same function, not across separate functions, right? It feels like there may not be guarantees that the cached value would be set when it is checked for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 webp_uploads_filter_image_editor_output_format to determine the output format. If you have any ideas for a temporary storage that can be used within the same request, I’d appreciate that.
@adamsilverstein if you also have a better idea to handle this case, I’d appreciate that as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are multiple approaches. I came up with this to solve it:
The most elegant way is to replace the WP_Image_Editor_Imagick class with a custom class WebP_Uploads_Image_Editor_Imagick, which can be used to detect transparency and provide access to the protected variable.

But this will interfere with other plugins if they are trying to use their own custom class. I was thinking of just dynamically extending any custom classes extended from the WP_Image_Editor_Imagick class.

Copy link
Copy Markdown
Contributor Author

@b1ink0 b1ink0 Dec 17, 2025

Choose a reason for hiding this comment

The 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?

The issue I faced was with the subsize-image generation. The webp_uploads_filter_image_editor_output_format filter is called with an empty filename, so with this approach the sub-size images were getting generated as AVIF.

Because of that, the only way to check the current processing image calling the output-format filter is to access the $image of the image-editor instance that is processing it. But since the core does not expose the image-editor instance through globals or filters, there seems to be no way to determine the currently processed image. To solve this, the only idea I could come up with was using our own custom Image Editor class.

I have implemented the mentioned approach in this commit 2247b61.

cc: @adamsilverstein

}

return $file;
}
add_filter( 'wp_handle_upload_prefilter', 'webp_uploads_check_image_transparency' );
add_filter( 'wp_handle_sideload_prefilter', 'webp_uploads_check_image_transparency' );