From 287fd501f24de1b4a0852fdbf052c06d5ad6907a Mon Sep 17 00:00:00 2001 From: Anthony Burchell Date: Wed, 19 Jul 2023 22:33:47 +0000 Subject: [PATCH] Media: Adjust PDF upload handling to remove non-opaque alpha channels from previews. Previously, Imagick uploads of PDF files with non-opaque alpha channels would result in a black background replacing alpha in the generated thumbnail. This patch adds a `remove_pdf_alpha_channel()` function in the Imagick classes to use a white background instead. Props gitlost, joemcgill, joedolson, launchinteractive, emirpprime, mwtsn, ceer, maysi, madejackson, 6adminit, costdev, oglekler. Fixes #39216. git-svn-id: https://develop.svn.wordpress.org/trunk@56271 602fd350-edb4-49c9-b593-d223f7449a82 --- .../class-wp-image-editor-imagick.php | 22 ++++++++ tests/phpunit/data/images/test-alpha.pdf | Bin 0 -> 7175 bytes tests/phpunit/tests/image/editorImagick.php | 50 ++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 tests/phpunit/data/images/test-alpha.pdf diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php index 756fcce894..f29373f253 100644 --- a/src/wp-includes/class-wp-image-editor-imagick.php +++ b/src/wp-includes/class-wp-image-editor-imagick.php @@ -168,6 +168,10 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor { $this->image->setIteratorIndex( 0 ); } + if ( 'pdf' === $file_extension ) { + $this->remove_pdf_alpha_channel(); + } + $this->mime_type = $this->get_mime_type( $this->image->getImageFormat() ); } catch ( Exception $e ) { return new WP_Error( 'invalid_image', $e->getMessage(), $this->file ); @@ -751,6 +755,24 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor { return $saved; } + /** + * Removes PDF alpha after it's been read. + * + * @since 6.4.0 + */ + protected function remove_pdf_alpha_channel() { + $version = Imagick::getVersion(); + // Remove alpha channel if possible to avoid black backgrounds for Ghostscript >= 9.14. RemoveAlphaChannel added in ImageMagick 6.7.5. + if ( $version['versionNumber'] >= 0x675 ) { + try { + // Imagick::ALPHACHANNEL_REMOVE mapped to RemoveAlphaChannel in PHP imagick 3.2.0b2. + $this->image->setImageAlphaChannel( defined( 'Imagick::ALPHACHANNEL_REMOVE' ) ? Imagick::ALPHACHANNEL_REMOVE : 12 ); + } catch ( Exception $e ) { + return new WP_Error( 'pdf_alpha_process_failed', $e->getMessage() ); + } + } + } + /** * @since 3.5.0 * @since 6.0.0 The `$filesize` value was added to the returned array. diff --git a/tests/phpunit/data/images/test-alpha.pdf b/tests/phpunit/data/images/test-alpha.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1cbd4686e7e2374e835d34c70f2dfdea2775f4fd GIT binary patch literal 7175 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 zR8(xxcWlGT3assertNotWPError( $saved ); } + + /** + * Tests that the alpha channel of PDFs is removed from PDF previews. + * + * Only affects systems with Ghostscript version >= 9.14. + * + * @ticket 39216 + * + * @covers WP_Image_Editor_Imagick::remove_pdf_alpha_channel + */ + public function test_remove_pdf_alpha_channel_should_remove_the_alpha_channel_in_preview() { + if ( ! wp_image_editor_supports( array( 'mime_type' => 'application/pdf' ) ) ) { + $this->markTestSkipped( 'Rendering PDFs is not supported on this system.' ); + } + + $test_file = DIR_TESTDATA . '/images/test-alpha.pdf'; + $attachment_id = $this->factory->attachment->create_upload_object( $test_file ); + $this->assertNotEmpty( $attachment_id, 'The attachment was not created before testing.' ); + + $attached_file = get_attached_file( $attachment_id ); + $this->assertNotEmpty( $attached_file, 'The attached file was not returned.' ); + + $rgb = array( + 'r' => true, + 'g' => true, + 'b' => true, + ); + + // White. + $expected = array( + 'r' => 1, + 'g' => 1, + 'b' => 1, + ); + + $check = image_get_intermediate_size( $attachment_id, 'full' ); + $this->assertIsArray( $check, 'The intermediate size could not be retrieved.' ); + $this->assertArrayHasKey( 'file', $check, 'The intermediate size file was not found.' ); + + $check_file = path_join( dirname( $attached_file ), $check['file'] ); + $imagick = new Imagick( $check_file ); + $output = array_map( + static function( $value ) { + return (int) round( $value ); + }, + array_intersect_key( $imagick->getImagePixelColor( 100, 100 )->getColor( true /* normalized */ ), $rgb ) + ); + $imagick->destroy(); + $this->assertSame( $expected, $output, 'The image color of the generated thumb does not match expected opaque background.' ); // Allow for floating point equivalence. + } }