1use inflate::inflate_bytes_zlib;
2use std::str;
3use std::{fs::File, io::Read};
4
5mod tests;
6use crate::types::RGB;
7
8#[derive(Default)]
9struct PngChunk {
10 chunk_type: String,
11 data_len: usize,
12 chunk_len: usize,
13 data: Vec<u8>,
14 crc: Vec<u8>,
15}
16
17#[derive(PartialEq, Debug)]
18enum ColorType {
19 TrueColorRGB,
20 Indexed,
21}
22impl ColorType {
23 fn num_channels(&self) -> usize {
24 match self {
25 ColorType::TrueColorRGB => 3,
26 ColorType::Indexed => 1,
27 }
28 }
29}
30struct IHDRData {
31 width: u32,
32 height: u32,
33 bit_depth: u8,
34 color_type: ColorType,
35}
36
37impl Default for IHDRData {
38 fn default() -> IHDRData {
39 IHDRData {
40 width: 0,
41 height: 0,
42 bit_depth: 0,
43 color_type: ColorType::TrueColorRGB,
44 }
45 }
46}
47
48fn to_hex_string(bytes: Vec<u8>) -> Vec<String> {
49 bytes.iter().map(|b| format!("{:02X}", b)).collect()
50}
51
52pub fn check_valid_png(file: &mut File) {
53 const VALID_PNG: &str = "89504E470D0A1A0A";
54
55 let first_bytes: &mut Vec<u8> = &mut vec![0; 8];
56 let _ = File::read(file, first_bytes);
57 if to_hex_string(first_bytes.to_vec()).join("") != VALID_PNG {
58 eprintln!("pint: given file is not a valid png");
59 std::process::exit(0);
60 }
61}
62
63fn bytes_to_int(bytes_arr: &[u8]) -> u32 {
64 let mut dst = [0u8; 4];
65 dst.clone_from_slice(bytes_arr);
66 u32::from_be_bytes(dst)
67}
68fn parse_png_chunks(buf: &[u8]) -> Option<PngChunk> {
69 let mut result: PngChunk = PngChunk::default();
70 let chunk_types = ["IHDR", "PLTE", "IDAT", "IEND"];
71
72 result.data_len = bytes_to_int(&buf[0..4]) as usize;
73 result.chunk_len = result.data_len + 12;
74 result.chunk_type = match str::from_utf8(&buf[4..8]) {
75 Ok(s) => s.to_string(),
76 Err(_) => return None,
77 };
78
79 if !chunk_types.contains(&result.chunk_type.as_str()) {
80 return None;
81 }
82
83 result.data = buf[8..8 + result.data_len].to_vec();
84 result.crc = buf[8 + result.data_len..8 + result.data_len].to_vec();
85
86 Some(result)
87}
88
89fn parse_ihdr(data: Vec<u8>) -> IHDRData {
90 let result = IHDRData {
91 width: bytes_to_int(&data[0..4]),
92 height: bytes_to_int(&data[4..8]),
93 bit_depth: data[8],
94 color_type: match data[9] {
95 2 => ColorType::TrueColorRGB,
96 3 => ColorType::Indexed,
97 _ => panic!("Image Color-type is not Indexed or TrueColorRGB"),
98 },
99 };
100 assert!(result.bit_depth == 8, "images need bit-depth of 8");
101 result
102}
103
104#[derive(Clone, Copy, Debug)]
105enum RGBorU8 {
106 Rgb(RGB),
107 U8(u8),
108}
109
110fn get_current_pixel(
111 current_row: &[u8],
112 line_pos: usize,
113 plte: &Option<Vec<RGB>>,
114 color_type: &ColorType,
115) -> RGBorU8 {
116 match color_type {
117 ColorType::TrueColorRGB => RGBorU8::Rgb(RGB(
118 current_row[line_pos],
119 current_row[line_pos + 1],
120 current_row[line_pos + 2],
121 )),
122 ColorType::Indexed => RGBorU8::U8(current_row[line_pos]),
123 }
124}
125
126fn none(current_pixel: u8, prev_pixel: u8, up_pixel: u8, diag_pixel: u8) -> u8 {
127 current_pixel
128}
129fn sub_filter(current_pixel: u8, prev_pixel: u8, up_pixel: u8, diag_pixel: u8) -> u8 {
130 ((current_pixel as u16 + prev_pixel as u16) % 256_u16) as u8
132}
133fn up_filter(current_pixel: u8, prev_pixel: u8, up_pixel: u8, diag_pixel: u8) -> u8 {
134 ((current_pixel as u16 + up_pixel as u16) % 256_u16) as u8
135}
136fn avg_filter(current_pixel: u8, prev_pixel: u8, up_pixel: u8, diag_pixel: u8) -> u8 {
137 ((current_pixel as i16 + (up_pixel + prev_pixel) as i16 / 2) % 256) as u8
138}
139fn paeth_filter(current_pixel: u8, prev_pixel: u8, up_pixel: u8, diag_pixel: u8) -> u8 {
140 let p = prev_pixel as i16 + up_pixel as i16 - diag_pixel as i16;
141 let p_left_pixel = (p - prev_pixel as i16).abs();
142 let p_up_pixel = (p - up_pixel as i16).abs();
143 let p_diag_pixel = (p - diag_pixel as i16).abs();
144
145 let mut prediction = 0;
146 if p_left_pixel <= p_up_pixel && p_left_pixel <= p_diag_pixel {
147 prediction = prev_pixel;
148 } else if p_up_pixel <= p_diag_pixel {
149 prediction = up_pixel;
150 } else {
151 prediction = diag_pixel;
152 }
153 ((current_pixel as i16 + prediction as i16) % 256) as u8
154}
155
156fn do_filter(
157 i: usize,
158 current: RGBorU8,
159 prev: &mut RGBorU8,
160 up: RGBorU8,
161 diag: RGBorU8,
162 plte: &Option<Vec<RGB>>,
163) -> RGB {
164 let filter = [none, sub_filter, up_filter, avg_filter, paeth_filter];
165 assert!(i < filter.len(), "No such filter exists");
166
167 match current {
168 RGBorU8::Rgb(c) => {
169 let p = match prev {
170 RGBorU8::Rgb(p) => p,
171 _ => panic!("RGB values dont store u8"),
172 };
173
174 let u = match up {
175 RGBorU8::Rgb(u) => u,
176 _ => panic!("RGB values dont store u8"),
177 };
178 let d = match diag {
179 RGBorU8::Rgb(d) => d,
180 _ => panic!("RGB values dont store u8"),
181 };
182 let applied = RGB(
183 filter[i](c.0, p.0, u.0, d.0),
184 filter[i](c.1, p.1, u.1, d.1),
185 filter[i](c.2, p.2, u.2, d.2),
186 );
187 *prev = RGBorU8::Rgb(applied);
188 applied
189 }
190 RGBorU8::U8(c) => {
191 let p = match prev {
192 RGBorU8::U8(p) => *p,
193 _ => panic!("Indexed values dont store RGB"),
194 };
195 let u = match up {
196 RGBorU8::U8(u) => u,
197 _ => panic!("U8 values dont store RGB"),
198 };
199 let d = match diag {
200 RGBorU8::U8(d) => d,
201 _ => panic!("U8 values dont store RGB"),
202 };
203
204 let applied = filter[i](c, p, u, d);
205 *prev = RGBorU8::U8(applied);
206 (*plte).as_ref().expect("plte exists on indexed color-type")[applied as usize]
207 }
208 }
209}
210
211fn get_diag_pixel(
212 prev_row: &[RGBorU8],
213 line_pos: usize,
214 default_val: RGBorU8,
215 color_type: &ColorType,
216) -> RGBorU8 {
217 if (line_pos as i16 / color_type.num_channels() as i16) > 0 {
218 prev_row[(line_pos / color_type.num_channels()) - 1]
219 } else {
220 default_val
221 }
222}
223
224fn apply_filter(
225 current_row: &[u8],
226 prev_row: &mut Vec<RGBorU8>,
227 filter_index: usize,
228 color_type: &ColorType,
229 plte: &Option<Vec<RGB>>,
230 default_val: RGBorU8,
231) -> Vec<RGB> {
232 let mut line_pos = 0;
233 let mut prev_pixel = default_val;
234 let mut result: Vec<RGB> = Vec::new();
235 let mut current_row_applied = Vec::new();
236
237 while line_pos < current_row.len() - (color_type.num_channels() - 1) {
238 let pixel = get_current_pixel(current_row, line_pos, plte, color_type);
239 let up = prev_row[line_pos / color_type.num_channels()];
240 let diag = get_diag_pixel(prev_row, line_pos, default_val, color_type);
241
242 result.push(do_filter(
243 filter_index,
244 pixel,
245 &mut prev_pixel,
246 up,
247 diag,
248 plte,
249 ));
250 current_row_applied.push(prev_pixel);
251
252 line_pos += color_type.num_channels();
253 }
254 *prev_row = current_row_applied;
255
256 result
257}
258
259fn parse_data(
260 image_data: Vec<Vec<u8>>,
261 meta_data: IHDRData,
262 plte: Option<Vec<RGB>>,
263 default_val: RGBorU8,
264) -> Vec<Vec<RGB>> {
265 let mut inflated = Vec::new();
266 for i in image_data {
267 inflated.append(&mut inflate_bytes_zlib(&i as &[u8]).unwrap());
268 }
269 let mut rgb_img: Vec<Vec<RGB>> = Vec::new();
270 let mut j = 0;
271 let byte_width = meta_data.width as usize * meta_data.color_type.num_channels();
272 let mut prev_row: Vec<RGBorU8> = vec![default_val; meta_data.width as usize];
273
274 let mut i = 0;
275 while j + byte_width < inflated.len() {
276 i += 1;
277 let filter = inflated[j] as usize;
278 let current_row = &inflated[j + 1..j + byte_width + 1]; rgb_img.push(apply_filter(
281 current_row,
282 &mut prev_row,
283 filter,
284 &meta_data.color_type,
285 &plte,
286 default_val,
287 ));
288
289 j += byte_width + 1;
290 }
291
292 rgb_img
293}
294
295fn parse_plte(data: Vec<u8>) -> Vec<RGB> {
296 assert!(data.len() % 3 == 0, "data should be splittable in triplets");
297
298 let mut result: Vec<RGB> = Vec::new();
299 for i in (0..data.len()).step_by(3) {
300 result.push(RGB(data[i], data[i + 1], data[i + 2]))
301 }
302 result
303}
304
305pub fn decode_png(mut file: File) -> Vec<Vec<RGB>> {
306 let file_byte_size = File::metadata(&file).unwrap().len();
307 let buf: &mut [u8] = &mut vec![0; (file_byte_size - 8) as usize]; let result = File::read(&mut file, buf);
309 let mut i: u32 = 0;
310 let mut meta_data: IHDRData = IHDRData::default();
311 let mut plte: Option<Vec<RGB>> = None;
312 let mut data: Vec<Vec<u8>> = Vec::new();
313 let mut rgb_img: Vec<Vec<RGB>> = Vec::new();
314
315 assert!(file_byte_size - 8 > 0);
316 while i < (file_byte_size - 8) as u32 {
317 let chunk = match parse_png_chunks(&buf[i as usize..]) {
318 Some(c) => c,
319 None => {
320 i += 1;
321 continue;
322 }
323 };
324
325 match chunk.chunk_type.as_str() {
326 "IHDR" => meta_data = parse_ihdr(chunk.data),
327 "PLTE" => plte = Some(parse_plte(chunk.data)),
328 "IDAT" => {
329 if meta_data.color_type == ColorType::Indexed && plte == None {
330 panic!("pint: couldn't find PLTE chunk in image.");
332 }
333 data.push(chunk.data);
334 }
335 "IEND" => {
336 let default_val = match meta_data.color_type {
337 ColorType::TrueColorRGB => RGBorU8::Rgb(RGB(0, 0, 0)),
338 ColorType::Indexed => RGBorU8::U8(0),
339 };
340 rgb_img = parse_data(data, meta_data, plte, default_val);
341
342 break;
343 }
344 _ => {
345 i += 1;
346 continue;
347 }
348 }
349 i += chunk.chunk_len as u32;
350 }
351 rgb_img
352}
353pub fn infer_codel_size(rgb_img: &Vec<Vec<RGB>>) -> i32 {
354 let mut min_size = i32::MAX;
355 let mut current_size = 1i32;
356 let mut current_color: RGB;
357
358 for y in 0..rgb_img.len() {
360 let mut prev_color: RGB = rgb_img[y][0];
361 for x in 1..rgb_img[0].len() {
362 current_color = rgb_img[y][x];
363 if prev_color == current_color {
364 current_size += 1;
365 } else if current_size < min_size {
366 min_size = current_size;
367 current_size = 1;
368 }
369 prev_color = current_color;
370 }
371 }
372 for x in 0..rgb_img[0].len() {
374 let mut prev_color: RGB = rgb_img[0][x];
375 for y in 1..rgb_img.len() {
376 current_color = rgb_img[y][x];
377 if prev_color == current_color {
378 current_size += 1;
379 } else if current_size < min_size {
380 min_size = current_size;
381 current_size = 1;
382 }
383 prev_color = current_color;
384 }
385 }
386 if min_size < 1 {
387 eprintln!("warning: inferred codel-size less than 1 => defaults to 1");
388 1
389 } else if rgb_img[0].len() as i32 % min_size != 0 || rgb_img.len() as i32 % min_size != 0 {
390 eprintln!("warning: inferred codel-size doesnt fit image-dimensions => defaults to 1");
391 1
392 } else {
393 min_size
394 }
395}