From 5e1aacafd0fb1965a71d80e89c5bc76bceb5a45c Mon Sep 17 00:00:00 2001 From: Isotr0py <2037008807@qq.com> Date: Mon, 7 Oct 2024 17:48:30 +0800 Subject: [PATCH] support I;16 pixel values decoding --- src/decode.rs | 118 +++++++++++++++++++++++++----------- test/images/sample_grey.jxl | Bin 0 -> 808 bytes test/images/sample_grey.png | Bin 0 -> 3208 bytes test/test_plugin.py | 23 +++++-- 4 files changed, 101 insertions(+), 40 deletions(-) create mode 100644 test/images/sample_grey.jxl create mode 100644 test/images/sample_grey.png diff --git a/src/decode.rs b/src/decode.rs index 56ca34c..c56930d 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -24,9 +24,13 @@ struct ImageInfo { } impl ImageInfo { - fn from(item: Metadata) -> ImageInfo { + fn from(item: Metadata, pixel: &Data) -> ImageInfo { + let pixel_type = match &pixel { + Data::Pixels(pixels) => Some(pixels), + Data::Jpeg(_) => None, + }; ImageInfo { - mode: Self::mode(item.num_color_channels, item.has_alpha_channel), + mode: Self::mode(item.num_color_channels, item.has_alpha_channel, pixel_type), width: item.width, height: item.height, num_channels: item.num_color_channels, @@ -34,39 +38,26 @@ impl ImageInfo { } } - fn mode(num_channels: u32, has_alpha_channel: bool) -> String { - match (num_channels, has_alpha_channel) { + fn mode(num_channels: u32, has_alpha_channel: bool, pixel_type: Option<&Pixels>) -> String { + let mode = match (num_channels, has_alpha_channel) { (1, false) => "L".to_string(), (1, true) => "LA".to_string(), (3, false) => "RGB".to_string(), (3, true) => "RGBA".to_string(), _ => panic!("Unsupported number of channels"), - } - } -} - -pub fn convert_pixels(pixels: Pixels) -> Vec { - let mut result = Vec::new(); - match pixels { - Pixels::Uint8(pixels) => { - for pixel in pixels { - result.push(pixel); - } - } - Pixels::Uint16(pixels) => { - for pixel in pixels { - result.push((pixel >> 8) as u8); - result.push(pixel as u8); + }; + if let Some(Pixels::Uint16(_)) = pixel_type { + if mode == "L" { + return "I;16".to_string(); } } - Pixels::Float(pixels) => { - for pixel in pixels { - result.push((pixel * 255.0) as u8); + if let Some(Pixels::Float(_)) = pixel_type { + if mode == "L" { + return "F".to_string(); } } - Pixels::Float16(_) => panic!("Float16 is not supported yet"), + mode } - result } #[pyclass(module = "pillow_jxl")] @@ -96,6 +87,67 @@ impl Decoder { } } +impl Decoder { + fn pixels_to_bytes_8bit(&self, pixels: Pixels) -> Vec { + // Convert pixels to bytes with 8-bit casting + let mut result = Vec::new(); + match pixels { + Pixels::Uint8(pixels) => { + return pixels; + } + Pixels::Uint16(pixels) => { + for pixel in pixels { + result.push((pixel >> 8) as u8); + } + } + Pixels::Float(pixels) => { + for pixel in pixels { + result.push((pixel * 255.0) as u8); + } + } + Pixels::Float16(_) => panic!("Float16 is not supported yet"), + } + result + } + + fn pixels_to_bytes(&self, pixels: Pixels) -> Vec { + // Convert pixels to bytes without casting + let mut result = Vec::new(); + match pixels { + Pixels::Uint8(pixels) => { + return pixels; + } + Pixels::Uint16(pixels) => { + for pixel in pixels { + let pix_bytes = pixel.to_ne_bytes(); + for byte in pix_bytes.iter() { + result.push(*byte); + } + } + } + Pixels::Float(pixels) => { + for pixel in pixels { + let pix_bytes = pixel.to_ne_bytes(); + for byte in pix_bytes.iter() { + result.push(*byte); + } + } + } + Pixels::Float16(_) => panic!("Float16 is not supported yet"), + } + result + } + + fn convert_pil_pixels(&self, pixels: Pixels, num_channels: u32) -> Vec { + let result = match num_channels { + 1 => self.pixels_to_bytes(pixels), + 3 => self.pixels_to_bytes_8bit(pixels), + _ => panic!("Unsupported number of channels"), + }; + result + } +} + impl Decoder { fn call_inner(&self, data: &[u8]) -> PyResult<(bool, ImageInfo, Cow<'_, [u8]>, Cow<'_, [u8]>)> { let parallel_runner = ThreadsRunner::new( @@ -113,20 +165,16 @@ impl Decoder { .build() .map_err(to_pyjxlerror)?; let (info, img) = decoder.reconstruct(&data).map_err(to_pyjxlerror)?; - let (jpeg, img) = match img { - Data::Jpeg(x) => (true, x), - Data::Pixels(x) => (false, convert_pixels(x)), - }; let icc_profile: Vec = match &info.icc_profile { Some(x) => x.to_vec(), None => Vec::new(), }; - Ok(( - jpeg, - ImageInfo::from(info), - Cow::Owned(img), - Cow::Owned(icc_profile), - )) + let img_info = ImageInfo::from(info, &img); + let (jpeg, img) = match img { + Data::Jpeg(x) => (true, x), + Data::Pixels(x) => (false, self.convert_pil_pixels(x, img_info.num_channels)), + }; + Ok((jpeg, img_info, Cow::Owned(img), Cow::Owned(icc_profile))) } } diff --git a/test/images/sample_grey.jxl b/test/images/sample_grey.jxl new file mode 100644 index 0000000000000000000000000000000000000000..c9d8505118485da04d3652b21a1a645ce304b210 GIT binary patch literal 808 zcmV+@1K0fj3Wxzt;3uivTSXBJ#Q*~W0LTCUH~?4xSV|ZZ4+jSUaA+)BMM}d^g#ZvU zG&;yBIrRZVtyT516d*#XKLZ8GRS5=gV^AQ0C;)5=63Vb+Pa#m6Zp8(n>Hz=%6)K~Q z8@!zB`SL@D#k7s<3~jY-VM(IMI5tTqa0n@|qiv%ypi!mYgt9bM&CnVDIxoM2r6k{S;FUI0M7At9VV zayd7=C}0HXIdoL_oY$t9+Eg`j39keBvKe*OaaQyci;N9`Z<8FhDvy+H5w{eccb80{wT zJQ+8G1~S7sV85@PM_omnyX*Wu0^9?wJZX9MI17;EZbWG{O4P<)F%`F9Z)+l8zl(v^ z7)m9)i{3DRzF!!>D|wz{NGxpXKLS#3`sUMPl>F(-PD33|mIOlDs@tu{rYk5xZqPvW z9Gq1H*lVHB=fxMex0IYf4xBjrPqD6q3K(phs2WE$-oetSUweWL^je2jYE5u;4aA=9 z5DH4`RpLMU^|cA@g`j9Y6M*R0m7!Y|WG?T*iFv`4b~y6}HAjJwC#L@0=mRb|2$rlX mfyYEf&`pqs1PoBh@5HD+(3+{=!?Cw)Un{1CFR81Zg2fCvAaK0^ literal 0 HcmV?d00001 diff --git a/test/images/sample_grey.png b/test/images/sample_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..ebf799781a2303fda23df4ea5d910cd5b1515d71 GIT binary patch literal 3208 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