1use super::indexed_image::{IndexedImage, pixel_count};
4use crate::{Result, error::Error};
5
6#[derive(Debug, Clone)]
7pub struct PcxFile {
8 pub version: u8,
9 pub encoding: u8,
10 pub x_min: u16,
11 pub y_min: u16,
12 pub bytes_per_line: u16,
13 pub image: IndexedImage,
14}
15
16pub fn parse_pcx_file(data: &[u8]) -> Result<PcxFile> {
19 if data.len() < 128 + 769 || data[0] != 0x0a {
20 return Err(Error::Parse(
21 "PCX: invalid signature or truncated file".into(),
22 ));
23 }
24 if data[3] != 8 || data[65] != 1 || data[2] > 1 {
25 return Err(Error::Parse(
26 "PCX: expected raw or RLE 8-bit single-plane image".into(),
27 ));
28 }
29 let word = |i| u16::from_le_bytes([data[i], data[i + 1]]);
30 let x_min = word(4);
31 let y_min = word(6);
32 let width = word(8)
33 .checked_sub(x_min)
34 .and_then(|n| n.checked_add(1))
35 .ok_or_else(|| Error::Parse("PCX: invalid horizontal bounds".into()))?;
36 let height = word(10)
37 .checked_sub(y_min)
38 .and_then(|n| n.checked_add(1))
39 .ok_or_else(|| Error::Parse("PCX: invalid vertical bounds".into()))?;
40 let count = pixel_count(width, height)?;
41 let bytes_per_line = word(66);
42 if bytes_per_line < width {
43 return Err(Error::Parse(
44 "PCX: scanline stride smaller than width".into(),
45 ));
46 }
47 let palette_start = data.len() - 769;
48 if data[palette_start] != 0x0c {
49 return Err(Error::Parse("PCX: missing 256-color palette marker".into()));
50 }
51 let mut palette = [[0; 3]; 256];
52 for (color, bytes) in palette
53 .iter_mut()
54 .zip(data[palette_start + 1..].as_chunks::<3>().0)
55 {
56 color.copy_from_slice(bytes);
57 }
58 let encoded = &data[128..palette_start];
59 let mut cursor = 0;
60 let mut pixels = Vec::with_capacity(count);
61 for _ in 0..height {
62 let mut row = Vec::with_capacity(usize::from(bytes_per_line));
63 while row.len() < usize::from(bytes_per_line) {
64 let control = *encoded
65 .get(cursor)
66 .ok_or_else(|| Error::Parse("PCX: truncated scanline".into()))?;
67 cursor += 1;
68 let (run, value) = if data[2] == 1 && control & 0xc0 == 0xc0 {
69 let value = *encoded
70 .get(cursor)
71 .ok_or_else(|| Error::Parse("PCX: truncated RLE run".into()))?;
72 cursor += 1;
73 (usize::from(control & 0x3f), value)
74 } else {
75 (1, control)
76 };
77 if run == 0 || row.len() + run > usize::from(bytes_per_line) {
78 return Err(Error::Parse(
79 "PCX: invalid run across scanline boundary".into(),
80 ));
81 }
82 row.resize(row.len() + run, value);
83 }
84 pixels.extend_from_slice(&row[..usize::from(width)]);
85 }
86 if cursor != encoded.len() {
87 return Err(Error::Parse(
88 "PCX: trailing image data before palette".into(),
89 ));
90 }
91 Ok(PcxFile {
92 version: data[1],
93 encoding: data[2],
94 x_min,
95 y_min,
96 bytes_per_line,
97 image: IndexedImage {
98 width,
99 height,
100 palette,
101 pixels,
102 transparent_index: None,
103 },
104 })
105}