Repository: shssoichiro/oxipng
Pull request #500 (2023-04-20 06:09:58)

ColorType Refactor
This PR refactors the `ColorType` enum to incorporate both the palette and transparency data, moving them out of `PngImage`. This doesn't really simplify anything but the intention is to improve the interfaces that will end up being public as part of the raw API (see discussion in #482).

`BitDepth` has also been changed slightly to work with the underlying u8 representation along with a `TryFrom` implementation, which allows some simplifications.

Lastly, I've added some convenience functions `ColorType::is_rgb()`, `ColorType::has_alpha()` and `PngImage::bytes_per_channel()` to allow more tidy and consistent code in some places.

The main annoyance is it is not as easy to make assertions about colour types in the tests. My solution here was to define some constants in the test files to compare the header code rather the enum itself.

Ping @AlexTMjugador.

shssoichiro: Thanks, looks good, just one small request re. clarity/documentation.

Patch:
diff --git a/src/colors.rs b/src/colors.rs
index e339092b..32999849 100644
--- a/src/colors.rs
+++ b/src/colors.rs
@@ -1,14 +1,26 @@
+use rgb::{RGB16, RGBA8};
 use std::fmt;
 
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+use crate::PngError;
+
+#[derive(Debug, PartialEq, Eq, Clone)]
 /// The color type used to represent this image
 pub enum ColorType {
     /// Grayscale, with one color channel
-    Grayscale,
+    Grayscale {
+        /// Optional shade of gray that should be rendered as transparent
+        transparent_shade: Option<u16>,
+    },
     /// RGB, with three color channels
-    RGB,
-    /// Indexed, with one byte per pixel representing one of up to 256 colors in the image
-    Indexed,
+    RGB {
+        /// Optional color value that should be rendered as transparent
+        transparent_color: Option<RGB16>,
+    },
+    /// Indexed, with one byte per pixel representing a color from the palette
+    Indexed {
+        /// The palette containing the colors used, up to 256 entries
+        palette: Vec<RGBA8>,
+    },
     /// Grayscale + Alpha, with two color channels
     GrayscaleAlpha,
     /// RGBA, with four color channels
@@ -22,9 +34,9 @@ impl fmt::Display for ColorType {
             f,
             "{}",
             match *self {
-                ColorType::Grayscale => "Grayscale",
-                ColorType::RGB => "RGB",
-                ColorType::Indexed => "Indexed",
+                ColorType::Grayscale { .. } => "Grayscale",
+                ColorType::RGB { .. } => "RGB",
+                ColorType::Indexed { .. } => "Indexed",
                 ColorType::GrayscaleAlpha => "Grayscale + Alpha",
                 ColorType::RGBA => "RGB + Alpha",
             }
@@ -35,85 +47,71 @@ impl fmt::Display for ColorType {
 impl ColorType {
     /// Get the code used by the PNG specification to denote this color type
     #[inline]
-    pub fn png_header_code(self) -> u8 {
+    pub fn png_header_code(&self) -> u8 {
         match self {
-            ColorType::Grayscale => 0,
-            ColorType::RGB => 2,
-            ColorType::Indexed => 3,
+            ColorType::Grayscale { .. } => 0,
+            ColorType::RGB { .. } => 2,
+            ColorType::Indexed { .. } => 3,
             ColorType::GrayscaleAlpha => 4,
             ColorType::RGBA => 6,
         }
     }
 
     #[inline]
-    pub fn channels_per_pixel(self) -> u8 {
+    pub fn channels_per_pixel(&self) -> u8 {
         match self {
-            ColorType::Grayscale | ColorType::Indexed => 1,
+            ColorType::Grayscale { .. } | ColorType::Indexed { .. } => 1,
             ColorType::GrayscaleAlpha => 2,
-            ColorType::RGB => 3,
+            ColorType::RGB { .. } => 3,
             ColorType::RGBA => 4,
         }
     }
+
+    #[inline]
+    pub fn is_rgb(&self) -> bool {
+        matches!(self, ColorType::RGB { .. } | ColorType::RGBA)
+    }
+
+    #[inline]
+    pub fn has_alpha(&self) -> bool {
+        matches!(self, ColorType::GrayscaleAlpha | ColorType::RGBA)
+    }
 }
 
+#[repr(u8)]
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
 /// The number of bits to be used per channel per pixel
 pub enum BitDepth {
     /// One bit per channel per pixel
-    One,
+    One = 1,
     /// Two bits per channel per pixel
-    Two,
+    Two = 2,
     /// Four bits per channel per pixel
-    Four,
+    Four = 4,
     /// Eight bits per channel per pixel
-    Eight,
+    Eight = 8,
     /// Sixteen bits per channel per pixel
-    Sixteen,
+    Sixteen = 16,
 }
 
-impl fmt::Display for BitDepth {
-    #[inline]
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(
-            f,
-            "{}",
-            match *self {
-                BitDepth::One => "1",
-                BitDepth::Two => "2",
-                BitDepth::Four => "4",
-                BitDepth::Eight => "8",
-                BitDepth::Sixteen => "16",
-            }
-        )
-    }
-}
+impl TryFrom<u8> for BitDepth {
+    type Error = PngError;
 
-impl BitDepth {
-    /// Retrieve the number of bits per channel per pixel as a `u8`
-    #[inline]
-    pub fn as_u8(self) -> u8 {
-        match self {
-            BitDepth::One => 1,
-            BitDepth::Two => 2,
-            BitDepth::Four => 4,
-            BitDepth::Eight => 8,
-            BitDepth::Sixteen => 16,
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            1 => Ok(Self::One),
+            2 => Ok(Self::Two),
+            4 => Ok(Self::Four),
+            8 => Ok(Self::Eight),
+            16 => Ok(Self::Sixteen),
+            _ => Err(PngError::new("Unexpected bit depth")),
         }
     }
-    /// Parse a number of bits per channel per pixel into a `BitDepth`
-    ///
-    /// # Panics
-    ///
-    /// If depth is unsupported
+}
+
+impl fmt::Display for BitDepth {
     #[inline]
-    pub fn from_u8(depth: u8) -> BitDepth {
-        match depth {
-            1 => BitDepth::One,
-            2 => BitDepth::Two,
-            4 => BitDepth::Four,
-            8 => BitDepth::Eight,
-            16 => BitDepth::Sixteen,
-            _ => panic!("Unsupported bit depth"),
-        }
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", *self as u8)
     }
 }
diff --git a/src/headers.rs b/src/headers.rs
index 7fdeef1e..a3e2b4f9 100644
--- a/src/headers.rs
+++ b/src/headers.rs
@@ -4,10 +4,11 @@ use crate::error::PngError;
 use crate::interlace::Interlacing;
 use crate::PngResult;
 use indexmap::IndexSet;
+use rgb::{RGB16, RGBA8};
 use std::io;
 use std::io::{Cursor, Read};
 
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone)]
 /// Headers from the IHDR chunk of the image
 pub struct IhdrData {
     /// The width of the image in pixels
@@ -30,8 +31,8 @@ impl IhdrData {
     /// Bits per pixel
     #[must_use]
     #[inline]
-    pub fn bpp(&self) -> u8 {
-        self.bit_depth.as_u8() * self.color_type.channels_per_pixel()
+    pub fn bpp(&self) -> usize {
+        self.bit_depth as usize * self.color_type.channels_per_pixel() as usize
     }
 
     /// Byte length of IDAT that is correct for this IHDR
@@ -41,8 +42,8 @@ impl IhdrData {
         let h = self.height as usize;
         let bpp = self.bpp();
 
-        fn bitmap_size(bpp: u8, w: usize, h: usize) -> usize {
-            (((w / 8) * bpp as usize) + ((w & 7) * bpp as usize + 7) / 8) * h
+        fn bitmap_size(bpp: usize, w: usize, h: usize) -> usize {
+            ((w * bpp + 7) / 8) * h
         }
 
         if self.interlaced == Interlacing::None {
@@ -143,27 +144,36 @@ pub fn parse_next_header<'a>(
     Ok(Some(RawHeader { name, data }))
 }
 
-pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
+pub fn parse_ihdr_header(
+    byte_data: &[u8],
+    palette_data: Option<Vec<u8>>,
+    trns_data: Option<Vec<u8>>,
+) -> PngResult<IhdrData> {
     // This eliminates bounds checks for the rest of the function
     let interlaced = byte_data.get(12).copied().ok_or(PngError::TruncatedData)?;
     let mut rdr = Cursor::new(&byte_data[0..8]);
     Ok(IhdrData {
         color_type: match byte_data[9] {
-            0 => ColorType::Grayscale,
-            2 => ColorType::RGB,
-            3 => ColorType::Indexed,
+            0 => ColorType::Grayscale {
+                transparent_shade: trns_data
+                    .filter(|t| t.len() >= 2)
+                    .map(|t| u16::from_be_bytes([t[0], t[1]])),
+            },
+            2 => ColorType::RGB {
+                transparent_color: trns_data.filter(|t| t.len() >= 6).map(|t| RGB16 {
+                    r: u16::from_be_bytes([t[0], t[1]]),
+                    g: u16::from_be_bytes([t[2], t[3]]),
+                    b: u16::from_be_bytes([t[4], t[5]]),
+                }),
+            },
+            3 => ColorType::Indexed {
+                palette: palette_to_rgba(palette_data, trns_data).unwrap_or_default(),
+            },
             4 => ColorType::GrayscaleAlpha,
             6 => ColorType::RGBA,
             _ => return Err(PngError::new("Unexpected color type in header")),
         },
-        bit_depth: match byte_data[8] {
-            1 => BitDepth::One,
-            2 => BitDepth::Two,
-            4 => BitDepth::Four,
-            8 => BitDepth::Eight,
-            16 => BitDepth::Sixteen,
-            _ => return Err(PngError::new("Unexpected bit depth in header")),
-        },
+        bit_depth: byte_data[8].try_into()?,
         width: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
         height: read_be_u32(&mut rdr).map_err(|_| PngError::TruncatedData)?,
         compression: byte_data[10],
@@ -172,6 +182,25 @@ pub fn parse_ihdr_header(byte_data: &[u8]) -> PngResult<IhdrData> {
     })
 }
 
+/// Construct an RGBA palette from the raw palette and transparency data
+fn palette_to_rgba(
+    palette_data: Option<Vec<u8>>,
+    trns_data: Option<Vec<u8>>,
+) -> Result<Vec<RGBA8>, PngError> {
+    let palette_data = palette_data.ok_or_else(|| PngError::new("no palette in indexed image"))?;
+    let mut palette: Vec<_> = palette_data
+        .chunks(3)
+        .map(|color| RGBA8::new(color[0], color[1], color[2], 255))
+        .collect();
+
+    if let Some(trns_data) = trns_data {
+        for (color, trns) in palette.iter_mut().zip(trns_data) {
+            color.a = trns;
+        }
+    }
+    Ok(palette)
+}
+
 #[inline]
 fn read_be_u32<T: AsRef<[u8]>>(rdr: &mut Cursor<T>) -> Result<u32, io::Error> {
     let mut int_buf = [0; 4];
diff --git a/src/interlace.rs b/src/interlace.rs
index 8ac0fdc7..88c146ff 100644
--- a/src/interlace.rs
+++ b/src/interlace.rs
@@ -45,11 +45,11 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
         let bit_vec = line.data.view_bits::<Msb0>();
         for (i, bit) in bit_vec.iter().by_vals().enumerate() {
             // Avoid moving padded 0's into new image
-            if i >= (png.ihdr.width * u32::from(bits_per_pixel)) as usize {
+            if i >= (png.ihdr.width as usize * bits_per_pixel) {
                 break;
             }
             // Copy pixels into interlaced passes
-            let pix_modulo = (i / bits_per_pixel as usize) % 8;
+            let pix_modulo = (i / bits_per_pixel) % 8;
             match index % 8 {
                 0 => match pix_modulo {
                     0 => passes[0].push(bit),
@@ -87,12 +87,11 @@ pub fn interlace_image(png: &PngImage) -> PngImage {
     PngImage {
         data: output,
         ihdr: IhdrData {
+            color_type: png.ihdr.color_type.clone(),
             interlaced: Interlacing::Adam7,
             ..png.ihdr
         },
         aux_headers: png.aux_headers.clone(),
-        palette: png.palette.clone(),
-        transparency_pixel: png.transparency_pixel.clone(),
     }
 }
 
@@ -103,19 +102,18 @@ pub fn deinterlace_image(png: &PngImage) -> PngImage {
             _ => deinterlace_bits(png),
         },
         ihdr: IhdrData {
+            color_type: png.ihdr.color_type.clone(),
             interlaced: Interlacing::None,
             ..png.ihdr
         },
         aux_headers: png.aux_headers.clone(),
-        palette: png.palette.clone(),
-        transparency_pixel: png.transparency_pixel.clone(),
     }
 }
 
 /// Deinterlace by bits, for images with less than 8bpp
 fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
     let bits_per_pixel = png.ihdr.bpp();
-    let bits_per_line = bits_per_pixel as usize * png.ihdr.width as usize;
+    let bits_per_line = bits_per_pixel * png.ihdr.width as usize;
     // Initialize each output line with blank data
     let mut lines: Vec<BitVec<u8, Msb0>> =
         vec![bitvec![u8, Msb0; 0; bits_per_line]; png.ihdr.height as usize];
@@ -128,22 +126,22 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
             + u32::from(pass_constants.x_step)
             - 1)
             / u32::from(pass_constants.x_step)) as usize
-            * bits_per_pixel as usize;
+            * bits_per_pixel;
         for (i, bit) in bit_vec.iter().by_vals().enumerate() {
             // Avoid moving padded 0's into new image
             if i >= bits_in_line {
                 break;
             }
             let current_x: usize = pass_constants.x_shift as usize
-                + (i / bits_per_pixel as usize) * pass_constants.x_step as usize;
+                + (i / bits_per_pixel) * pass_constants.x_step as usize;
             // Copy this bit into the output line
-            let index = (i % bits_per_pixel as usize) + current_x * bits_per_pixel as usize;
+            let index = (i % bits_per_pixel) + current_x * bits_per_pixel;
             lines[current_y].set(index, bit);
         }
         // Calculate the next line and move to next pass if necessary
         current_y += pass_constants.y_step as usize;
         if current_y >= png.ihdr.height as usize {
-            if !increment_pass(&mut current_pass, png.ihdr) {
+            if !increment_pass(&mut current_pass, &png.ihdr) {
                 break;
             }
             pass_constants = interlaced_constants(current_pass);
@@ -163,7 +161,7 @@ fn deinterlace_bits(png: &PngImage) -> Vec<u8> {
 /// Deinterlace by bytes, for images with at least 8bpp
 fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
     let bytes_per_pixel = png.ihdr.bpp() / 8;
-    let bytes_per_line = bytes_per_pixel as usize * png.ihdr.width as usize;
+    let bytes_per_line = bytes_per_pixel * png.ihdr.width as usize;
     // Initialize each output line with some blank data
     let mut lines: Vec<Vec<u8>> = vec![vec![0; bytes_per_line]; png.ihdr.height as usize];
     let mut current_pass = 1;
@@ -172,15 +170,15 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
     for line in png.scan_lines(false) {
         for (i, byte) in line.data.iter().enumerate() {
             let current_x: usize = pass_constants.x_shift as usize
-                + (i / bytes_per_pixel as usize) * pass_constants.x_step as usize;
+                + (i / bytes_per_pixel) * pass_constants.x_step as usize;
             // Copy this byte into the output line
-            let index = (i % bytes_per_pixel as usize) + current_x * bytes_per_pixel as usize;
+            let index = (i % bytes_per_pixel) + current_x * bytes_per_pixel;
             lines[current_y][index] = *byte;
         }
         // Calculate the next line and move to next pass if necessary
         current_y += pass_constants.y_step as usize;
         if current_y >= png.ihdr.height as usize {
-            if !increment_pass(&mut current_pass, png.ihdr) {
+            if !increment_pass(&mut current_pass, &png.ihdr) {
                 break;
             }
             pass_constants = interlaced_constants(current_pass);
@@ -190,7 +188,7 @@ fn deinterlace_bytes(png: &PngImage) -> Vec<u8> {
     lines.concat()
 }
 
-fn increment_pass(current_pass: &mut u8, ihdr: IhdrData) -> bool {
+fn increment_pass(current_pass: &mut u8, ihdr: &IhdrData) -> bool {
     if *current_pass == 7 {
         return false;
     }
diff --git a/src/lib.rs b/src/lib.rs
index 0f5b637f..d88746b7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,7 +25,7 @@ extern crate rayon;
 mod rayon;
 
 use crate::atomicmin::AtomicMin;
-use crate::colors::BitDepth;
+use crate::colors::{BitDepth, ColorType};
 use crate::deflate::{crc32, inflate};
 use crate::evaluate::Evaluator;
 use crate::png::PngData;
@@ -569,7 +569,7 @@ fn optimize_png(
 
             if filters.is_empty() {
                 // Pick a filter automatically
-                if png.raw.ihdr.bit_depth.as_u8() >= 8 {
+                if png.raw.ihdr.bit_depth as u8 >= 8 {
                     // Bigrams is the best all-rounder when there's at least one byte per pixel
                     filters.insert(RowFilter::Bigrams);
                 } else {
@@ -814,7 +814,7 @@ impl Deadline {
 
 /// Display the format of the image data
 fn report_format(prefix: &str, png: &PngImage) {
-    if let Some(ref palette) = png.palette {
+    if let ColorType::Indexed { palette } = &png.ihdr.color_type {
         debug!(
             "{}{} bits/pixel, {} colors in palette ({})",
             prefix,
diff --git a/src/png/mod.rs b/src/png/mod.rs
index f3d712d0..32870581 100644
--- a/src/png/mod.rs
+++ b/src/png/mod.rs
@@ -1,4 +1,4 @@
-use crate::colors::ColorType;
+use crate::colors::{BitDepth, ColorType};
 use crate::deflate;
 use crate::error::PngError;
 use crate::filters::*;
@@ -8,7 +8,6 @@ use bitvec::bitarr;
 use indexmap::IndexMap;
 use libdeflater::{CompressionLvl, Compressor};
 use rgb::ComponentSlice;
-use rgb::RGBA8;
 use rustc_hash::FxHashMap;
 use std::fs::File;
 use std::io::{BufReader, Read, Write};
@@ -31,11 +30,6 @@ pub struct PngImage {
     pub ihdr: IhdrData,
     /// The uncompressed, unfiltered data from the IDAT chunk
     pub data: Vec<u8>,
-    /// The palette containing colors used in an Indexed image
-    /// Contains 3 bytes per color (R+G+B), up to 768
-    pub palette: Option<Vec<RGBA8>>,
-    /// The pixel value that should be rendered as transparent
-    pub transparency_pixel: Option<Vec<u8>>,
     /// All non-critical headers from the PNG are stored here
     pub aux_headers: IndexMap<[u8; 4], Vec<u8>>,
 }
@@ -51,8 +45,6 @@ pub struct PngData {
     pub filtered: Vec<u8>,
 }
 
-type PaletteWithTrns = (Option<Vec<RGBA8>>, Option<Vec<u8>>);
-
 impl PngData {
     /// Create a new `PngData` struct by opening a file
     #[inline]
@@ -116,7 +108,11 @@ impl PngData {
             Some(ihdr) => ihdr,
             None => return Err(PngError::ChunkMissing("IHDR")),
         };
-        let ihdr_header = parse_ihdr_header(&ihdr)?;
+        let ihdr_header = parse_ihdr_header(
+            &ihdr,
+            aux_headers.remove(b"PLTE"),
+            aux_headers.remove(b"tRNS"),
+        )?;
         let raw_data = deflate::inflate(idat_headers.as_ref(), ihdr_header.raw_data_size())?;
 
         // Reject files with incorrect width/height or truncated data
@@ -124,17 +120,9 @@ impl PngData {
             return Err(PngError::TruncatedData);
         }
 
-        let (palette, transparency_pixel) = Self::palette_to_rgba(
-            ihdr_header.color_type,
-            aux_headers.remove(b"PLTE"),
-            aux_headers.remove(b"tRNS"),
-        )?;
-
         let mut raw = PngImage {
             ihdr: ihdr_header,
             data: raw_data,
-            palette,
-            transparency_pixel,
             aux_headers,
         };
         let unfiltered = raw.unfilter_image()?;
@@ -146,31 +134,6 @@ impl PngData {
         })
     }
 
-    /// Handle transparency header
-    fn palette_to_rgba(
-        color_type: ColorType,
-        palette_data: Option<Vec<u8>>,
-        trns_data: Option<Vec<u8>>,
-    ) -> Result<PaletteWithTrns, PngError> {
-        if color_type == ColorType::Indexed {
-            let palette_data =
-                palette_data.ok_or_else(|| PngError::new("no palette in indexed image"))?;
-            let mut palette: Vec<_> = palette_data
-                .chunks(3)
-                .map(|color| RGBA8::new(color[0], color[1], color[2], 255))
-                .collect();
-
-            if let Some(trns_data) = trns_data {
-                for (color, trns) in palette.iter_mut().zip(trns_data) {
-                    color.a = trns;
-                }
-            }
-            Ok((Some(palette), None))
-        } else {
-            Ok((None, trns_data))
-        }
-    }
-
     /// Format the `PngData` struct into a valid PNG bytestream
     pub fn output(&self) -> Vec<u8> {
         // PNG header
@@ -181,7 +144,7 @@ impl PngData {
         ihdr_data
             .write_all(&self.raw.ihdr.height.to_be_bytes())
             .ok();
-        ihdr_data.write_all(&[self.raw.ihdr.bit_depth.as_u8()]).ok();
+        ihdr_data.write_all(&[self.raw.ihdr.bit_depth as u8]).ok();
         ihdr_data
             .write_all(&[self.raw.ihdr.color_type.png_header_code()])
             .ok();
@@ -198,40 +161,49 @@ impl PngData {
         {
             write_png_block(key, header, &mut output);
         }
-        // Palette
-        if let Some(ref palette) = self.raw.palette {
-            let mut palette_data = Vec::with_capacity(palette.len() * 3);
-            let mut max_palette_size = 1 << (self.raw.ihdr.bit_depth.as_u8() as usize);
-            // Ensure bKGD color doesn't get truncated from palette
-            if let Some(&idx) = self.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
-                max_palette_size = max_palette_size.max(idx as usize + 1);
+        // Palette and transparency
+        match &self.raw.ihdr.color_type {
+            ColorType::Indexed { palette } => {
+                let mut palette_data = Vec::with_capacity(palette.len() * 3);
+                let mut max_palette_size = 1 << (self.raw.ihdr.bit_depth as u8);
+                // Ensure bKGD color doesn't get truncated from palette
+                if let Some(&idx) = self.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
+                    max_palette_size = max_palette_size.max(idx as usize + 1);
+                }
+                for px in palette.iter().take(max_palette_size) {
+                    palette_data.extend_from_slice(px.rgb().as_slice());
+                }
+                write_png_block(b"PLTE", &palette_data, &mut output);
+                let num_transparent = palette.iter().take(max_palette_size).enumerate().fold(
+                    0,
+                    |prev, (index, px)| {
+                        if px.a == 255 {
+                            prev
+                        } else {
+                            index + 1
+                        }
+                    },
+                );
+                if num_transparent > 0 {
+                    let trns_data: Vec<_> =
+                        palette[0..num_transparent].iter().map(|px| px.a).collect();
+                    write_png_block(b"tRNS", &trns_data, &mut output);
+                }
             }
-            for px in palette.iter().take(max_palette_size) {
-                palette_data.extend_from_slice(px.rgb().as_slice());
+            ColorType::Grayscale {
+                transparent_shade: Some(trns),
+            } => {
+                // Transparency pixel - 2 byte u16
+                write_png_block(b"tRNS", &trns.to_be_bytes(), &mut output);
             }
-            write_png_block(b"PLTE", &palette_data, &mut output);
-            let num_transparent =
-                palette
-                    .iter()
-                    .take(max_palette_size)
-                    .enumerate()
-                    .fold(
-                        0,
-                        |prev, (index, px)| {
-                            if px.a == 255 {
-                                prev
-                            } else {
-                                index + 1
-                            }
-                        },
-                    );
-            if num_transparent > 0 {
-                let trns_data: Vec<_> = palette[0..num_transparent].iter().map(|px| px.a).collect();
+            ColorType::RGB {
+                transparent_color: Some(trns),
+            } => {
+                // Transparency pixel - 6 byte RGB16
+                let trns_data: Vec<_> = trns.iter().flat_map(|c| c.to_be_bytes()).collect();
                 write_png_block(b"tRNS", &trns_data, &mut output);
             }
-        } else if let Some(ref transparency_pixel) = self.raw.transparency_pixel {
-            // Transparency pixel
-            write_png_block(b"tRNS", transparency_pixel, &mut output);
+            _ => {}
         }
         // Special ancillary headers that need to come after PLTE but before IDAT
         for (key, header) in self
@@ -274,8 +246,18 @@ impl PngImage {
 
     /// Return the number of channels in the image, based on color type
     #[inline]
-    pub fn channels_per_pixel(&self) -> u8 {
-        self.ihdr.color_type.channels_per_pixel()
+    pub fn channels_per_pixel(&self) -> usize {
+        self.ihdr.color_type.channels_per_pixel() as usize
+    }
+
+    /// Return the number of bytes per channel in the image
+    #[inline]
+    pub fn bytes_per_channel(&self) -> usize {
+        match self.ihdr.bit_depth {
+            BitDepth::Sixteen => 2,
+            // Depths lower than 8 will round up to 1 byte
+            _ => 1,
+        }
     }
 
     /// Return an iterator over the scanlines of the image
@@ -287,7 +269,7 @@ impl PngImage {
     /// Reverse all filters applied on the image, returning an unfiltered IDAT bytestream
     fn unfilter_image(&self) -> Result<Vec<u8>, PngError> {
         let mut unfiltered = Vec::with_capacity(self.data.len());
-        let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
+        let bpp = self.bytes_per_channel() * self.channels_per_pixel();
         let mut last_line: Vec<u8> = Vec::new();
         let mut last_pass = None;
         let mut unfiltered_buf = Vec::new();
@@ -309,13 +291,12 @@ impl PngImage {
     /// Apply the specified filter type to all rows in the image
     pub fn filter_image(&self, filter: RowFilter, optimize_alpha: bool) -> Vec<u8> {
         let mut filtered = Vec::with_capacity(self.data.len());
-        let bpp = ((self.ihdr.bit_depth.as_u8() * self.channels_per_pixel() + 7) / 8) as usize;
+        let bpp = self.bytes_per_channel() * self.channels_per_pixel();
         // If alpha optimization is enabled, determine how many bytes of alpha there are per pixel
-        let alpha_bytes = match self.ihdr.color_type {
-            ColorType::RGBA | ColorType::GrayscaleAlpha if optimize_alpha => {
-                (self.ihdr.bit_depth.as_u8() / 8) as usize
-            }
-            _ => 0,
+        let alpha_bytes = if optimize_alpha && self.ihdr.color_type.has_alpha() {
+            self.bytes_per_channel()
+        } else {
+            0
         };
 
         let mut prev_line = Vec::new();
diff --git a/src/png/scan_lines.rs b/src/png/scan_lines.rs
index 509373b2..1c612f89 100644
--- a/src/png/scan_lines.rs
+++ b/src/png/scan_lines.rs
@@ -43,7 +43,7 @@ impl<'a> Iterator for ScanLines<'a> {
 struct ScanLineRanges {
     /// Current pass number, and 0-indexed row within the pass
     pass: Option<(u8, u32)>,
-    bits_per_pixel: u8,
+    bits_per_pixel: usize,
     width: u32,
     height: u32,
     left: usize,
@@ -53,7 +53,7 @@ struct ScanLineRanges {
 impl ScanLineRanges {
     pub fn new(png: &PngImage, has_filter: bool) -> Self {
         Self {
-            bits_per_pixel: png.ihdr.bit_depth.as_u8() * png.channels_per_pixel(),
+            bits_per_pixel: png.ihdr.bpp(),
             width: png.ihdr.width,
             height: png.ihdr.height,
             left: png.data.len(),
@@ -143,8 +143,8 @@ impl Iterator for ScanLineRanges {
             // Standard, non-interlaced PNG scanlines
             (self.width, None)
         };
-        let bits_per_line = pixels_per_line * u32::from(self.bits_per_pixel);
-        let mut len = ((bits_per_line + 7) / 8) as usize;
+        let bits_per_line = pixels_per_line as usize * self.bits_per_pixel;
+        let mut len = (bits_per_line + 7) / 8;
         if self.has_filter {
             len += 1;
         }
diff --git a/src/reduction/alpha.rs b/src/reduction/alpha.rs
index cf8c22ad..4b9fd439 100644
--- a/src/reduction/alpha.rs
+++ b/src/reduction/alpha.rs
@@ -1,23 +1,21 @@
+use rgb::RGB16;
+
 use crate::colors::{BitDepth, ColorType};
 use crate::headers::IhdrData;
 use crate::png::PngImage;
 
 /// Clean the alpha channel by setting the color of all fully transparent pixels to black
 pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
-    let (bpc, bpp) = match png.ihdr.color_type {
-        ColorType::RGBA | ColorType::GrayscaleAlpha => {
-            let cpp = png.channels_per_pixel();
-            let bpc = png.ihdr.bit_depth.as_u8() / 8;
-            (bpc as usize, (bpc * cpp) as usize)
-        }
-        _ => {
-            return None;
-        }
-    };
+    if !png.ihdr.color_type.has_alpha() {
+        return None;
+    }
+    let byte_depth = png.bytes_per_channel();
+    let bpp = png.channels_per_pixel() * byte_depth;
+    let colored_bytes = bpp - byte_depth;
 
     let mut reduced = Vec::with_capacity(png.data.len());
     for pixel in png.data.chunks(bpp) {
-        if pixel.iter().skip(bpp - bpc).all(|b| *b == 0) {
+        if pixel.iter().skip(colored_bytes).all(|b| *b == 0) {
             reduced.resize(reduced.len() + bpp, 0);
         } else {
             reduced.extend_from_slice(pixel);
@@ -26,23 +24,18 @@ pub fn cleaned_alpha_channel(png: &PngImage) -> Option<PngImage> {
 
     Some(PngImage {
         data: reduced,
-        ihdr: png.ihdr,
-        palette: png.palette.clone(),
-        transparency_pixel: png.transparency_pixel.clone(),
+        ihdr: png.ihdr.clone(),
         aux_headers: png.aux_headers.clone(),
     })
 }
 
 #[must_use]
 pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
-    let target_color_type = match png.ihdr.color_type {
-        ColorType::GrayscaleAlpha => ColorType::Grayscale,
-        ColorType::RGBA => ColorType::RGB,
-        _ => return None,
-    };
-    let byte_depth = (png.ihdr.bit_depth.as_u8() >> 3) as usize;
-    let channels = png.channels_per_pixel() as usize;
-    let bpp = channels * byte_depth;
+    if !png.ihdr.color_type.has_alpha() {
+        return None;
+    }
+    let byte_depth = png.bytes_per_channel();
+    let bpp = png.channels_per_pixel() * byte_depth;
     let colored_bytes = bpp - byte_depth;
 
     // If alpha optimisation is enabled, see if the image contains only fully opaque and fully transparent pixels.
@@ -66,13 +59,7 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
 
     let transparency_pixel = if has_transparency {
         // If no unused color was found we will have to fail here
-        // Otherwise, proceed to construct the tRNS chunk
-        let unused_color = used_colors.iter().position(|b| !*b)? as u8;
-        Some(match png.ihdr.bit_depth {
-            BitDepth::Sixteen => vec![unused_color; colored_bytes],
-            // 8-bit is still stored as 16-bit, with the high byte set to 0
-            _ => [0, unused_color].repeat(colored_bytes),
-        })
+        Some(used_colors.iter().position(|b| !*b)? as u8)
     } else {
         None
     };
@@ -80,13 +67,27 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
     let mut raw_data = Vec::with_capacity(png.data.len());
     for pixel in png.data.chunks(bpp) {
         match transparency_pixel {
-            Some(ref trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
-                raw_data.resize(raw_data.len() + colored_bytes, trns[1]);
+            Some(trns) if pixel.iter().skip(colored_bytes).all(|b| *b == 0) => {
+                raw_data.resize(raw_data.len() + colored_bytes, trns);
             }
             _ => raw_data.extend_from_slice(&pixel[0..colored_bytes]),
         };
     }
 
+    // Construct the color type with appropriate transparency data
+    let transparent = transparency_pixel.map(|trns| match png.ihdr.bit_depth {
+        BitDepth::Sixteen => (trns as u16) << 8 | trns as u16,
+        _ => trns as u16,
+    });
+    let target_color_type = match png.ihdr.color_type {
+        ColorType::GrayscaleAlpha => ColorType::Grayscale {
+            transparent_shade: transparent,
+        },
+        _ => ColorType::RGB {
+            transparent_color: transparent.map(|t| RGB16::new(t, t, t)),
+        },
+    };
+
     let mut aux_headers = png.aux_headers.clone();
     // sBIT contains information about alpha channel's original depth,
     // and alpha has just been removed
@@ -102,7 +103,5 @@ pub fn reduced_alpha_channel(png: &PngImage, optimize_alpha: bool) -> Option<Png
             ..png.ihdr
         },
         aux_headers,
-        transparency_pixel,
-        palette: None,
     })
 }
diff --git a/src/reduction/bit_depth.rs b/src/reduction/bit_depth.rs
index 8ee5ca0e..948de473 100644
--- a/src/reduction/bit_depth.rs
+++ b/src/reduction/bit_depth.rs
@@ -2,13 +2,11 @@ use crate::colors::{BitDepth, ColorType};
 use crate::headers::IhdrData;
 use crate::png::PngImage;
 
-/// Attempt to reduce the bit depth of the image
-/// Returns true if the bit depth was reduced, false otherwise
+/// Attempt to reduce the bit depth of the image, returning the reduced image if successful
 #[must_use]
 pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage> {
     if png.ihdr.bit_depth != BitDepth::Sixteen {
-        if png.ihdr.color_type == ColorType::Indexed || png.ihdr.color_type == ColorType::Grayscale
-        {
+        if png.channels_per_pixel() == 1 {
             return reduce_bit_depth_8_or_less(png, minimum_bits);
         }
         return None;
@@ -23,11 +21,10 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
     Some(PngImage {
         data: png.data.iter().step_by(2).cloned().collect(),
         ihdr: IhdrData {
+            color_type: png.ihdr.color_type.clone(),
             bit_depth: BitDepth::Eight,
             ..png.ihdr
         },
-        palette: None,
-        transparency_pixel: png.transparency_pixel.clone(),
         aux_headers: png.aux_headers.clone(),
     })
 }
@@ -35,14 +32,14 @@ pub fn reduce_bit_depth(png: &PngImage, minimum_bits: usize) -> Option<PngImage>
 #[must_use]
 pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Option<PngImage> {
     assert!((1..8).contains(&minimum_bits));
-    let bit_depth: usize = png.ihdr.bit_depth.as_u8() as usize;
+    let bit_depth = png.ihdr.bit_depth as usize;
     if minimum_bits >= bit_depth || bit_depth > 8 {
         return None;
     }
     // Calculate the current number of pixels per byte
     let ppb = 8 / bit_depth;
 
-    if png.ihdr.color_type == ColorType::Indexed {
+    if let ColorType::Indexed { .. } = png.ihdr.color_type {
         for line in png.scan_lines(false) {
             let line_max = line
                 .data
@@ -129,12 +126,11 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
     }
 
     // If the image is grayscale we also need to reduce the transparency pixel
-    let mut transparency_pixel = png
-        .transparency_pixel
-        .clone()
-        .filter(|t| png.ihdr.color_type == ColorType::Grayscale && t.len() >= 2);
-    if let Some(trans) = transparency_pixel {
-        let reduced_trans = trans[1] >> (bit_depth - minimum_bits);
+    let color_type = if let ColorType::Grayscale {
+        transparent_shade: Some(trans),
+    } = png.ihdr.color_type
+    {
+        let reduced_trans = (trans & 0xFF) >> (bit_depth - minimum_bits);
         // Verify the reduction is valid by restoring back to original bit depth
         let mut check = reduced_trans;
         let mut bits = minimum_bits;
@@ -142,22 +138,25 @@ pub fn reduce_bit_depth_8_or_less(png: &PngImage, mut minimum_bits: usize) -> Op
             check = check << bits | check;
             bits <<= 1;
         }
-        if trans[0] == 0 && trans[1] == check {
-            transparency_pixel = Some(vec![0, reduced_trans]);
-        } else {
-            // The transparency doesn't fit the new bit depth and is therefore unused - set it to None
-            transparency_pixel = None;
+        // If the transparency doesn't fit the new bit depth it is therefore unused - set it to None
+        ColorType::Grayscale {
+            transparent_shade: if trans == check {
+                Some(reduced_trans)
+            } else {
+                None
+            },
         }
-    }
+    } else {
+        png.ihdr.color_type.clone()
+    };
 
     Some(PngImage {
         data: reduced,
         ihdr: IhdrData {
-            bit_depth: BitDepth::from_u8(minimum_bits as u8),
+            color_type,
+            bit_depth: (minimum_bits as u8).try_into().unwrap(),
             ..png.ihdr
         },
         aux_headers: png.aux_headers.clone(),
-        palette: png.palette.clone(),
-        transparency_pixel,
     })
 }
diff --git a/src/reduction/color.rs b/src/reduction/color.rs
index 5e7135f8..e8d5e73f 100644
--- a/src/reduction/color.rs
+++ b/src/reduction/color.rs
@@ -2,7 +2,7 @@ use crate::colors::{BitDepth, ColorType};
 use crate::headers::IhdrData;
 use crate::png::PngImage;
 use indexmap::IndexMap;
-use rgb::{FromSlice, RGB8, RGBA, RGBA8};
+use rgb::{ComponentMap, FromSlice, RGBA, RGBA8};
 use rustc_hash::FxHasher;
 use std::hash::{BuildHasherDefault, Hash};
 
@@ -41,12 +41,9 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
     let mut raw_data = Vec::with_capacity(png.data.len());
     let mut palette = FxIndexMap::default();
     palette.reserve(257);
-    let transparency_pixel = png
-        .transparency_pixel
-        .as_ref()
-        .filter(|t| png.ihdr.color_type == ColorType::RGB && t.len() >= 6)
-        .map(|t| RGB8::new(t[1], t[3], t[5]));
-    let ok = if png.ihdr.color_type == ColorType::RGB {
+    let ok = if let ColorType::RGB { transparent_color } = png.ihdr.color_type {
+        // Convert the RGB16 transparency to RGB8
+        let transparency_pixel = transparent_color.map(|t| t.map(|c| c as u8));
         reduce_scanline_to_palette(
             png.data.as_rgb().iter().cloned().map(|px| {
                 px.alpha(if Some(px) != transparency_pixel {
@@ -132,20 +129,20 @@ pub fn reduce_to_palette(png: &PngImage) -> Option<PngImage> {
     Some(PngImage {
         data: raw_data,
         ihdr: IhdrData {
-            color_type: ColorType::Indexed,
+            color_type: ColorType::Indexed {
+                palette: palette_vec,
+            },
             ..png.ihdr
         },
         aux_headers,
-        transparency_pixel: None,
-        palette: Some(palette_vec),
     })
 }
 
 #[must_use]
 pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
     let mut reduced = Vec::with_capacity(png.data.len());
-    let byte_depth = png.ihdr.bit_depth.as_u8() as usize >> 3;
-    let bpp = png.channels_per_pixel() as usize * byte_depth;
+    let byte_depth = png.bytes_per_channel();
+    let bpp = png.channels_per_pixel() * byte_depth;
     let last_color = 2 * byte_depth;
     for pixel in png.data.chunks(bpp) {
         if byte_depth == 1 {
@@ -158,16 +155,6 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
         reduced.extend_from_slice(&pixel[last_color..]);
     }
 
-    let transparency_pixel = if let Some(ref trns) = png.transparency_pixel {
-        if trns.len() != 6 || trns[0..2] != trns[2..4] || trns[2..4] != trns[4..6] {
-            None
-        } else {
-            Some(trns[0..2].to_owned())
-        }
-    } else {
-        png.transparency_pixel.clone()
-    };
-
     let mut aux_headers = png.aux_headers.clone();
     if let Some(sbit_header) = png.aux_headers.get(b"sBIT") {
         if let Some(&byte) = sbit_header.first() {
@@ -180,17 +167,22 @@ pub fn reduce_rgb_to_grayscale(png: &PngImage) -> Option<PngImage> {
         }
     }
 
+    let color_type = match png.ihdr.color_type {
+        ColorType::RGB { transparent_color } => ColorType::Grayscale {
+            // Copy the transparent component if it is also gray
+            transparent_shade: transparent_color
+                .filter(|t| t.r == t.g && t.g == t.b)
+                .map(|t| t.r),
+        },
+        _ => ColorType::GrayscaleAlpha,
+    };
+
     Some(PngImage {
         data: reduced,
         ihdr: IhdrData {
-            color_type: match png.ihdr.color_type {
-                ColorType::RGBA => ColorType::GrayscaleAlpha,
-                _ => ColorType::Grayscale,
-            },
+            color_type,
             ..png.ihdr
         },
         aux_headers,
-        palette: None,
-        transparency_pixel,
     })
 }
diff --git a/src/reduction/mod.rs b/src/reduction/mod.rs
index defd0eab..8154189c 100644
--- a/src/reduction/mod.rs
+++ b/src/reduction/mod.rs
@@ -18,10 +18,11 @@ pub(crate) use crate::bit_depth::reduce_bit_depth;
 /// Attempt to reduce the number of colors in the palette
 /// Returns `None` if palette hasn't changed
 pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage> {
-    if png.ihdr.color_type != ColorType::Indexed {
+    let palette = match &png.ihdr.color_type {
+        ColorType::Indexed { palette } => palette,
         // Can't reduce if there is no palette
-        return None;
-    }
+        _ => return None,
+    };
     if png.ihdr.bit_depth == BitDepth::One {
         // Gains from 1-bit images will be at most 1 byte
         // Not worth the CPU time
@@ -31,8 +32,6 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
     let mut palette_map = [None; 256];
     let mut used = [false; 256];
     {
-        let palette = png.palette.as_ref()?;
-
         // Find palette entries that are never used
         match png.ihdr.bit_depth {
             BitDepth::Eight => {
@@ -109,11 +108,15 @@ pub fn reduced_palette(png: &PngImage, optimize_alpha: bool) -> Option<PngImage>
         }
     }
 
-    do_palette_reduction(png, &palette_map)
+    do_palette_reduction(png, palette, &palette_map)
 }
 
 #[must_use]
-fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Option<PngImage> {
+fn do_palette_reduction(
+    png: &PngImage,
+    palette: &[RGBA8],
+    palette_map: &[Option<u8>; 256],
+) -> Option<PngImage> {
     let byte_map = palette_map_to_byte_map(png, palette_map)?;
 
     // Reassign data bytes to new indices
@@ -131,12 +134,12 @@ fn do_palette_reduction(png: &PngImage, palette_map: &[Option<u8>; 256]) -> Opti
 
     Some(PngImage {
         ihdr: IhdrData {
-            color_type: ColorType::Indexed,
+            color_type: ColorType::Indexed {
+                palette: reordered_palette(palette, palette_map),
+            },
             ..png.ihdr
         },
         data: raw_data,
-        transparency_pixel: None,
-        palette: Some(reordered_palette(png.palette.as_ref()?, palette_map)),
         aux_headers,
     })
 }
@@ -187,22 +190,20 @@ fn reordered_palette(palette: &[RGBA8], palette_map: &[Option<u8>; 256]) -> Vec<
     new_palette
 }
 
-/// Attempt to reduce the color type of the image
-/// Returns true if the color type was reduced, false otherwise
+/// Attempt to reduce the color type of the image, returning the reduced image if successful
 pub fn reduce_color_type(
     png: &PngImage,
     grayscale_reduction: bool,
     optimize_alpha: bool,
 ) -> Option<PngImage> {
-    let mut should_reduce_bit_depth = false;
+    let was_single_channel = png.channels_per_pixel() == 1;
     let mut reduced = Cow::Borrowed(png);
 
-    // Go down one step at a time
-    // Maybe not the most efficient, but it's safe
-    if grayscale_reduction && matches!(reduced.ihdr.color_type, ColorType::RGBA | ColorType::RGB) {
+    // Go down one step at a time - maybe not the most efficient, but it's safe
+    // Attempt to reduce RGB to grayscale
+    if grayscale_reduction && reduced.ihdr.color_type.is_rgb() {
         if let Some(r) = reduce_rgb_to_grayscale(&reduced) {
             reduced = Cow::Owned(r);
-            should_reduce_bit_depth = reduced.ihdr.color_type == ColorType::Grayscale;
         }
     }
 
@@ -210,17 +211,13 @@ pub fn reduce_color_type(
     if reduced.ihdr.color_type == ColorType::GrayscaleAlpha {
         if let Some(r) = reduced_alpha_channel(&reduced, optimize_alpha) {
             reduced = Cow::Owned(r);
-            should_reduce_bit_depth = true;
         }
     }
 
-    if matches!(
-        reduced.ihdr.color_type,
-        ColorType::RGBA | ColorType::RGB | ColorType::GrayscaleAlpha
-    ) {
+    // Attempt to reduce to palette, if not already a single channel
+    if reduced.channels_per_pixel() != 1 {
         if let Some(r) = reduce_to_palette(&reduced) {
             reduced = Cow::Owned(r);
-            should_reduce_bit_depth = true;
 
             // Make sure that palette gets sorted. Ideally, this should be done within reduce_to_palette.
             if let Some(r) = reduced_palette(&reduced, optimize_alpha) {
@@ -236,9 +233,8 @@ pub fn reduce_color_type(
         }
     }
 
-    if should_reduce_bit_depth {
-        // Some conversions will allow us to perform bit depth reduction that
-        // wasn't possible before
+    // Some conversions will allow us to perform bit depth reduction that wasn't possible before
+    if !was_single_channel && reduced.channels_per_pixel() == 1 {
         if let Some(r) = reduce_bit_depth_8_or_less(&reduced, 1) {
             reduced = Cow::Owned(r);
         }


Test patch:
diff --git a/tests/filters.rs b/tests/filters.rs
index e1dbad40..065c1259 100644
--- a/tests/filters.rs
+++ b/tests/filters.rs
@@ -5,6 +5,12 @@ use std::fs::remove_file;
 use std::path::Path;
 use std::path::PathBuf;
 
+const GRAYSCALE: u8 = 0;
+const RGB: u8 = 2;
+const INDEXED: u8 = 3;
+const GRAYSCALE_ALPHA: u8 = 4;
+const RGBA: u8 = 6;
+
 fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
     let mut options = oxipng::Options {
         force: true,
@@ -23,9 +29,9 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
 fn test_it_converts(
     input: &str,
     filter: RowFilter,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
 ) {
     let input = PathBuf::from(input);
@@ -34,7 +40,7 @@ fn test_it_converts(
     let png = PngData::new(&input, opts.fix_errors).unwrap();
     opts.filter = IndexSet::new();
     opts.filter.insert(filter);
-    assert_eq!(png.raw.ihdr.color_type, color_type_in);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
@@ -52,12 +58,10 @@ fn test_it_converts(
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_out);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
-    if let Some(palette) = png.raw.palette.as_ref() {
-        assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
-    } else {
-        assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
     }
 
     remove_file(output).ok();
@@ -68,9 +72,9 @@ fn filter_0_for_rgba_16() {
     test_it_converts(
         "tests/files/filter_0_for_rgba_16.png",
         RowFilter::None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -80,9 +84,9 @@ fn filter_1_for_rgba_16() {
     test_it_converts(
         "tests/files/filter_1_for_rgba_16.png",
         RowFilter::Sub,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -92,9 +96,9 @@ fn filter_2_for_rgba_16() {
     test_it_converts(
         "tests/files/filter_2_for_rgba_16.png",
         RowFilter::Up,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -104,9 +108,9 @@ fn filter_3_for_rgba_16() {
     test_it_converts(
         "tests/files/filter_3_for_rgba_16.png",
         RowFilter::Average,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -116,9 +120,9 @@ fn filter_4_for_rgba_16() {
     test_it_converts(
         "tests/files/filter_4_for_rgba_16.png",
         RowFilter::Paeth,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -128,9 +132,9 @@ fn filter_5_for_rgba_16() {
     test_it_converts(
         "tests/files/filter_5_for_rgba_16.png",
         RowFilter::MinSum,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -140,9 +144,9 @@ fn filter_0_for_rgba_8() {
     test_it_converts(
         "tests/files/filter_0_for_rgba_8.png",
         RowFilter::None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -152,9 +156,9 @@ fn filter_1_for_rgba_8() {
     test_it_converts(
         "tests/files/filter_1_for_rgba_8.png",
         RowFilter::Sub,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -164,9 +168,9 @@ fn filter_2_for_rgba_8() {
     test_it_converts(
         "tests/files/filter_2_for_rgba_8.png",
         RowFilter::Up,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -176,9 +180,9 @@ fn filter_3_for_rgba_8() {
     test_it_converts(
         "tests/files/filter_3_for_rgba_8.png",
         RowFilter::Average,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -188,9 +192,9 @@ fn filter_4_for_rgba_8() {
     test_it_converts(
         "tests/files/filter_4_for_rgba_8.png",
         RowFilter::Paeth,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -200,9 +204,9 @@ fn filter_5_for_rgba_8() {
     test_it_converts(
         "tests/files/filter_5_for_rgba_8.png",
         RowFilter::MinSum,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -212,9 +216,9 @@ fn filter_0_for_rgb_16() {
     test_it_converts(
         "tests/files/filter_0_for_rgb_16.png",
         RowFilter::None,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -224,9 +228,9 @@ fn filter_1_for_rgb_16() {
     test_it_converts(
         "tests/files/filter_1_for_rgb_16.png",
         RowFilter::Sub,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -236,9 +240,9 @@ fn filter_2_for_rgb_16() {
     test_it_converts(
         "tests/files/filter_2_for_rgb_16.png",
         RowFilter::Up,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -248,9 +252,9 @@ fn filter_3_for_rgb_16() {
     test_it_converts(
         "tests/files/filter_3_for_rgb_16.png",
         RowFilter::Average,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -260,9 +264,9 @@ fn filter_4_for_rgb_16() {
     test_it_converts(
         "tests/files/filter_4_for_rgb_16.png",
         RowFilter::Paeth,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -272,9 +276,9 @@ fn filter_5_for_rgb_16() {
     test_it_converts(
         "tests/files/filter_5_for_rgb_16.png",
         RowFilter::MinSum,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -284,9 +288,9 @@ fn filter_0_for_rgb_8() {
     test_it_converts(
         "tests/files/filter_0_for_rgb_8.png",
         RowFilter::None,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -296,9 +300,9 @@ fn filter_1_for_rgb_8() {
     test_it_converts(
         "tests/files/filter_1_for_rgb_8.png",
         RowFilter::Sub,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -308,9 +312,9 @@ fn filter_2_for_rgb_8() {
     test_it_converts(
         "tests/files/filter_2_for_rgb_8.png",
         RowFilter::Up,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -320,9 +324,9 @@ fn filter_3_for_rgb_8() {
     test_it_converts(
         "tests/files/filter_3_for_rgb_8.png",
         RowFilter::Average,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -332,9 +336,9 @@ fn filter_4_for_rgb_8() {
     test_it_converts(
         "tests/files/filter_4_for_rgb_8.png",
         RowFilter::Paeth,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -344,9 +348,9 @@ fn filter_5_for_rgb_8() {
     test_it_converts(
         "tests/files/filter_5_for_rgb_8.png",
         RowFilter::MinSum,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -356,9 +360,9 @@ fn filter_0_for_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/filter_0_for_grayscale_alpha_16.png",
         RowFilter::None,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -368,9 +372,9 @@ fn filter_1_for_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/filter_1_for_grayscale_alpha_16.png",
         RowFilter::Sub,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -380,9 +384,9 @@ fn filter_2_for_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/filter_2_for_grayscale_alpha_16.png",
         RowFilter::Up,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -392,9 +396,9 @@ fn filter_3_for_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/filter_3_for_grayscale_alpha_16.png",
         RowFilter::Average,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -404,9 +408,9 @@ fn filter_4_for_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/filter_4_for_grayscale_alpha_16.png",
         RowFilter::Paeth,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -416,9 +420,9 @@ fn filter_5_for_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/filter_5_for_grayscale_alpha_16.png",
         RowFilter::MinSum,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -428,9 +432,9 @@ fn filter_0_for_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/filter_0_for_grayscale_alpha_8.png",
         RowFilter::None,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -440,9 +444,9 @@ fn filter_1_for_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/filter_1_for_grayscale_alpha_8.png",
         RowFilter::Sub,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -452,9 +456,9 @@ fn filter_2_for_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/filter_2_for_grayscale_alpha_8.png",
         RowFilter::Up,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -464,9 +468,9 @@ fn filter_3_for_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/filter_3_for_grayscale_alpha_8.png",
         RowFilter::Average,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -476,9 +480,9 @@ fn filter_4_for_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/filter_4_for_grayscale_alpha_8.png",
         RowFilter::Paeth,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -488,9 +492,9 @@ fn filter_5_for_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/filter_5_for_grayscale_alpha_8.png",
         RowFilter::MinSum,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -500,9 +504,9 @@ fn filter_0_for_grayscale_16() {
     test_it_converts(
         "tests/files/filter_0_for_grayscale_16.png",
         RowFilter::None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -512,9 +516,9 @@ fn filter_1_for_grayscale_16() {
     test_it_converts(
         "tests/files/filter_1_for_grayscale_16.png",
         RowFilter::Sub,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -524,9 +528,9 @@ fn filter_2_for_grayscale_16() {
     test_it_converts(
         "tests/files/filter_2_for_grayscale_16.png",
         RowFilter::Up,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -536,9 +540,9 @@ fn filter_3_for_grayscale_16() {
     test_it_converts(
         "tests/files/filter_3_for_grayscale_16.png",
         RowFilter::Average,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -548,9 +552,9 @@ fn filter_4_for_grayscale_16() {
     test_it_converts(
         "tests/files/filter_4_for_grayscale_16.png",
         RowFilter::Paeth,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -560,9 +564,9 @@ fn filter_5_for_grayscale_16() {
     test_it_converts(
         "tests/files/filter_5_for_grayscale_16.png",
         RowFilter::MinSum,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -572,9 +576,9 @@ fn filter_0_for_grayscale_8() {
     test_it_converts(
         "tests/files/filter_0_for_grayscale_8.png",
         RowFilter::None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -584,9 +588,9 @@ fn filter_1_for_grayscale_8() {
     test_it_converts(
         "tests/files/filter_1_for_grayscale_8.png",
         RowFilter::Sub,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -596,9 +600,9 @@ fn filter_2_for_grayscale_8() {
     test_it_converts(
         "tests/files/filter_2_for_grayscale_8.png",
         RowFilter::Up,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -608,9 +612,9 @@ fn filter_3_for_grayscale_8() {
     test_it_converts(
         "tests/files/filter_3_for_grayscale_8.png",
         RowFilter::Average,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -620,9 +624,9 @@ fn filter_4_for_grayscale_8() {
     test_it_converts(
         "tests/files/filter_4_for_grayscale_8.png",
         RowFilter::Paeth,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -632,9 +636,9 @@ fn filter_5_for_grayscale_8() {
     test_it_converts(
         "tests/files/filter_5_for_grayscale_8.png",
         RowFilter::MinSum,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -644,9 +648,9 @@ fn filter_0_for_palette_4() {
     test_it_converts(
         "tests/files/filter_0_for_palette_4.png",
         RowFilter::None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -656,9 +660,9 @@ fn filter_1_for_palette_4() {
     test_it_converts(
         "tests/files/filter_1_for_palette_4.png",
         RowFilter::Sub,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -668,9 +672,9 @@ fn filter_2_for_palette_4() {
     test_it_converts(
         "tests/files/filter_2_for_palette_4.png",
         RowFilter::Up,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -680,9 +684,9 @@ fn filter_3_for_palette_4() {
     test_it_converts(
         "tests/files/filter_3_for_palette_4.png",
         RowFilter::Average,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -692,9 +696,9 @@ fn filter_4_for_palette_4() {
     test_it_converts(
         "tests/files/filter_4_for_palette_4.png",
         RowFilter::Paeth,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -704,9 +708,9 @@ fn filter_5_for_palette_4() {
     test_it_converts(
         "tests/files/filter_5_for_palette_4.png",
         RowFilter::MinSum,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -716,9 +720,9 @@ fn filter_0_for_palette_2() {
     test_it_converts(
         "tests/files/filter_0_for_palette_2.png",
         RowFilter::None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -728,9 +732,9 @@ fn filter_1_for_palette_2() {
     test_it_converts(
         "tests/files/filter_1_for_palette_2.png",
         RowFilter::Sub,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -740,9 +744,9 @@ fn filter_2_for_palette_2() {
     test_it_converts(
         "tests/files/filter_2_for_palette_2.png",
         RowFilter::Up,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -752,9 +756,9 @@ fn filter_3_for_palette_2() {
     test_it_converts(
         "tests/files/filter_3_for_palette_2.png",
         RowFilter::Average,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -764,9 +768,9 @@ fn filter_4_for_palette_2() {
     test_it_converts(
         "tests/files/filter_4_for_palette_2.png",
         RowFilter::Paeth,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -776,9 +780,9 @@ fn filter_5_for_palette_2() {
     test_it_converts(
         "tests/files/filter_5_for_palette_2.png",
         RowFilter::MinSum,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -788,9 +792,9 @@ fn filter_0_for_palette_1() {
     test_it_converts(
         "tests/files/filter_0_for_palette_1.png",
         RowFilter::None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -800,9 +804,9 @@ fn filter_1_for_palette_1() {
     test_it_converts(
         "tests/files/filter_1_for_palette_1.png",
         RowFilter::Sub,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -812,9 +816,9 @@ fn filter_2_for_palette_1() {
     test_it_converts(
         "tests/files/filter_2_for_palette_1.png",
         RowFilter::Up,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -824,9 +828,9 @@ fn filter_3_for_palette_1() {
     test_it_converts(
         "tests/files/filter_3_for_palette_1.png",
         RowFilter::Average,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -836,9 +840,9 @@ fn filter_4_for_palette_1() {
     test_it_converts(
         "tests/files/filter_4_for_palette_1.png",
         RowFilter::Paeth,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -848,9 +852,9 @@ fn filter_5_for_palette_1() {
     test_it_converts(
         "tests/files/filter_5_for_palette_1.png",
         RowFilter::MinSum,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
diff --git a/tests/flags.rs b/tests/flags.rs
index fc7ace9e..d31f70e2 100644
--- a/tests/flags.rs
+++ b/tests/flags.rs
@@ -11,6 +11,11 @@ use std::ops::Deref;
 use std::path::Path;
 use std::path::PathBuf;
 
+const GRAYSCALE: u8 = 0;
+const RGB: u8 = 2;
+const INDEXED: u8 = 3;
+const RGBA: u8 = 6;
+
 fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
     let mut options = oxipng::Options {
         force: true,
@@ -32,9 +37,9 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
     input: PathBuf,
     output: &OutFile,
     opts: &oxipng::Options,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
     mut callback_pre: CBPRE,
     mut callback_post: CBPOST,
@@ -44,7 +49,7 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
 {
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_in);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
 
     callback_pre(&input);
@@ -66,7 +71,7 @@ fn test_it_converts_callbacks<CBPRE, CBPOST>(
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_out);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
 
     remove_file(output).ok();
@@ -77,9 +82,9 @@ fn test_it_converts(
     input: PathBuf,
     output: &OutFile,
     opts: &oxipng::Options,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
 ) {
     test_it_converts_callbacks(
@@ -153,9 +158,9 @@ fn verbose_mode() {
             input,
             &output,
             &opts,
-            ColorType::RGB,
+            RGB,
             BitDepth::Eight,
-            ColorType::RGB,
+            RGB,
             BitDepth::Eight,
         );
     };
@@ -411,7 +416,7 @@ fn interlacing_0_to_1_small_files() {
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
     assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
@@ -430,7 +435,7 @@ fn interlacing_0_to_1_small_files() {
     };
 
     assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::One);
 
     remove_file(output).ok();
@@ -445,7 +450,7 @@ fn interlacing_1_to_0_small_files() {
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
     assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
@@ -464,7 +469,7 @@ fn interlacing_1_to_0_small_files() {
     };
 
     assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     // the depth can't be asserted reliably, because on such small file different zlib implementations pick different depth as the best
 
     remove_file(output).ok();
@@ -556,9 +561,9 @@ fn preserve_attrs() {
         input,
         &output,
         &opts,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
         callback_pre,
         callback_post,
@@ -575,7 +580,7 @@ fn fix_errors() {
 
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::RGBA);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), RGBA);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
@@ -593,7 +598,7 @@ fn fix_errors() {
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Grayscale);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), GRAYSCALE);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
 
     // Cannot check if pixels are equal because image crate cannot read corrupt (input) PNGs
@@ -613,9 +618,9 @@ fn zopfli_mode() {
         input,
         &output,
         &opts,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
diff --git a/tests/interlaced.rs b/tests/interlaced.rs
index 747c1253..ff884b1a 100644
--- a/tests/interlaced.rs
+++ b/tests/interlaced.rs
@@ -5,6 +5,12 @@ use std::fs::remove_file;
 use std::path::Path;
 use std::path::PathBuf;
 
+const GRAYSCALE: u8 = 0;
+const RGB: u8 = 2;
+const INDEXED: u8 = 3;
+const GRAYSCALE_ALPHA: u8 = 4;
+const RGBA: u8 = 6;
+
 fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
     let mut options = oxipng::Options {
         force: true,
@@ -22,16 +28,16 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
 
 fn test_it_converts(
     input: &str,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
 ) {
     let input = PathBuf::from(input);
     let (output, opts) = get_opts(&input);
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_in);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
     assert_eq!(png.raw.ihdr.interlaced, Interlacing::Adam7);
 
@@ -50,7 +56,7 @@ fn test_it_converts(
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_out);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
 
     remove_file(output).ok();
@@ -60,9 +66,9 @@ fn test_it_converts(
 fn interlaced_rgba_16_should_be_rgba_16() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_rgba_16.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -71,9 +77,9 @@ fn interlaced_rgba_16_should_be_rgba_16() {
 fn interlaced_rgba_16_should_be_rgba_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_rgba_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -82,9 +88,9 @@ fn interlaced_rgba_16_should_be_rgba_8() {
 fn interlaced_rgba_8_should_be_rgba_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_rgba_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -93,9 +99,9 @@ fn interlaced_rgba_8_should_be_rgba_8() {
 fn interlaced_rgba_16_should_be_rgb_16() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_rgb_16.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -104,9 +110,9 @@ fn interlaced_rgba_16_should_be_rgb_16() {
 fn interlaced_rgba_16_should_be_rgb_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_rgb_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -115,9 +121,9 @@ fn interlaced_rgba_16_should_be_rgb_8() {
 fn interlaced_rgba_8_should_be_rgb_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_rgb_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -126,9 +132,9 @@ fn interlaced_rgba_8_should_be_rgb_8() {
 fn interlaced_rgba_16_should_be_palette_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_palette_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -137,9 +143,9 @@ fn interlaced_rgba_16_should_be_palette_8() {
 fn interlaced_rgba_8_should_be_palette_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_palette_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -148,9 +154,9 @@ fn interlaced_rgba_8_should_be_palette_8() {
 fn interlaced_rgba_16_should_be_palette_4() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_palette_4.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -159,9 +165,9 @@ fn interlaced_rgba_16_should_be_palette_4() {
 fn interlaced_rgba_8_should_be_palette_4() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_palette_4.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -170,9 +176,9 @@ fn interlaced_rgba_8_should_be_palette_4() {
 fn interlaced_rgba_16_should_be_palette_2() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_palette_2.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -181,9 +187,9 @@ fn interlaced_rgba_16_should_be_palette_2() {
 fn interlaced_rgba_8_should_be_palette_2() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_palette_2.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -192,9 +198,9 @@ fn interlaced_rgba_8_should_be_palette_2() {
 fn interlaced_rgba_16_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_palette_1.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -203,9 +209,9 @@ fn interlaced_rgba_16_should_be_palette_1() {
 fn interlaced_rgba_8_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_palette_1.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -214,9 +220,9 @@ fn interlaced_rgba_8_should_be_palette_1() {
 fn interlaced_rgba_16_should_be_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_grayscale_alpha_16.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -225,9 +231,9 @@ fn interlaced_rgba_16_should_be_grayscale_alpha_16() {
 fn interlaced_rgba_16_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_grayscale_alpha_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -236,9 +242,9 @@ fn interlaced_rgba_16_should_be_grayscale_alpha_8() {
 fn interlaced_rgba_8_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_grayscale_alpha_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -247,9 +253,9 @@ fn interlaced_rgba_8_should_be_grayscale_alpha_8() {
 fn interlaced_rgba_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_grayscale_16.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -258,9 +264,9 @@ fn interlaced_rgba_16_should_be_grayscale_16() {
 fn interlaced_rgba_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_16_should_be_grayscale_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -269,9 +275,9 @@ fn interlaced_rgba_16_should_be_grayscale_8() {
 fn interlaced_rgba_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_rgba_8_should_be_grayscale_8.png",
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -280,9 +286,9 @@ fn interlaced_rgba_8_should_be_grayscale_8() {
 fn interlaced_rgb_16_should_be_rgb_16() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_rgb_16.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -291,9 +297,9 @@ fn interlaced_rgb_16_should_be_rgb_16() {
 fn interlaced_rgb_16_should_be_rgb_8() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_rgb_8.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -302,9 +308,9 @@ fn interlaced_rgb_16_should_be_rgb_8() {
 fn interlaced_rgb_8_should_be_rgb_8() {
     test_it_converts(
         "tests/files/interlaced_rgb_8_should_be_rgb_8.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -313,9 +319,9 @@ fn interlaced_rgb_8_should_be_rgb_8() {
 fn interlaced_rgb_16_should_be_palette_8() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_palette_8.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -324,9 +330,9 @@ fn interlaced_rgb_16_should_be_palette_8() {
 fn interlaced_rgb_8_should_be_palette_8() {
     test_it_converts(
         "tests/files/interlaced_rgb_8_should_be_palette_8.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -335,9 +341,9 @@ fn interlaced_rgb_8_should_be_palette_8() {
 fn interlaced_rgb_16_should_be_palette_4() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_palette_4.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -346,9 +352,9 @@ fn interlaced_rgb_16_should_be_palette_4() {
 fn interlaced_rgb_8_should_be_palette_4() {
     test_it_converts(
         "tests/files/interlaced_rgb_8_should_be_palette_4.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -357,9 +363,9 @@ fn interlaced_rgb_8_should_be_palette_4() {
 fn interlaced_rgb_16_should_be_palette_2() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_palette_2.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -368,9 +374,9 @@ fn interlaced_rgb_16_should_be_palette_2() {
 fn interlaced_rgb_8_should_be_palette_2() {
     test_it_converts(
         "tests/files/interlaced_rgb_8_should_be_palette_2.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -379,9 +385,9 @@ fn interlaced_rgb_8_should_be_palette_2() {
 fn interlaced_rgb_16_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_palette_1.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -390,9 +396,9 @@ fn interlaced_rgb_16_should_be_palette_1() {
 fn interlaced_rgb_8_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_rgb_8_should_be_palette_1.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -401,9 +407,9 @@ fn interlaced_rgb_8_should_be_palette_1() {
 fn interlaced_rgb_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_grayscale_16.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -412,9 +418,9 @@ fn interlaced_rgb_16_should_be_grayscale_16() {
 fn interlaced_rgb_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_grayscale_8.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -423,9 +429,9 @@ fn interlaced_rgb_16_should_be_grayscale_8() {
 fn interlaced_rgb_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_rgb_8_should_be_grayscale_8.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -434,9 +440,9 @@ fn interlaced_rgb_8_should_be_grayscale_8() {
 fn interlaced_palette_8_should_be_palette_8() {
     test_it_converts(
         "tests/files/interlaced_palette_8_should_be_palette_8.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -445,9 +451,9 @@ fn interlaced_palette_8_should_be_palette_8() {
 fn interlaced_palette_8_should_be_palette_4() {
     test_it_converts(
         "tests/files/interlaced_palette_8_should_be_palette_4.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -456,9 +462,9 @@ fn interlaced_palette_8_should_be_palette_4() {
 fn interlaced_palette_4_should_be_palette_4() {
     test_it_converts(
         "tests/files/interlaced_palette_4_should_be_palette_4.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -467,9 +473,9 @@ fn interlaced_palette_4_should_be_palette_4() {
 fn interlaced_palette_8_should_be_palette_2() {
     test_it_converts(
         "tests/files/interlaced_palette_8_should_be_palette_2.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -478,9 +484,9 @@ fn interlaced_palette_8_should_be_palette_2() {
 fn interlaced_palette_4_should_be_palette_2() {
     test_it_converts(
         "tests/files/interlaced_palette_4_should_be_palette_2.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -489,9 +495,9 @@ fn interlaced_palette_4_should_be_palette_2() {
 fn interlaced_palette_2_should_be_palette_2() {
     test_it_converts(
         "tests/files/interlaced_palette_2_should_be_palette_2.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -500,9 +506,9 @@ fn interlaced_palette_2_should_be_palette_2() {
 fn interlaced_palette_8_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_palette_8_should_be_palette_1.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -511,9 +517,9 @@ fn interlaced_palette_8_should_be_palette_1() {
 fn interlaced_palette_4_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_palette_4_should_be_palette_1.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -522,9 +528,9 @@ fn interlaced_palette_4_should_be_palette_1() {
 fn interlaced_palette_2_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_palette_2_should_be_palette_1.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -533,9 +539,9 @@ fn interlaced_palette_2_should_be_palette_1() {
 fn interlaced_palette_1_should_be_palette_1() {
     test_it_converts(
         "tests/files/interlaced_palette_1_should_be_palette_1.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -544,9 +550,9 @@ fn interlaced_palette_1_should_be_palette_1() {
 fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16.png",
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -555,9 +561,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_16() {
 fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8.png",
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -566,9 +572,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_alpha_8() {
 fn interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8.png",
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -577,9 +583,9 @@ fn interlaced_grayscale_alpha_8_should_be_grayscale_alpha_8() {
 fn interlaced_grayscale_alpha_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_16.png",
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -588,9 +594,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_16() {
 fn interlaced_grayscale_alpha_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_grayscale_alpha_16_should_be_grayscale_8.png",
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -599,9 +605,9 @@ fn interlaced_grayscale_alpha_16_should_be_grayscale_8() {
 fn interlaced_grayscale_alpha_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_grayscale_alpha_8_should_be_grayscale_8.png",
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -610,9 +616,9 @@ fn interlaced_grayscale_alpha_8_should_be_grayscale_8() {
 fn interlaced_grayscale_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/interlaced_grayscale_16_should_be_grayscale_16.png",
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -621,9 +627,9 @@ fn interlaced_grayscale_16_should_be_grayscale_16() {
 fn interlaced_grayscale_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_grayscale_16_should_be_grayscale_8.png",
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -632,9 +638,9 @@ fn interlaced_grayscale_16_should_be_grayscale_8() {
 fn interlaced_grayscale_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/interlaced_grayscale_8_should_be_grayscale_8.png",
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -643,9 +649,9 @@ fn interlaced_grayscale_8_should_be_grayscale_8() {
 fn interlaced_small_files() {
     test_it_converts(
         "tests/files/interlaced_small_files.png",
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -654,9 +660,9 @@ fn interlaced_small_files() {
 fn interlaced_odd_width() {
     test_it_converts(
         "tests/files/interlaced_odd_width.png",
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
diff --git a/tests/interlacing.rs b/tests/interlacing.rs
index e0841c88..10f558e6 100644
--- a/tests/interlacing.rs
+++ b/tests/interlacing.rs
@@ -5,6 +5,9 @@ use std::fs::remove_file;
 use std::path::Path;
 use std::path::PathBuf;
 
+const RGB: u8 = 2;
+const INDEXED: u8 = 3;
+
 fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
     let mut options = oxipng::Options {
         force: true,
@@ -23,16 +26,16 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
 fn test_it_converts(
     input: &str,
     interlace: Interlacing,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
 ) {
     let input = PathBuf::from(input);
     let (output, mut opts) = get_opts(&input);
     let png = PngData::new(&input, opts.fix_errors).unwrap();
     opts.interlace = Some(interlace);
-    assert_eq!(png.raw.ihdr.color_type, color_type_in);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
     assert_eq!(
         png.raw.ihdr.interlaced,
@@ -58,7 +61,7 @@ fn test_it_converts(
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_out);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
 
     remove_file(output).ok();
@@ -69,9 +72,9 @@ fn deinterlace_rgb_16() {
     test_it_converts(
         "tests/files/interlaced_rgb_16_should_be_rgb_16.png",
         Interlacing::None,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -81,9 +84,9 @@ fn deinterlace_rgb_8() {
     test_it_converts(
         "tests/files/interlaced_rgb_8_should_be_rgb_8.png",
         Interlacing::None,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -93,9 +96,9 @@ fn deinterlace_palette_8() {
     test_it_converts(
         "tests/files/interlaced_palette_8_should_be_palette_8.png",
         Interlacing::None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -105,9 +108,9 @@ fn deinterlace_palette_4() {
     test_it_converts(
         "tests/files/interlaced_palette_4_should_be_palette_4.png",
         Interlacing::None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -117,9 +120,9 @@ fn deinterlace_palette_2() {
     test_it_converts(
         "tests/files/interlaced_palette_2_should_be_palette_2.png",
         Interlacing::None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -129,9 +132,9 @@ fn deinterlace_palette_1() {
     test_it_converts(
         "tests/files/interlaced_palette_1_should_be_palette_1.png",
         Interlacing::None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -141,9 +144,9 @@ fn interlace_rgb_16() {
     test_it_converts(
         "tests/files/rgb_16_should_be_rgb_16.png",
         Interlacing::Adam7,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -153,9 +156,9 @@ fn interlace_rgb_8() {
     test_it_converts(
         "tests/files/rgb_8_should_be_rgb_8.png",
         Interlacing::Adam7,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -165,9 +168,9 @@ fn interlace_palette_8() {
     test_it_converts(
         "tests/files/palette_8_should_be_palette_8.png",
         Interlacing::Adam7,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -177,9 +180,9 @@ fn interlace_palette_4() {
     test_it_converts(
         "tests/files/palette_4_should_be_palette_4.png",
         Interlacing::Adam7,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -189,9 +192,9 @@ fn interlace_palette_2() {
     test_it_converts(
         "tests/files/palette_2_should_be_palette_2.png",
         Interlacing::Adam7,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -201,9 +204,9 @@ fn interlace_palette_1() {
     test_it_converts(
         "tests/files/palette_1_should_be_palette_1.png",
         Interlacing::Adam7,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
diff --git a/tests/reduction.rs b/tests/reduction.rs
index d88cc58c..040375af 100644
--- a/tests/reduction.rs
+++ b/tests/reduction.rs
@@ -5,6 +5,12 @@ use std::fs::remove_file;
 use std::path::Path;
 use std::path::PathBuf;
 
+const GRAYSCALE: u8 = 0;
+const RGB: u8 = 2;
+const INDEXED: u8 = 3;
+const GRAYSCALE_ALPHA: u8 = 4;
+const RGBA: u8 = 6;
+
 fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
     let mut options = oxipng::Options {
         force: true,
@@ -23,9 +29,9 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
 fn test_it_converts(
     input: &str,
     optimize_alpha: bool,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
 ) {
     let input = PathBuf::from(input);
@@ -33,7 +39,7 @@ fn test_it_converts(
     opts.optimize_alpha = optimize_alpha;
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_in);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
     assert_eq!(png.raw.ihdr.interlaced, Interlacing::None);
 
@@ -52,7 +58,7 @@ fn test_it_converts(
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_out);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
 
     remove_file(output).ok();
@@ -63,9 +69,9 @@ fn rgba_16_should_be_rgba_16() {
     test_it_converts(
         "tests/files/rgba_16_should_be_rgba_16.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
     );
 }
@@ -75,9 +81,9 @@ fn rgba_16_should_be_rgba_8() {
     test_it_converts(
         "tests/files/rgba_16_should_be_rgba_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -87,9 +93,9 @@ fn rgba_8_should_be_rgba_8() {
     test_it_converts(
         "tests/files/rgba_8_should_be_rgba_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -99,9 +105,9 @@ fn rgba_16_should_be_rgb_16() {
     test_it_converts(
         "tests/files/rgba_16_should_be_rgb_16.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -111,9 +117,9 @@ fn rgba_16_should_be_rgb_8() {
     test_it_converts(
         "tests/files/rgba_16_should_be_rgb_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -123,9 +129,9 @@ fn rgba_8_should_be_rgb_8() {
     test_it_converts(
         "tests/files/rgba_8_should_be_rgb_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -135,9 +141,9 @@ fn rgba_16_should_be_rgb_trns_16() {
     test_it_converts(
         "tests/files/rgba_16_should_be_rgb_trns_16.png",
         true,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -147,9 +153,9 @@ fn rgba_8_should_be_rgb_trns_8() {
     test_it_converts(
         "tests/files/rgba_8_should_be_rgb_trns_8.png",
         true,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -159,9 +165,9 @@ fn rgba_16_should_be_palette_8() {
     test_it_converts(
         "tests/files/rgba_16_should_be_palette_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -171,9 +177,9 @@ fn rgba_8_should_be_palette_8() {
     test_it_converts(
         "tests/files/rgba_8_should_be_palette_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -183,9 +189,9 @@ fn rgba_16_should_be_palette_4() {
     test_it_converts(
         "tests/files/rgba_16_should_be_palette_4.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -195,9 +201,9 @@ fn rgba_8_should_be_palette_4() {
     test_it_converts(
         "tests/files/rgba_8_should_be_palette_4.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -207,9 +213,9 @@ fn rgba_16_should_be_palette_2() {
     test_it_converts(
         "tests/files/rgba_16_should_be_palette_2.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -219,9 +225,9 @@ fn rgba_8_should_be_palette_2() {
     test_it_converts(
         "tests/files/rgba_8_should_be_palette_2.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -231,9 +237,9 @@ fn rgba_16_should_be_palette_1() {
     test_it_converts(
         "tests/files/rgba_16_should_be_palette_1.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -243,9 +249,9 @@ fn rgba_8_should_be_palette_1() {
     test_it_converts(
         "tests/files/rgba_8_should_be_palette_1.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -255,9 +261,9 @@ fn rgba_16_should_be_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/rgba_16_should_be_grayscale_alpha_16.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -267,9 +273,9 @@ fn rgba_16_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/rgba_16_should_be_grayscale_alpha_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -279,9 +285,9 @@ fn rgba_8_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/rgba_8_should_be_grayscale_alpha_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -291,9 +297,9 @@ fn rgba_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/rgba_16_should_be_grayscale_16.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -303,9 +309,9 @@ fn rgba_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/rgba_16_should_be_grayscale_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -315,9 +321,9 @@ fn rgba_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/rgba_8_should_be_grayscale_8.png",
         false,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -327,9 +333,9 @@ fn rgb_16_should_be_rgb_16() {
     test_it_converts(
         "tests/files/rgb_16_should_be_rgb_16.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -339,9 +345,9 @@ fn rgb_16_should_be_rgb_8() {
     test_it_converts(
         "tests/files/rgb_16_should_be_rgb_8.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -351,9 +357,9 @@ fn rgb_8_should_be_rgb_8() {
     test_it_converts(
         "tests/files/rgb_8_should_be_rgb_8.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -363,9 +369,9 @@ fn rgb_16_should_be_palette_8() {
     test_it_converts(
         "tests/files/rgb_16_should_be_palette_8.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -375,9 +381,9 @@ fn rgb_8_should_be_palette_8() {
     test_it_converts(
         "tests/files/rgb_8_should_be_palette_8.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -387,9 +393,9 @@ fn rgb_16_should_be_palette_4() {
     test_it_converts(
         "tests/files/rgb_16_should_be_palette_4.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -399,9 +405,9 @@ fn rgb_8_should_be_palette_4() {
     test_it_converts(
         "tests/files/rgb_8_should_be_palette_4.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -411,9 +417,9 @@ fn rgb_16_should_be_palette_2() {
     test_it_converts(
         "tests/files/rgb_16_should_be_palette_2.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -423,9 +429,9 @@ fn rgb_8_should_be_palette_2() {
     test_it_converts(
         "tests/files/rgb_8_should_be_palette_2.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -435,9 +441,9 @@ fn rgb_16_should_be_palette_1() {
     test_it_converts(
         "tests/files/rgb_16_should_be_palette_1.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -447,9 +453,9 @@ fn rgb_8_should_be_palette_1() {
     test_it_converts(
         "tests/files/rgb_8_should_be_palette_1.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -459,9 +465,9 @@ fn rgb_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/rgb_16_should_be_grayscale_16.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -471,9 +477,9 @@ fn rgb_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/rgb_16_should_be_grayscale_8.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -483,9 +489,9 @@ fn rgb_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/rgb_8_should_be_grayscale_8.png",
         false,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -495,9 +501,9 @@ fn palette_8_should_be_palette_8() {
     test_it_converts(
         "tests/files/palette_8_should_be_palette_8.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -507,9 +513,9 @@ fn palette_8_should_be_palette_4() {
     test_it_converts(
         "tests/files/palette_8_should_be_palette_4.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -519,9 +525,9 @@ fn palette_4_should_be_palette_4() {
     test_it_converts(
         "tests/files/palette_4_should_be_palette_4.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -531,9 +537,9 @@ fn palette_8_should_be_palette_2() {
     test_it_converts(
         "tests/files/palette_8_should_be_palette_2.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -543,9 +549,9 @@ fn palette_4_should_be_palette_2() {
     test_it_converts(
         "tests/files/palette_4_should_be_palette_2.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -555,9 +561,9 @@ fn palette_2_should_be_palette_2() {
     test_it_converts(
         "tests/files/palette_2_should_be_palette_2.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -567,9 +573,9 @@ fn palette_8_should_be_palette_1() {
     test_it_converts(
         "tests/files/palette_8_should_be_palette_1.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -579,9 +585,9 @@ fn palette_4_should_be_palette_1() {
     test_it_converts(
         "tests/files/palette_4_should_be_palette_1.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -591,9 +597,9 @@ fn palette_2_should_be_palette_1() {
     test_it_converts(
         "tests/files/palette_2_should_be_palette_1.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -603,9 +609,9 @@ fn palette_1_should_be_palette_1() {
     test_it_converts(
         "tests/files/palette_1_should_be_palette_1.png",
         false,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -615,9 +621,9 @@ fn grayscale_alpha_16_should_be_grayscale_alpha_16() {
     test_it_converts(
         "tests/files/grayscale_alpha_16_should_be_grayscale_alpha_16.png",
         false,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
     );
 }
@@ -627,9 +633,9 @@ fn grayscale_alpha_16_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/grayscale_alpha_16_should_be_grayscale_alpha_8.png",
         false,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -639,9 +645,9 @@ fn grayscale_alpha_8_should_be_grayscale_alpha_8() {
     test_it_converts(
         "tests/files/grayscale_alpha_8_should_be_grayscale_alpha_8.png",
         false,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -651,9 +657,9 @@ fn grayscale_alpha_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/grayscale_alpha_16_should_be_grayscale_16.png",
         false,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -663,9 +669,9 @@ fn grayscale_alpha_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/grayscale_alpha_16_should_be_grayscale_8.png",
         false,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -675,9 +681,9 @@ fn grayscale_alpha_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/grayscale_alpha_8_should_be_grayscale_8.png",
         false,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -687,9 +693,9 @@ fn grayscale_16_should_be_grayscale_16() {
     test_it_converts(
         "tests/files/grayscale_16_should_be_grayscale_16.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -699,9 +705,9 @@ fn grayscale_16_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/grayscale_16_should_be_grayscale_8.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -711,9 +717,9 @@ fn grayscale_8_should_be_grayscale_8() {
     test_it_converts(
         "tests/files/grayscale_8_should_be_grayscale_8.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -723,9 +729,9 @@ fn grayscale_8_should_be_grayscale_4() {
     test_it_converts(
         "tests/files/grayscale_8_should_be_grayscale_4.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Four,
     );
 }
@@ -735,9 +741,9 @@ fn grayscale_8_should_be_grayscale_2() {
     test_it_converts(
         "tests/files/grayscale_8_should_be_grayscale_2.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Two,
     );
 }
@@ -747,9 +753,9 @@ fn grayscale_4_should_be_grayscale_2() {
     test_it_converts(
         "tests/files/grayscale_4_should_be_grayscale_2.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Four,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Two,
     );
 }
@@ -759,9 +765,9 @@ fn grayscale_8_should_be_grayscale_1() {
     test_it_converts(
         "tests/files/grayscale_8_should_be_grayscale_1.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
     );
 }
@@ -771,9 +777,9 @@ fn grayscale_4_should_be_grayscale_1() {
     test_it_converts(
         "tests/files/grayscale_4_should_be_grayscale_1.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Four,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
     );
 }
@@ -783,9 +789,9 @@ fn grayscale_2_should_be_grayscale_1() {
     test_it_converts(
         "tests/files/grayscale_2_should_be_grayscale_1.png",
         false,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Two,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
     );
 }
@@ -795,9 +801,9 @@ fn grayscale_alpha_16_should_be_grayscale_trns_16() {
     test_it_converts(
         "tests/files/grayscale_alpha_16_should_be_grayscale_trns_16.png",
         true,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Sixteen,
     );
 }
@@ -807,9 +813,9 @@ fn grayscale_alpha_8_should_be_grayscale_trns_8() {
     test_it_converts(
         "tests/files/grayscale_alpha_8_should_be_grayscale_trns_8.png",
         true,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -821,7 +827,7 @@ fn small_files() {
 
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
@@ -839,7 +845,7 @@ fn small_files() {
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     // depth varies depending on zlib implementation used
 
     remove_file(output).ok();
@@ -852,9 +858,11 @@ fn palette_should_be_reduced_with_dupes() {
 
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
-    assert_eq!(png.raw.palette.as_ref().unwrap().len(), 43);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert_eq!(palette.len(), 43);
+    }
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
         Ok(_) => (),
@@ -871,9 +879,11 @@ fn palette_should_be_reduced_with_dupes() {
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
-    assert_eq!(png.raw.palette.as_ref().unwrap().len(), 35);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert_eq!(palette.len(), 35);
+    }
 
     remove_file(output).ok();
 }
@@ -885,9 +895,11 @@ fn palette_should_be_reduced_with_unused() {
 
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
-    assert_eq!(png.raw.palette.as_ref().unwrap().len(), 35);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert_eq!(palette.len(), 35);
+    }
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
         Ok(_) => (),
@@ -904,9 +916,11 @@ fn palette_should_be_reduced_with_unused() {
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
-    assert_eq!(png.raw.palette.as_ref().unwrap().len(), 33);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert_eq!(palette.len(), 33);
+    }
 
     remove_file(output).ok();
 }
@@ -918,9 +932,11 @@ fn palette_should_be_reduced_with_both() {
 
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
-    assert_eq!(png.raw.palette.as_ref().unwrap().len(), 43);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert_eq!(palette.len(), 43);
+    }
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
         Ok(_) => (),
@@ -937,9 +953,11 @@ fn palette_should_be_reduced_with_both() {
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, ColorType::Indexed);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), INDEXED);
     assert_eq!(png.raw.ihdr.bit_depth, BitDepth::Eight);
-    assert_eq!(png.raw.palette.as_ref().unwrap().len(), 33);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert_eq!(palette.len(), 33);
+    }
 
     remove_file(output).ok();
 }
@@ -949,9 +967,9 @@ fn rgba_16_reduce_alpha() {
     test_it_converts(
         "tests/files/rgba_16_reduce_alpha.png",
         true,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Sixteen,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -961,9 +979,9 @@ fn rgba_8_reduce_alpha() {
     test_it_converts(
         "tests/files/rgba_8_reduce_alpha.png",
         true,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -973,9 +991,9 @@ fn grayscale_alpha_16_reduce_alpha() {
     test_it_converts(
         "tests/files/grayscale_alpha_16_reduce_alpha.png",
         true,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Sixteen,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -985,9 +1003,9 @@ fn grayscale_alpha_8_reduce_alpha() {
     test_it_converts(
         "tests/files/grayscale_alpha_8_reduce_alpha.png",
         true,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
diff --git a/tests/regression.rs b/tests/regression.rs
index e88cd077..06833b25 100644
--- a/tests/regression.rs
+++ b/tests/regression.rs
@@ -5,6 +5,12 @@ use std::fs::remove_file;
 use std::path::Path;
 use std::path::PathBuf;
 
+const GRAYSCALE: u8 = 0;
+const RGB: u8 = 2;
+const INDEXED: u8 = 3;
+const GRAYSCALE_ALPHA: u8 = 4;
+const RGBA: u8 = 6;
+
 fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
     let mut options = oxipng::Options {
         force: true,
@@ -23,9 +29,9 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
 fn test_it_converts(
     input: &str,
     custom: Option<(OutFile, oxipng::Options)>,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
 ) {
     let input = PathBuf::from(input);
@@ -33,7 +39,8 @@ fn test_it_converts(
     let png = PngData::new(&input, opts.fix_errors).unwrap();
 
     assert_eq!(
-        png.raw.ihdr.color_type, color_type_in,
+        png.raw.ihdr.color_type.png_header_code(),
+        color_type_in,
         "test file is broken"
     );
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in, "test file is broken");
@@ -54,23 +61,22 @@ fn test_it_converts(
     };
 
     assert_eq!(
-        png.raw.ihdr.color_type, color_type_out,
+        png.raw.ihdr.color_type.png_header_code(),
+        color_type_out,
         "optimized to wrong color type"
     );
     assert_eq!(
         png.raw.ihdr.bit_depth, bit_depth_out,
         "optimized to wrong bit depth"
     );
-    if let Some(palette) = png.raw.palette.as_ref() {
-        let mut max_palette_size = 1 << (png.raw.ihdr.bit_depth.as_u8() as usize);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        let mut max_palette_size = 1 << (png.raw.ihdr.bit_depth as u8);
         // Ensure bKGD color is valid
         if let Some(&idx) = png.raw.aux_headers.get(b"bKGD").and_then(|b| b.first()) {
             assert!(palette.len() > idx as usize);
             max_palette_size = max_palette_size.max(idx as usize + 1);
         }
         assert!(palette.len() <= max_palette_size);
-    } else {
-        assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
     }
 
     remove_file(output).ok();
@@ -81,9 +87,9 @@ fn issue_29() {
     test_it_converts(
         "tests/files/issue-29.png",
         None,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -127,9 +133,9 @@ fn issue_52_01() {
     test_it_converts(
         "tests/files/issue-52-01.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -139,9 +145,9 @@ fn issue_52_02() {
     test_it_converts(
         "tests/files/issue-52-02.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -151,9 +157,9 @@ fn issue_52_03() {
     test_it_converts(
         "tests/files/issue-52-03.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -163,9 +169,9 @@ fn issue_52_04() {
     test_it_converts(
         "tests/files/issue-52-04.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -175,9 +181,9 @@ fn issue_52_05() {
     test_it_converts(
         "tests/files/issue-52-05.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -187,9 +193,9 @@ fn issue_52_06() {
     test_it_converts(
         "tests/files/issue-52-06.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
     );
 }
@@ -199,9 +205,9 @@ fn issue_56() {
     test_it_converts(
         "tests/files/issue-56.png",
         None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -211,9 +217,9 @@ fn issue_58() {
     test_it_converts(
         "tests/files/issue-58.png",
         None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -223,9 +229,9 @@ fn issue_59() {
     test_it_converts(
         "tests/files/issue-59.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -235,9 +241,9 @@ fn issue_60() {
     test_it_converts(
         "tests/files/issue-60.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -247,9 +253,9 @@ fn issue_80() {
     test_it_converts(
         "tests/files/issue-80.png",
         None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Two,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -259,9 +265,9 @@ fn issue_82() {
     test_it_converts(
         "tests/files/issue-82.png",
         None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Four,
     );
 }
@@ -271,9 +277,9 @@ fn issue_89() {
     test_it_converts(
         "tests/files/issue-89.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -283,9 +289,9 @@ fn issue_92_filter_0() {
     test_it_converts(
         "tests/files/issue-92.png",
         None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -300,9 +306,9 @@ fn issue_92_filter_5() {
     test_it_converts(
         input,
         Some((output, opts)),
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -316,9 +322,9 @@ fn issue_113() {
     test_it_converts(
         input,
         Some((output, opts)),
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::GrayscaleAlpha,
+        GRAYSCALE_ALPHA,
         BitDepth::Eight,
     );
 }
@@ -326,14 +332,7 @@ fn issue_113() {
 #[test]
 fn issue_129() {
     let input = "tests/files/issue-129.png";
-    test_it_converts(
-        input,
-        None,
-        ColorType::RGB,
-        BitDepth::Eight,
-        ColorType::Indexed,
-        BitDepth::Eight,
-    );
+    test_it_converts(input, None, RGB, BitDepth::Eight, INDEXED, BitDepth::Eight);
 }
 
 #[test]
@@ -344,9 +343,9 @@ fn issue_133() {
     test_it_converts(
         input,
         Some((output, opts)),
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -356,9 +355,9 @@ fn issue_140() {
     test_it_converts(
         "tests/files/issue-140.png",
         None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Two,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Two,
     );
 }
@@ -368,9 +367,9 @@ fn issue_141() {
     test_it_converts(
         "tests/files/issue-141.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -380,9 +379,9 @@ fn issue_153() {
     test_it_converts(
         "tests/files/issue-153.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -392,9 +391,9 @@ fn issue_159() {
     test_it_converts(
         "tests/files/issue-159.png",
         None,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::One,
     );
 }
@@ -404,9 +403,9 @@ fn issue_171() {
     test_it_converts(
         "tests/files/issue-171.png",
         None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -416,9 +415,9 @@ fn issue_175() {
     test_it_converts(
         "tests/files/issue-175.png",
         None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
     );
 }
@@ -432,9 +431,9 @@ fn issue_182() {
     test_it_converts(
         input,
         Some((output, opts)),
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
     );
 }
@@ -444,9 +443,9 @@ fn issue_195() {
     test_it_converts(
         "tests/files/issue-195.png",
         None,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }
@@ -456,9 +455,9 @@ fn issue_426_01() {
     test_it_converts(
         "tests/files/issue-426-01.png",
         None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
     );
 }
@@ -468,9 +467,9 @@ fn issue_426_02() {
     test_it_converts(
         "tests/files/issue-426-02.png",
         None,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::One,
     );
 }
diff --git a/tests/strategies.rs b/tests/strategies.rs
index aa5d80f4..415014fe 100644
--- a/tests/strategies.rs
+++ b/tests/strategies.rs
@@ -5,6 +5,11 @@ use std::fs::remove_file;
 use std::path::Path;
 use std::path::PathBuf;
 
+const GRAYSCALE: u8 = 0;
+const RGB: u8 = 2;
+const INDEXED: u8 = 3;
+const RGBA: u8 = 6;
+
 fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
     let mut options = oxipng::Options {
         force: true,
@@ -23,9 +28,9 @@ fn get_opts(input: &Path) -> (OutFile, oxipng::Options) {
 fn test_it_converts(
     input: &str,
     filter: RowFilter,
-    color_type_in: ColorType,
+    color_type_in: u8,
     bit_depth_in: BitDepth,
-    color_type_out: ColorType,
+    color_type_out: u8,
     bit_depth_out: BitDepth,
 ) {
     let input = PathBuf::from(input);
@@ -34,7 +39,7 @@ fn test_it_converts(
     let png = PngData::new(&input, opts.fix_errors).unwrap();
     opts.filter = IndexSet::new();
     opts.filter.insert(filter);
-    assert_eq!(png.raw.ihdr.color_type, color_type_in);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_in);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_in);
 
     match oxipng::optimize(&InFile::Path(input), &output, &opts) {
@@ -52,12 +57,10 @@ fn test_it_converts(
         }
     };
 
-    assert_eq!(png.raw.ihdr.color_type, color_type_out);
+    assert_eq!(png.raw.ihdr.color_type.png_header_code(), color_type_out);
     assert_eq!(png.raw.ihdr.bit_depth, bit_depth_out);
-    if let Some(palette) = png.raw.palette.as_ref() {
-        assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth.as_u8() as usize));
-    } else {
-        assert_ne!(png.raw.ihdr.color_type, ColorType::Indexed);
+    if let ColorType::Indexed { palette } = &png.raw.ihdr.color_type {
+        assert!(palette.len() <= 1 << (png.raw.ihdr.bit_depth as u8));
     }
 
     remove_file(output).ok();
@@ -68,9 +71,9 @@ fn filter_minsum() {
     test_it_converts(
         "tests/files/rgb_16_should_be_rgb_16.png",
         RowFilter::MinSum,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
-        ColorType::RGB,
+        RGB,
         BitDepth::Sixteen,
     );
 }
@@ -80,9 +83,9 @@ fn filter_entropy() {
     test_it_converts(
         "tests/files/rgb_8_should_be_rgb_8.png",
         RowFilter::Entropy,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
-        ColorType::RGB,
+        RGB,
         BitDepth::Eight,
     );
 }
@@ -92,9 +95,9 @@ fn filter_bigrams() {
     test_it_converts(
         "tests/files/rgba_8_should_be_rgba_8.png",
         RowFilter::Bigrams,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
-        ColorType::RGBA,
+        RGBA,
         BitDepth::Eight,
     );
 }
@@ -104,9 +107,9 @@ fn filter_bigent() {
     test_it_converts(
         "tests/files/grayscale_8_should_be_grayscale_8.png",
         RowFilter::BigEnt,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
-        ColorType::Grayscale,
+        GRAYSCALE,
         BitDepth::Eight,
     );
 }
@@ -116,9 +119,9 @@ fn filter_brute() {
     test_it_converts(
         "tests/files/palette_8_should_be_palette_8.png",
         RowFilter::Brute,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
-        ColorType::Indexed,
+        INDEXED,
         BitDepth::Eight,
     );
 }