Skip to main content

rgpre/import/
pcx.rs

1//! Eight-bit, single-plane PCX images. See docs/formats/PCX.md.
2
3use 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
16/// Decode raw or RLE 8-bit PCX with a trailing 256-color palette.
17/// Other bit depths and plane counts are rejected. Images are limited to 16 Mi pixels.
18pub 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}