<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[59588] trunk: Media: enable high bit depth resized image output with Imagick.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/59588">59588</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/59588","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>adamsilverstein</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2025-01-07 21:04:35 +0000 (Tue, 07 Jan 2025)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Media: enable high bit depth resized image output with Imagick.

Fix an issue where uploaded HDR images were resized and output as SDR and thus significantly degraded from the original. When using Imagick, output images will now match the bit depth of the uploaded image.

Add a new filter 'image_max_bit_depth' which developers can use to control the maximum bit depth for resized images.

Props adamsilverstein, kirasong, gregbenz, apermo.
Fixes <a href="https://core.trac.wordpress.org/ticket/62285">#62285</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpimageeditorimagickphp">trunk/src/wp-includes/class-wp-image-editor-imagick.php</a></li>
<li><a href="#trunktestsphpunittestsimageeditorImagickphp">trunk/tests/phpunit/tests/image/editorImagick.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpimageeditorimagickphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-image-editor-imagick.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-image-editor-imagick.php   2025-01-07 16:13:20 UTC (rev 59587)
+++ trunk/src/wp-includes/class-wp-image-editor-imagick.php     2025-01-07 21:04:35 UTC (rev 59588)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -503,11 +503,23 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                }
</span><span class="cx" style="display: block; padding: 0 10px">                        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        // Limit the bit depth of resized images to 8 bits per channel.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 // Limit the bit depth of resized images.
</ins><span class="cx" style="display: block; padding: 0 10px">                         if ( is_callable( array( $this->image, 'getImageDepth' ) ) && is_callable( array( $this->image, 'setImageDepth' ) ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                if ( 8 < $this->image->getImageDepth() ) {
-                                       $this->image->setImageDepth( 8 );
-                               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         /**
+                                * Filters the maximum bit depth of resized images.
+                                *
+                                * This filter only applies when resizing using the Imagick editor since GD
+                                * does not support getting or setting bit depth.
+                                *
+                                * Use this to adjust the maximum bit depth of resized images.
+                                *
+                                * @since 6.8.0
+                                *
+                                * @param int $max_depth   The maximum bit depth. Default is the input depth.
+                                * @param int $image_depth The bit depth of the original image.
+                                */
+                               $max_depth = apply_filters( 'image_max_bit_depth', $this->image->getImageDepth(), $this->image->getImageDepth() );
+                               $this->image->setImageDepth( $max_depth );
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                } catch ( Exception $e ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        return new WP_Error( 'image_resize_error', $e->getMessage() );
</span></span></pre></div>
<a id="trunktestsphpunittestsimageeditorImagickphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/image/editorImagick.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/image/editorImagick.php 2025-01-07 16:13:20 UTC (rev 59587)
+++ trunk/tests/phpunit/tests/image/editorImagick.php   2025-01-07 21:04:35 UTC (rev 59588)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -691,4 +691,71 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $imagick->destroy();
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertSame( $expected, $output, 'The image color of the generated thumb does not match expected opaque background.' ); // Allow for floating point equivalence.
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * Test filter `image_max_bit_depth` correctly sets the maximum bit depth of resized images.
+        *
+        * @ticket 62285
+        */
+       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' ) ) {
+                       $this->markTestSkipped( 'The image editor does not support the AVIF mime type.' );
+               }
+
+               // Skip if depth methods not available.
+               if ( ! method_exists( 'Imagick', 'getImageDepth' ) || ! method_exists( 'Imagick', 'setImageDepth' ) ) {
+                       $this->markTestSkipped( 'The image editor does not support get or setImageDepth.' );
+               }
+
+               // Verify source image has 10-bit depth.
+               $imagick = new Imagick( $file );
+               $this->assertSame( 10, $imagick->getImageDepth() );
+
+               // Test ability to save 10-bit image.
+               $imagick->setImageDepth( 10 );
+               $test_file = tempnam( get_temp_dir(), '' ) . 'test10.avif';
+               $imagick->writeImage( $test_file );
+               $im = new Imagick( $test_file );
+
+               if ( $im->getImageDepth() !== 10 ) {
+                       $this->markTestSkipped( 'Imagick is unable to save a 10 bit image.' );
+               }
+               $im->destroy();
+               unlink( $test_file );
+
+               // Test default behavior preserves 10-bit depth.
+               $imagick_image_editor->load();
+               $imagick_image_editor->resize( 100, 50 );
+               $test_file = tempnam( get_temp_dir(), '' ) . 'test1.avif';
+               $imagick_image_editor->save( $test_file );
+               $im = new Imagick( $test_file );
+               $this->assertSame( 10, $im->getImageDepth() );
+               unlink( $test_file );
+               $im->destroy();
+
+               // Test filter can set 8-bit depth
+               add_filter( 'image_max_bit_depth', array( $this, '__return_eight' ) );
+               $imagick_image_editor = new WP_Image_Editor_Imagick( $file );
+               $imagick_image_editor->load();
+               $imagick_image_editor->resize( 100, 50 );
+               $test_file = tempnam( get_temp_dir(), '' ) . 'test2.avif';
+               $imagick_image_editor->save( $test_file );
+               $im = new Imagick( $test_file );
+               $this->assertSame( 8, $im->getImageDepth() );
+               unlink( $test_file );
+               $im->destroy();
+       }
+
+       /**
+        * Helper function to return 8 for the `image_max_bit_depth` filter.
+        *
+        * @return int
+        */
+       public function __return_eight() {
+               return 8;
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>