From 677b38ce7d9ca7b1860fb103b0c3b0d1a00feceb Mon Sep 17 00:00:00 2001 From: David Chavez Date: Tue, 18 Jul 2023 10:48:56 +0200 Subject: [PATCH] Feature: Native -> PNG (#13) * Rename Image to PNGImage * Update PNG Image methods + cmd line * fmt * Add NativeImage and conversions * Add writing PNG and parsing TLUT * Fix bugs and add tests * Add CI png exports * fmt * Passing tests * Remove debugging output * Add ia4 from rgb8 png conversion * Add more tests and fixes * Add CI4 test + fixes * Update some comments * Small fixes * Fix fmt --- Cargo.lock | 86 ++++++++++++ Cargo.toml | 1 + src/color.rs | 17 ++- src/image/mod.rs | 1 + src/image/native_image.rs | 282 ++++++++++++++++++++++++++++++++++++++ src/image/png_image.rs | 28 ++++ src/lib.rs | 34 ++++- tests/.DS_Store | Bin 0 -> 6148 bytes tests/ci4.data.bin | 1 + tests/ci4.png | Bin 0 -> 168 bytes tests/ci4.tlut.bin | 1 + tests/color.rs | 6 +- tests/i4.png | Bin 0 -> 150 bytes tests/i4.png.bin | 1 + tests/ia4.png | Bin 0 -> 161 bytes tests/ia4.png.bin | 1 + tests/native_image.rs | 147 ++++++++++++++++++++ tests/png_image.rs | 76 +++++----- 18 files changed, 639 insertions(+), 43 deletions(-) create mode 100644 src/image/native_image.rs create mode 100644 tests/.DS_Store create mode 100644 tests/ci4.data.bin create mode 100644 tests/ci4.png create mode 100644 tests/ci4.tlut.bin create mode 100644 tests/i4.png create mode 100644 tests/i4.png.bin create mode 100644 tests/ia4.png create mode 100644 tests/ia4.png.bin create mode 100644 tests/native_image.rs diff --git a/Cargo.lock b/Cargo.lock index dc7533a..fc55185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.1" @@ -184,6 +190,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.4.1" @@ -196,6 +208,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -231,6 +253,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -241,6 +269,27 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "num_enum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -254,6 +303,7 @@ dependencies = [ "anyhow", "byteorder", "clap", + "num_enum", "png", ] @@ -270,6 +320,16 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.63" @@ -325,6 +385,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.9" @@ -402,3 +479,12 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 89a57fa..174f978 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ png = "0.17.9" clap = { version = "4.1.12", features = ["derive"] } anyhow = "1.0.72" byteorder = "1.4.3" +num_enum = "0.6.1" diff --git a/src/color.rs b/src/color.rs index 5b9f3e0..e78a3a4 100644 --- a/src/color.rs +++ b/src/color.rs @@ -32,6 +32,17 @@ impl Color { Color { r, g, b, a: 0xFF } } + /// Creates a new color from a 32-bit RGBA pixel. + #[inline] + pub fn from_u32(pixel: u32) -> Color { + let r = ((pixel >> 24) & 0xFF) as u8; + let g = ((pixel >> 16) & 0xFF) as u8; + let b = ((pixel >> 8) & 0xFF) as u8; + let a = (pixel & 0xFF) as u8; + + Color { r, g, b, a } + } + /// Converts a 16-bit RGBA pixel to a 32-bit RGBA color. #[inline] pub fn from_u16(pixel: u16) -> Color { @@ -62,15 +73,15 @@ impl Color { /// Converts a 32-bit RGBA color to a 16-bit RGBA pixel and /// returns the two 8-bit components. #[inline] - pub fn rgba16(self) -> (u8, u8) { + pub fn rgba16(self) -> [u8; 2] { let pixel = self.to_u16(); - ((pixel >> 8) as u8, (pixel & 0xFF) as u8) + [(pixel >> 8) as u8, (pixel & 0xFF) as u8] } /// Converts the rgb components to a single intensity value. /// This is used for grayscale images. #[inline] pub fn rgb_to_intensity(&self) -> u8 { - (self.r as f32 * 0.2126 + self.g as f32 * 0.7152 + 0.0722 * self.b as f32) as u8 + (self.r as f32 * 0.2126 + self.g as f32 * 0.7152 + 0.0722 * self.b as f32).round() as u8 } } diff --git a/src/image/mod.rs b/src/image/mod.rs index 2ff5289..81820e7 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -1 +1,2 @@ +pub mod native_image; pub mod png_image; diff --git a/src/image/native_image.rs b/src/image/native_image.rs new file mode 100644 index 0000000..ae16342 --- /dev/null +++ b/src/image/native_image.rs @@ -0,0 +1,282 @@ +use crate::color::Color; +use crate::{ImageSize, ImageType, TextureLUT}; +use anyhow::Result; +use byteorder::{BigEndian, ReadBytesExt}; +use std::io::{Cursor, Read, Write}; + +pub struct NativeImage { + pub format: ImageType, + pub width: u32, + pub height: u32, + pub data: Vec, +} + +impl NativeImage { + pub fn read( + mut reader: R, + format: ImageType, + width: u32, + height: u32, + ) -> Result { + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + + Ok(Self { + format, + width, + height, + data, + }) + } + + /// Decodes the image into RGBA8 format and writes it image bytes to the given writer. + pub fn decode(&self, writer: &mut W, tlut_color_table: Option<&[u8]>) -> Result<()> { + let mut cursor = Cursor::new(&self.data); + + match self.format { + ImageType::I4 => { + for _y in 0..self.height { + for _x in (0..self.width).step_by(2) { + let byte = cursor.read_u8()?; + + let intensity = byte & 0xF0; + writer.write_all(&[intensity, intensity, intensity, 0xFF])?; + + let intensity = (byte & 0x0F) << 4; + writer.write_all(&[intensity, intensity, intensity, 0xFF])?; + } + } + } + ImageType::I8 => { + for _y in 0..self.height { + for _x in 0..self.width { + let intensity = cursor.read_u8()?; + writer.write_all(&[intensity, intensity, intensity, 0xFF])?; + } + } + } + ImageType::Ia4 => { + for _y in 0..self.height { + for _x in (0..self.width).step_by(2) { + let byte = cursor.read_u8()?; + + let source = (byte & 0xF0) >> 4; + let intensity = ((source & 0x0E) >> 1) * 32; + let alpha = (source & 0x01) * 255; + writer.write_all(&[intensity, intensity, intensity, alpha])?; + + let source = byte & 0x0F; + let intensity = ((source & 0x0E) >> 1) * 32; + let alpha = (source & 0x01) * 255; + writer.write_all(&[intensity, intensity, intensity, alpha])?; + } + } + } + ImageType::Ia8 => { + for _y in 0..self.height { + for _x in 0..self.width { + let byte = cursor.read_u8()?; + + let intensity = byte & 0xF0; + let alpha = (byte & 0x0F) << 4; + + writer.write_all(&[intensity, intensity, intensity, alpha])?; + } + } + } + ImageType::Ia16 => { + for _y in 0..self.height { + for _x in 0..self.width { + let intensity = cursor.read_u8()?; + let alpha = cursor.read_u8()?; + + writer.write_all(&[intensity, intensity, intensity, alpha])?; + } + } + } + ImageType::Ci4 => { + assert!(tlut_color_table.is_some()); + + for _y in 0..self.height { + for _x in (0..self.width).step_by(2) { + let byte = cursor.read_u8()?; + + let index = (byte >> 4) & 0x0F; + writer.write_all(&get_tlut_color_at_index(tlut_color_table, index))?; + + let index = byte & 0x0F; + writer.write_all(&get_tlut_color_at_index(tlut_color_table, index))?; + } + } + } + ImageType::Ci8 => { + assert!(tlut_color_table.is_some()); + + for _y in 0..self.height { + for _x in 0..self.width { + let index = cursor.read_u8()?; + writer.write_all(&get_tlut_color_at_index(tlut_color_table, index))?; + } + } + } + ImageType::Rgba16 => { + for _y in 0..self.height { + for _x in 0..self.width { + let pixel = cursor.read_u16::()?; + let color = Color::from_u16(pixel); + writer.write_all(&[color.r, color.g, color.b, color.a])?; + } + } + } + ImageType::Rgba32 => { + for _y in 0..self.height { + for _x in 0..self.width { + let pixel = cursor.read_u32::()?; + let color = Color::from_u32(pixel); + writer.write_all(&[color.r, color.g, color.b, color.a])?; + } + } + } + } + + Ok(()) + } + + /// Decodes the image into RGBA8 and writes it as PNG to the given writer. + /// Exception is CI4 and CI8, which get written as an indexed PNG. + pub fn as_png(&self, writer: &mut W, tlut_color_table: Option<&[u8]>) -> Result<()> { + let mut data: Vec = vec![]; + let mut encoder = png::Encoder::new(writer, self.width, self.height); + + match self.format { + ImageType::I4 => { + self.decode(&mut data, None)?; + } + ImageType::I8 => { + self.decode(&mut data, None)?; + } + ImageType::Ia4 => { + self.decode(&mut data, None)?; + } + ImageType::Ia8 => { + self.decode(&mut data, None)?; + } + ImageType::Ia16 => { + self.decode(&mut data, None)?; + } + ImageType::Ci4 => { + assert!(tlut_color_table.is_some()); + let mut cursor = Cursor::new(&self.data); + let mut data: Vec = vec![]; + + for _y in 0..self.height { + for _x in (0..self.width).step_by(2) { + let byte = cursor.read_u8()?; + data.push(byte); + } + } + + encoder.set_color(png::ColorType::Indexed); + encoder.set_depth(png::BitDepth::Four); + + let (palette_data, trans_data) = + split_color_table_for_png(tlut_color_table.unwrap()); + + encoder.set_palette(palette_data); + encoder.set_trns(trans_data); + + let mut writer = encoder.write_header()?; + writer.write_image_data(&data)?; + + return Ok(()); + } + ImageType::Ci8 => { + assert!(tlut_color_table.is_some()); + let mut cursor = Cursor::new(&self.data); + let mut data: Vec = vec![]; + + for _y in 0..self.height { + for _x in 0..self.width { + let index = cursor.read_u8()?; + data.push(index); + } + } + + encoder.set_color(png::ColorType::Indexed); + encoder.set_depth(png::BitDepth::Eight); + + let (palette_data, trans_data) = + split_color_table_for_png(tlut_color_table.unwrap()); + + encoder.set_palette(palette_data); + encoder.set_trns(trans_data); + + let mut writer = encoder.write_header()?; + writer.write_image_data(&data)?; + + return Ok(()); + } + ImageType::Rgba16 => { + self.decode(&mut data, None)?; + } + ImageType::Rgba32 => { + self.decode(&mut data, None)?; + } + } + + encoder.set_color(png::ColorType::Rgba); + encoder.set_depth(png::BitDepth::Eight); + + let mut writer = encoder.write_header()?; + writer.write_image_data(&data)?; + + Ok(()) + } +} + +/// Parses a tlut into a RGBA8 color table +pub fn parse_tlut(bytes: &[u8], size: ImageSize, mode: TextureLUT) -> Result> { + assert_eq!( + mode, + TextureLUT::Rgba16, + "Only RGBA16 TLUTs are supported at the moment" + ); + + let mut output: Vec = vec![]; + let cursor = &mut Cursor::new(bytes); + for _i in 0..(size.get_tlut_size()) { + let pixel = cursor.read_u16::()?; + let color = Color::from_u16(pixel); + output.write_all(&[color.r, color.g, color.b, color.a])?; + } + + Ok(output) +} + +/// Reads an rgba color from a buffer starting at the given offset +fn get_tlut_color_at_index(tlut_color_table: Option<&[u8]>, index: u8) -> [u8; 4] { + assert!(tlut_color_table.is_some()); + + if let Some(tlut_color_table) = tlut_color_table { + let r = tlut_color_table[(index * 4) as usize]; + let g = tlut_color_table[((index * 4) + 1) as usize]; + let b = tlut_color_table[((index * 4) + 2) as usize]; + let a = tlut_color_table[((index * 4) + 3) as usize]; + + return [r, g, b, a]; + } + + unreachable!() +} + +/// Splits a color table into a palette and a transparency table for png encoding +fn split_color_table_for_png(table: &[u8]) -> (Vec, Vec) { + let palette_data: Vec = table + .chunks(4) + .flat_map(|chunk| chunk[..3].to_vec()) + .collect(); + + let trans_data: Vec = table.chunks(4).map(|chunk| chunk[3]).collect(); + + (palette_data, trans_data) +} diff --git a/src/image/png_image.rs b/src/image/png_image.rs index 607144b..5acb949 100644 --- a/src/image/png_image.rs +++ b/src/image/png_image.rs @@ -196,6 +196,19 @@ impl PNGImage { writer.write_u8(high << 4 | (low & 0xF)).unwrap(); }) } + (ColorType::Rgba, BitDepth::Eight) => self.data.chunks_exact(8).for_each(|chunk| { + let c1 = Color::RGBA(chunk[0], chunk[1], chunk[2], chunk[3]); + let intensity1 = (c1.rgb_to_intensity() >> 5) << 1; + let alpha1 = (c1.a > 127) as u8; + + let c2 = Color::RGBA(chunk[4], chunk[5], chunk[6], chunk[7]); + let intensity2 = (c2.rgb_to_intensity() >> 5) << 1; + let alpha2 = (c2.a > 127) as u8; + + let high = intensity1 | alpha1; + let low = intensity2 | alpha2; + writer.write_u8(high << 4 | (low & 0xF)).unwrap(); + }), p => panic!("unsupported format {:?}", p), } @@ -208,6 +221,13 @@ impl PNGImage { .data .chunks_exact(2) .for_each(|chunk| writer.write_u8(chunk[0] << 4 | (chunk[1] & 0x0F)).unwrap()), + (ColorType::Rgba, BitDepth::Eight) => self.data.chunks_exact(4).for_each(|chunk| { + let c = Color::RGBA(chunk[0], chunk[1], chunk[2], chunk[3]); + let i = (c.rgb_to_intensity() >> 4) & 0xF; + let a = (c.a >> 4) & 0xF; + + writer.write_u8(i << 4 | a).unwrap(); + }), p => panic!("unsupported format {:?}", p), } @@ -217,6 +237,14 @@ impl PNGImage { pub fn as_ia16(&self, writer: &mut W) -> Result<()> { match (self.color_type, self.bit_depth) { (ColorType::GrayscaleAlpha, BitDepth::Eight) => writer.write_all(&self.data)?, + (ColorType::Rgba, BitDepth::Eight) => self.data.chunks_exact(4).for_each(|chunk| { + let c = Color::RGBA(chunk[0], chunk[1], chunk[2], chunk[3]); + let i = c.rgb_to_intensity(); + let a = c.a; + + writer.write_u8(i).unwrap(); + writer.write_u8(a).unwrap(); + }), p => panic!("unsupported format {:?}", p), } diff --git a/src/lib.rs b/src/lib.rs index b823090..c79d290 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,38 @@ +use num_enum::TryFromPrimitive; + pub mod color; pub mod image; +pub use image::native_image::NativeImage; pub use image::png_image::create_palette_from_png; pub use image::png_image::PNGImage; mod utils; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TryFromPrimitive)] +#[repr(u8)] pub enum ImageSize { - Bits4, + Bits4 = 0, Bits8 = 1, Bits16 = 2, Bits32 = 3, DD = 5, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +impl ImageSize { + pub fn get_tlut_size(&self) -> usize { + match self { + ImageSize::Bits4 => 0x10, + ImageSize::Bits8 => 0x100, + ImageSize::Bits16 => 0x1000, + ImageSize::Bits32 => 0x10000, + _ => panic!("Invalid size: {:?}", self), + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TryFromPrimitive)] +#[repr(u8)] pub enum ImageFormat { Rgba = 0, Yuv = 1, @@ -24,7 +41,8 @@ pub enum ImageFormat { I = 4, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TryFromPrimitive)] +#[repr(u8)] pub enum ImageType { I4, I8, @@ -66,3 +84,11 @@ impl ImageType { } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive)] +#[repr(u8)] +pub enum TextureLUT { + None = 0, + Rgba16 = 2, + Ia16 = 3, +} diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0ĸúYÑ \ No newline at end of file diff --git a/tests/ci4.png b/tests/ci4.png new file mode 100644 index 0000000000000000000000000000000000000000..e463077f2121ebd13531992b3a38aeeed6251c37 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^EFjFn3?!rPH~Ru9g8-ip*XQr6y}Y~>-y0s|xVJTG zilOD+9u9`3UY1T?0>weq+0XBt6X3|c#u0RHO7Z*TdzN#Y-A*qB>J=yn@(cdY!0;ag zfP64(mC{rVAWPiS#WAFUk&%bt^M3|)JBA}W7=A@Euw4`@VBy(!?YR_4wWq6}%Q~lo FCIA~bI6eRX literal 0 HcmV?d00001 diff --git a/tests/ci4.tlut.bin b/tests/ci4.tlut.bin new file mode 100644 index 0000000..001ea36 --- /dev/null +++ b/tests/ci4.tlut.bin @@ -0,0 +1 @@ +ç_JR'LÀw²å1ïˆA¢OBDržo6È‚n‚VåwYÙç \ No newline at end of file diff --git a/tests/color.rs b/tests/color.rs index 9a991a1..83d3476 100644 --- a/tests/color.rs +++ b/tests/color.rs @@ -58,17 +58,17 @@ fn test_color_rba16() { // Test case 1: Color value with maximum component values let color = Color::WHITE; let pixel = color.rgba16(); - assert_eq!(pixel, (0xFF, 0xFF)); + assert_eq!(pixel, [0xFF, 0xFF]); // Test case 2: Color value with minimum component values let color = Color::BLACK; let pixel = color.rgba16(); - assert_eq!(pixel, (0, 1)); + assert_eq!(pixel, [0, 1]); // Test case 3: Random color values let color = Color::RGBA(120, 200, 50, 150); let pixel = color.rgba16(); - assert_eq!(pixel, (126, 76)); + assert_eq!(pixel, [126, 76]); } #[test] diff --git a/tests/i4.png b/tests/i4.png new file mode 100644 index 0000000000000000000000000000000000000000..a7d16e2e857383ba4e78481eae37dc2b451c1747 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~j!2~2Dq<3>+gKh t;$gVX!S*j=#d@G241B2(o@u_m3|c@o2M~k6rQpdR%G1@)Wt~$(69DR(B@6%n literal 0 HcmV?d00001 diff --git a/tests/ia4.png.bin b/tests/ia4.png.bin new file mode 100644 index 0000000..e96e018 --- /dev/null +++ b/tests/ia4.png.bin @@ -0,0 +1 @@ +FŠÎW›ß \ No newline at end of file diff --git a/tests/native_image.rs b/tests/native_image.rs new file mode 100644 index 0000000..b2d7c05 --- /dev/null +++ b/tests/native_image.rs @@ -0,0 +1,147 @@ +use anyhow::Result; +use pigment64::image::native_image::parse_tlut; +use pigment64::{create_palette_from_png, ImageSize, ImageType, NativeImage, PNGImage, TextureLUT}; + +#[test] +fn ci4() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("ci4.data.bin"); + let image = NativeImage::read(original_bytes, ImageType::Ci4, 4, 4)?; + + let tlut_bytes: &[u8] = include_bytes!("ci4.tlut.bin"); + let tlut_table: Vec = parse_tlut(tlut_bytes, ImageSize::Bits4, TextureLUT::Rgba16)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, Some(tlut_table.as_slice()))?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_ci4(&mut output_bytes)?; + + // convert the png back to a texture lut + let mut output_tlut: Vec = Vec::new(); + create_palette_from_png(output.as_slice(), &mut output_tlut)?; + + assert_eq!(output_bytes, original_bytes); + assert_eq!(output_tlut, tlut_bytes); + Ok(()) +} + +#[test] +fn i4() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("i4.png.bin"); + let image = NativeImage::read(original_bytes, ImageType::I4, 16, 1)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, None)?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_i4(&mut output_bytes)?; + + assert_eq!(output_bytes, original_bytes); + Ok(()) +} + +#[test] +fn i8() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("i8.png.bin"); + let image = NativeImage::read(original_bytes, ImageType::I8, 16, 16)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, None)?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_i8(&mut output_bytes)?; + + assert_eq!(output_bytes, original_bytes); + Ok(()) +} + +#[test] +fn ia4() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("ia4.png.bin"); + let image = NativeImage::read(original_bytes, ImageType::Ia4, 16, 1)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, None)?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_ia4(&mut output_bytes)?; + + assert_eq!(output_bytes, original_bytes); + Ok(()) +} + +#[test] +fn ia8() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("ia8.png.bin"); + let image = NativeImage::read(original_bytes, ImageType::Ia8, 16, 16)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, None)?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_ia8(&mut output_bytes)?; + + assert_eq!(output_bytes, original_bytes); + Ok(()) +} + +#[test] +fn ia16() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("ia16.png.bin"); + let image = NativeImage::read(original_bytes, ImageType::Ia16, 256, 256)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, None)?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_ia16(&mut output_bytes)?; + + assert_eq!(output_bytes, original_bytes); + Ok(()) +} + +#[test] +fn rgba16() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("rgba16.png.bin"); + let image = NativeImage::read(original_bytes, ImageType::Rgba16, 256, 256)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, None)?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_rgba16(&mut output_bytes)?; + + assert_eq!(output_bytes, original_bytes); + Ok(()) +} + +#[test] +fn rgba32() -> Result<()> { + let original_bytes: &[u8] = include_bytes!("rgba32.png.bin"); + let image = NativeImage::read(original_bytes, ImageType::Rgba32, 32, 32)?; + + let mut output: Vec = Vec::new(); + image.as_png(&mut output, None)?; + + // convert the png back to a native image + let image = PNGImage::read(output.as_slice())?; + let mut output_bytes: Vec = Vec::new(); + image.as_rgba32(&mut output_bytes)?; + + assert_eq!(output_bytes, original_bytes); + Ok(()) +} diff --git a/tests/png_image.rs b/tests/png_image.rs index 7f624c5..ec960ac 100644 --- a/tests/png_image.rs +++ b/tests/png_image.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use pigment64::PNGImage; +use pigment64::{create_palette_from_png, PNGImage}; use std::io::Cursor; // TODO: convert input into all permutations of color type and bit depth @@ -15,25 +15,31 @@ use std::io::Cursor; // Ok(()) // } -// #[test] -// fn ci4() -> Result<()> { -// let input_bytes = include_bytes!("ci4.png"); -// let image = Image::read_png(&mut Cursor::new(input_bytes)); +#[test] +fn ci4() -> Result<()> { + let input_bytes: &[u8] = include_bytes!("ci4.png"); + let image = PNGImage::read(input_bytes)?; -// let expected_bytes = include_bytes!("ci4.png.bin"); -// assert_eq!(image.as_ci4(), expected_bytes); -// Ok(()) -// } + let expected_bytes = include_bytes!("ci4.data.bin"); + let mut output: Vec = Vec::new(); + image.as_ci4(&mut output)?; -// #[test] -// fn i4() -> Result<()> { -// let input_bytes = include_bytes!("i4.png"); -// let image = Image::read_png(&mut Cursor::new(input_bytes)); + assert_eq!(output, expected_bytes); + Ok(()) +} -// let expected_bytes = include_bytes!("i4.png.bin"); -// assert_eq!(image.as_i4(), expected_bytes); -// Ok(()) -// } +#[test] +fn i4() -> Result<()> { + let input_bytes: &[u8] = include_bytes!("i4.png"); + let image = PNGImage::read(input_bytes)?; + + let expected_bytes = include_bytes!("i4.png.bin"); + let mut output: Vec = Vec::new(); + image.as_i4(&mut output)?; + + assert_eq!(output, expected_bytes); + Ok(()) +} #[test] fn i8() -> Result<()> { @@ -48,15 +54,18 @@ fn i8() -> Result<()> { Ok(()) } -// #[test] -// fn ia4() -> Result<()> { -// let input_bytes = include_bytes!("ia4.png"); -// let image = Image::read_png(&mut Cursor::new(input_bytes)); +#[test] +fn ia4() -> Result<()> { + let input_bytes: &[u8] = include_bytes!("ia4.png"); + let image = PNGImage::read(input_bytes)?; -// let expected_bytes = include_bytes!("ia4.png.bin"); -// assert_eq!(image.as_ia4(), expected_bytes); -// Ok(()) -// } + let expected_bytes = include_bytes!("ia4.png.bin"); + let mut output: Vec = Vec::new(); + image.as_ia4(&mut output)?; + + assert_eq!(output, expected_bytes); + Ok(()) +} #[test] fn ia8() -> Result<()> { @@ -110,12 +119,13 @@ fn rgba32() -> Result<()> { Ok(()) } -// #[test] -// fn palette() -> Result<()> { -// let input_bytes = include_bytes!("ci8.png"); -// let palette = get_palette_rgba16(&mut Cursor::new(input_bytes)); +#[test] +fn palette() -> Result<()> { + let input_bytes: &[u8] = include_bytes!("ci4.png"); + let mut output_tlut: Vec = Vec::new(); + create_palette_from_png(input_bytes, &mut output_tlut)?; -// let expected_bytes = include_bytes!("ci8.pal.bin"); -// assert_eq!(palette, expected_bytes); -// Ok(()) -// } + let expected_bytes = include_bytes!("ci4.tlut.bin"); + assert_eq!(output_tlut, expected_bytes); + Ok(()) +}