1#![cfg(feature = "image")]
9
10use image::GrayImage;
11
12#[derive(Debug, Clone)]
14pub struct DecodedGray {
15 pub gray: GrayImage,
17 pub filter: String,
19}
20
21pub fn decode_image_to_gray(
23 data: &[u8],
24 filter: &str,
25 width: u32,
26 height: u32,
27) -> Option<DecodedGray> {
28 let primary = primary_filter(filter);
29 let gray = match primary {
30 "DCTDecode" => decode_dct(data),
31 "JPXDecode" => decode_jpx(data, width, height),
32 "JBIG2Decode" => decode_jbig2(data, width, height),
33 "CCITTFaxDecode" => decode_ccitt_best_effort(data, width, height),
34 _ => decode_raw_or_flate(data, width, height).or_else(|| decode_dct(data)),
35 }?;
36 Some(DecodedGray {
37 gray,
38 filter: primary.to_string(),
39 })
40}
41
42fn primary_filter(filter: &str) -> &str {
43 filter
44 .split([' ', ',', '[', ']'])
45 .map(str::trim)
46 .rfind(|s| !s.is_empty())
47 .unwrap_or(filter)
48}
49
50fn decode_dct(data: &[u8]) -> Option<GrayImage> {
51 image::load_from_memory(data).ok().map(|img| img.to_luma8())
52}
53
54fn decode_raw_or_flate(data: &[u8], width: u32, height: u32) -> Option<GrayImage> {
55 if width == 0 || height == 0 {
56 return None;
57 }
58 let n = (width as usize).checked_mul(height as usize)?;
59 if data.len() == n {
60 return GrayImage::from_raw(width, height, data.to_vec());
61 }
62 if data.len() == n * 3 {
63 let mut luma = Vec::with_capacity(n);
64 for px in data.chunks_exact(3) {
65 let y = (0.299 * f64::from(px[0]) + 0.587 * f64::from(px[1]) + 0.114 * f64::from(px[2]))
66 .round()
67 .clamp(0.0, 255.0) as u8;
68 luma.push(y);
69 }
70 return GrayImage::from_raw(width, height, luma);
71 }
72 if data.len() == n * 4 {
73 let mut luma = Vec::with_capacity(n);
74 for px in data.chunks_exact(4) {
75 let y = (0.299 * f64::from(px[0]) + 0.587 * f64::from(px[1]) + 0.114 * f64::from(px[2]))
76 .round()
77 .clamp(0.0, 255.0) as u8;
78 luma.push(y);
79 }
80 return GrayImage::from_raw(width, height, luma);
81 }
82 None
83}
84
85fn decode_ccitt_best_effort(data: &[u8], width: u32, height: u32) -> Option<GrayImage> {
86 if width == 0 || height == 0 {
87 return None;
88 }
89 let row_bytes = (width as usize).div_ceil(8);
90 let expected = row_bytes.checked_mul(height as usize)?;
91 if data.len() < expected {
92 return None;
93 }
94 let mut luma = Vec::with_capacity((width * height) as usize);
95 for y in 0..height as usize {
96 let row = &data[y * row_bytes..(y + 1) * row_bytes];
97 for x in 0..width as usize {
98 let byte = row[x / 8];
99 let bit = 7 - (x % 8);
100 let on = ((byte >> bit) & 1) != 0;
101 luma.push(if on { 0 } else { 255 });
102 }
103 }
104 GrayImage::from_raw(width, height, luma)
105}
106
107#[cfg(feature = "codecs-jpx")]
108fn decode_jpx(data: &[u8], width: u32, height: u32) -> Option<GrayImage> {
109 let img = jpeg2k::Image::from_bytes(data).ok()?;
111 let w = img.width().max(1);
112 let h = img.height().max(1);
113 let _ = (width, height);
114 let pixels = img.get_pixels(None).ok()?;
115 let mut luma = Vec::with_capacity((w * h) as usize);
116 let bpp = if pixels.len() >= (w * h * 4) as usize {
118 4
119 } else if pixels.len() >= (w * h * 3) as usize {
120 3
121 } else if pixels.len() >= (w * h) as usize {
122 1
123 } else {
124 return None;
125 };
126 if bpp == 1 {
127 return GrayImage::from_raw(w, h, pixels[..(w * h) as usize].to_vec());
128 }
129 for px in pixels.chunks_exact(bpp) {
130 let y = (0.299 * f64::from(px[0]) + 0.587 * f64::from(px[1]) + 0.114 * f64::from(px[2]))
131 .round()
132 .clamp(0.0, 255.0) as u8;
133 luma.push(y);
134 }
135 GrayImage::from_raw(w, h, luma)
136}
137
138#[cfg(not(feature = "codecs-jpx"))]
139fn decode_jpx(data: &[u8], width: u32, height: u32) -> Option<GrayImage> {
140 decode_raw_or_flate(data, width, height).or_else(|| decode_dct(data))
142}
143
144#[cfg(feature = "codecs-jbig2")]
145fn decode_jbig2(data: &[u8], _width: u32, _height: u32) -> Option<GrayImage> {
146 use hayro_jbig2::{Decoder, Image as Jbig2Image};
147
148 let image = Jbig2Image::new_embedded(data, None)
149 .or_else(|_| Jbig2Image::new(data))
150 .ok()?;
151 let w = image.width();
152 let h = image.height();
153 let mut buf = vec![255u8; (w as usize).saturating_mul(h as usize)];
154
155 struct LumaBuf<'a> {
156 buf: &'a mut [u8],
157 pos: usize,
158 }
159 impl Decoder for LumaBuf<'_> {
160 fn push_pixel(&mut self, black: bool) {
161 if self.pos < self.buf.len() {
162 self.buf[self.pos] = if black { 0 } else { 255 };
163 self.pos += 1;
164 }
165 }
166 fn push_pixel_chunk(&mut self, black: bool, chunk_count: u32) {
167 let luma = if black { 0 } else { 255 };
168 let count = (chunk_count as usize).saturating_mul(8);
169 let end = (self.pos + count).min(self.buf.len());
170 self.buf[self.pos..end].fill(luma);
171 self.pos = end;
172 }
173 fn next_line(&mut self) {}
174 }
175
176 let mut decoder = LumaBuf {
177 buf: &mut buf,
178 pos: 0,
179 };
180 image.decode(&mut decoder).ok()?;
181 GrayImage::from_raw(w, h, buf)
182}
183
184#[cfg(not(feature = "codecs-jbig2"))]
185fn decode_jbig2(data: &[u8], width: u32, height: u32) -> Option<GrayImage> {
186 decode_ccitt_best_effort(data, width, height)
187 .or_else(|| decode_raw_or_flate(data, width, height))
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn raw_gray_roundtrip() {
196 let data = vec![0u8, 128, 255, 64];
197 let g = decode_image_to_gray(&data, "FlateDecode", 2, 2).unwrap();
198 assert_eq!(g.gray.width(), 2);
199 assert_eq!(g.gray.height(), 2);
200 }
201
202 #[test]
203 fn primary_filter_from_array_like() {
204 assert_eq!(primary_filter("FlateDecode"), "FlateDecode");
205 assert_eq!(primary_filter("[ FlateDecode ]"), "FlateDecode");
206 }
207
208 #[test]
209 fn jbig2_filter_does_not_panic_without_feature() {
210 let _ = decode_image_to_gray(&[0u8; 16], "JBIG2Decode", 4, 4);
211 }
212
213 #[test]
214 fn jpx_filter_does_not_panic_without_feature() {
215 let _ = decode_image_to_gray(&[0u8; 16], "JPXDecode", 4, 4);
216 }
217}