Skip to content

Commit 23385a5

Browse files
fintelialupine
andcommitted
Add PCX decoding
Co-authored-by: Nick Thomas <me@ur.gs>
1 parent a1729b3 commit 23385a5

File tree

7 files changed

+141
-1
lines changed

7 files changed

+141
-1
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ publish = false
77

88
include = ["src", "tests/reference.rs"]
99

10+
[features]
11+
default = ["pcx"]
12+
pcx = []
13+
1014
[dependencies]
1115
image = { version = "0.25.5", default-features = false }
16+
pcx = "0.2.4"
1217

1318
[dev-dependencies]
1419
image = { version = "0.25.5", default-features = false, features = ["png"] }

src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
//! let img = image::open("path/to/image.pcx").unwrap();
1616
//! ```
1717
18+
#[cfg(feature = "pcx")]
19+
pub mod pcx;
20+
1821
/// Register all enabled extra formats with the image crate.
1922
pub fn register() {
20-
// TODO
23+
image::hooks::register_decoding_hook(
24+
image::ImageFormat::Pcx,
25+
Box::new(|r| Ok(Box::new(pcx::PCXDecoder::new(r)?))),
26+
);
2127
}

src/pcx.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//! Decoding of PCX Images
2+
//!
3+
//! PCX (PiCture eXchange) Format is an obsolete image format from the 1980s.
4+
//!
5+
//! # Related Links
6+
//! * <https://en.wikipedia.org/wiki/PCX> - The PCX format on Wikipedia
7+
8+
use std::io::{self, BufRead, Seek};
9+
use std::iter;
10+
11+
use image::{ColorType, ExtendedColorType, ImageDecoder, ImageError, ImageResult};
12+
13+
/// Decoder for PCX images.
14+
pub struct PCXDecoder<R>
15+
where
16+
R: BufRead + Seek,
17+
{
18+
dimensions: (u32, u32),
19+
inner: pcx::Reader<R>,
20+
}
21+
22+
impl<R> PCXDecoder<R>
23+
where
24+
R: BufRead + Seek,
25+
{
26+
/// Create a new `PCXDecoder`.
27+
pub fn new(r: R) -> Result<PCXDecoder<R>, ImageError> {
28+
let inner = pcx::Reader::new(r).map_err(convert_pcx_decode_error)?;
29+
let dimensions = (u32::from(inner.width()), u32::from(inner.height()));
30+
31+
Ok(PCXDecoder { dimensions, inner })
32+
}
33+
}
34+
35+
fn convert_pcx_decode_error(err: io::Error) -> ImageError {
36+
ImageError::IoError(err)
37+
}
38+
39+
impl<R: BufRead + Seek> ImageDecoder for PCXDecoder<R> {
40+
fn dimensions(&self) -> (u32, u32) {
41+
self.dimensions
42+
}
43+
44+
fn color_type(&self) -> ColorType {
45+
ColorType::Rgb8
46+
}
47+
48+
fn original_color_type(&self) -> ExtendedColorType {
49+
if self.inner.is_paletted() {
50+
return ExtendedColorType::Unknown(self.inner.header.bit_depth);
51+
}
52+
53+
match (
54+
self.inner.header.number_of_color_planes,
55+
self.inner.header.bit_depth,
56+
) {
57+
(1, 1) => ExtendedColorType::L1,
58+
(1, 2) => ExtendedColorType::L2,
59+
(1, 4) => ExtendedColorType::L4,
60+
(1, 8) => ExtendedColorType::L8,
61+
(3, 1) => ExtendedColorType::Rgb1,
62+
(3, 2) => ExtendedColorType::Rgb2,
63+
(3, 4) => ExtendedColorType::Rgb4,
64+
(3, 8) => ExtendedColorType::Rgb8,
65+
(4, 1) => ExtendedColorType::Rgba1,
66+
(4, 2) => ExtendedColorType::Rgba2,
67+
(4, 4) => ExtendedColorType::Rgba4,
68+
(4, 8) => ExtendedColorType::Rgba8,
69+
(_, _) => unreachable!(),
70+
}
71+
}
72+
73+
fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
74+
assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
75+
76+
let height = self.inner.height() as usize;
77+
let width = self.inner.width() as usize;
78+
79+
match self.inner.palette_length() {
80+
// No palette to interpret, so we can just write directly to buf
81+
None => {
82+
for i in 0..height {
83+
let offset = i * 3 * width;
84+
self.inner
85+
.next_row_rgb(&mut buf[offset..offset + (width * 3)])
86+
.map_err(convert_pcx_decode_error)?;
87+
}
88+
}
89+
90+
// We need to convert from the palette colours to RGB values inline,
91+
// but the pcx crate can't give us the palette first. Work around it
92+
// by taking the paletted image into a buffer, then converting it to
93+
// RGB8 after.
94+
Some(palette_length) => {
95+
let mut pal_buf: Vec<u8> = iter::repeat(0).take(height * width).collect();
96+
97+
for i in 0..height {
98+
let offset = i * width;
99+
self.inner
100+
.next_row_paletted(&mut pal_buf[offset..offset + width])
101+
.map_err(convert_pcx_decode_error)?;
102+
}
103+
104+
let mut palette: Vec<u8> =
105+
iter::repeat(0).take(3 * palette_length as usize).collect();
106+
self.inner
107+
.read_palette(&mut palette[..])
108+
.map_err(convert_pcx_decode_error)?;
109+
110+
for i in 0..height {
111+
for j in 0..width {
112+
let pixel = pal_buf[i * width + j] as usize;
113+
let offset = i * width * 3 + j * 3;
114+
115+
buf[offset] = palette[pixel * 3];
116+
buf[offset + 1] = palette[pixel * 3 + 1];
117+
buf[offset + 2] = palette[pixel * 3 + 2];
118+
}
119+
}
120+
}
121+
}
122+
123+
Ok(())
124+
}
125+
126+
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
127+
(*self).read_image(buf)
128+
}
129+
}

tests/images/pcx/24bit.pcx

167 KB
Binary file not shown.

tests/images/pcx/24bit.png

78.8 KB
Loading

tests/images/pcx/256-palette.pcx

41.8 KB
Binary file not shown.

tests/images/pcx/256-palette.png

42.5 KB
Loading

0 commit comments

Comments
 (0)