Repository: shssoichiro/oxipng
Pull request #499 (2023-04-17 05:05:42)

Include palette and transparency size in evaluations
This is super minor but I started it back in January (in relation to #485) and thought I might as well open a PR for it.

For very small data, including the palette size in evaluations can sometimes achieve a better result.

issue-52-02 master: 1379 bytes
issue-52-02 PR: 1171 bytes

issue-52-03 master: 1555 bytes
issue-52-03 PR: 1315 bytes

issue-52-06 master: 249 bytes
issue-52-06 PR: 231 bytes (apparently 32bpp can be smaller than 2bpp!!)

I also tested this on the images mentioned in #418 and found oxipng now gives a better overall result than leanify.

andrews05: On hold until #500 lands. I might make it account for tRNS also.
andrews05: Reopened with added logic for transparency, which can help with even smaller data such as issue-52-06.

Patch:
diff --git a/src/evaluate.rs b/src/evaluate.rs
index 23cc9832..ee92b17a 100644
--- a/src/evaluate.rs
+++ b/src/evaluate.rs
@@ -30,7 +30,7 @@ pub struct Candidate {
 impl Candidate {
     fn cmp_key(&self) -> impl Ord {
         (
-            self.image.idat_data.len(),
+            self.image.estimated_output_size(),
             self.image.raw.data.len(),
             self.image.raw.ihdr.bit_depth,
             self.filter,
@@ -134,7 +134,6 @@ impl Evaluator {
                 if let Ok(idat_data) =
                     deflate::deflate(&filtered, compression, &best_candidate_size)
                 {
-                    best_candidate_size.set_min(idat_data.len());
                     let new = Candidate {
                         image: PngData {
                             idat_data,
@@ -145,6 +144,7 @@ impl Evaluator {
                         is_reduction,
                         nth,
                     };
+                    best_candidate_size.set_min(new.image.estimated_output_size());
 
                     #[cfg(feature = "parallel")]
                     {
diff --git a/src/png/mod.rs b/src/png/mod.rs
index 32870581..dd37f69c 100644
--- a/src/png/mod.rs
+++ b/src/png/mod.rs
@@ -134,6 +134,27 @@ impl PngData {
         })
     }
 
+    /// Return an estimate of the output size
+    pub fn estimated_output_size(&self) -> usize {
+        // Add the size of the PLTE and tRNS chunks to the compressed idat size
+        // This can help with evaluation of very small data
+        let size = self.idat_data.len();
+        size + match &self.raw.ihdr.color_type {
+            ColorType::Indexed { palette } => {
+                let plte = 12 + palette.len() * 3;
+                let trns = palette.iter().filter(|p| p.a != 255).count();
+                if trns != 0 {
+                    plte + 12 + trns
+                } else {
+                    plte
+                }
+            }
+            ColorType::Grayscale { transparent_shade } if transparent_shade.is_some() => 12 + 2,
+            ColorType::RGB { transparent_color } if transparent_color.is_some() => 12 + 6,
+            _ => 0,
+        }
+    }
+
     /// Format the `PngData` struct into a valid PNG bytestream
     pub fn output(&self) -> Vec<u8> {
         // PNG header


Test patch:
diff --git a/tests/regression.rs b/tests/regression.rs
index 06833b25..a161a3bf 100644
--- a/tests/regression.rs
+++ b/tests/regression.rs
@@ -147,7 +147,7 @@ fn issue_52_02() {
         None,
         RGBA,
         BitDepth::Eight,
-        INDEXED,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -159,7 +159,7 @@ fn issue_52_03() {
         None,
         RGBA,
         BitDepth::Eight,
-        INDEXED,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -195,8 +195,8 @@ fn issue_52_06() {
         None,
         RGBA,
         BitDepth::Eight,
-        INDEXED,
-        BitDepth::Two,
+        RGBA,
+        BitDepth::Eight,
     );
 }