use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use crate::common::error::{BioFormatsError, Result};
use crate::common::metadata::{DimensionOrder, ImageMetadata, LookupTable};
use crate::common::pixel_type::PixelType;
use crate::common::reader::FormatReader;
pub struct PcxReader {
path: Option<PathBuf>,
meta: Option<ImageMetadata>,
pixels: Option<Vec<u8>>,
}
impl PcxReader {
pub fn new() -> Self {
PcxReader {
path: None,
meta: None,
pixels: None,
}
}
}
impl Default for PcxReader {
fn default() -> Self {
Self::new()
}
}
fn load_pcx(path: &Path) -> Result<(ImageMetadata, Vec<u8>)> {
let mut f = File::open(path).map_err(BioFormatsError::Io)?;
let file_len = f.metadata().map_err(BioFormatsError::Io)?.len();
let mut header = [0u8; 128];
f.read_exact(&mut header).map_err(BioFormatsError::Io)?;
if header[0] != 0x0A {
return Err(BioFormatsError::InvalidData(
"PCX: invalid manufacturer byte".into(),
));
}
let version = header[1] as i32;
let _bits_per_pixel = header[3];
let read_i16 =
|off: usize| -> i32 { i16::from_le_bytes([header[off], header[off + 1]]) as i32 };
let x_min = read_i16(4);
let y_min = read_i16(6);
let x_max = read_i16(8);
let y_max = read_i16(10);
let size_x = (x_max - x_min).max(0) as u32;
let size_y = (y_max - y_min).max(0) as u32;
let n_color_planes = if version == 5 {
header[65] as usize
} else {
header[63] as usize
};
let (bpl_off, pal_off) = if version == 5 { (66, 68) } else { (64, 66) };
let bytes_per_line = u16::from_le_bytes([header[bpl_off], header[bpl_off + 1]]) as usize;
let palette_type = u16::from_le_bytes([header[pal_off], header[pal_off + 1]]);
let pixel_offset = (pal_off as u64 + 2) + 58;
if n_color_planes == 0 || bytes_per_line == 0 || size_x == 0 || size_y == 0 {
return Err(BioFormatsError::InvalidData(
"PCX: invalid dimensions / plane count".into(),
));
}
if bytes_per_line < size_x as usize {
return Err(BioFormatsError::InvalidData(
"PCX: bytes-per-line is shorter than image width".into(),
));
}
let mut lookup_table = None;
let mut is_indexed = false;
if version == 5 && n_color_planes == 1 && file_len >= 768 {
f.seek(SeekFrom::Start(file_len - 768))
.map_err(BioFormatsError::Io)?;
let mut pal = [0u8; 768];
f.read_exact(&mut pal).map_err(BioFormatsError::Io)?;
let mut red = vec![0u16; 256];
let mut green = vec![0u16; 256];
let mut blue = vec![0u16; 256];
for i in 0..256 {
red[i] = pal[i * 3] as u16;
green[i] = pal[i * 3 + 1] as u16;
blue[i] = pal[i * 3 + 2] as u16;
}
lookup_table = Some(LookupTable { red, green, blue });
is_indexed = true;
}
f.seek(SeekFrom::Start(pixel_offset))
.map_err(BioFormatsError::Io)?;
let total = bytes_per_line
.checked_mul(size_y as usize)
.and_then(|n| n.checked_mul(n_color_planes))
.ok_or_else(|| BioFormatsError::InvalidData("PCX: image byte count overflows".into()))?;
let mut b = vec![0u8; total];
let mut reader = std::io::BufReader::new(f);
let mut pt = 0usize;
let mut byte = [0u8; 1];
while pt < total {
reader.read_exact(&mut byte).map_err(BioFormatsError::Io)?;
let val = byte[0] as i32;
if ((val & 0xc0) >> 6) == 3 {
let len = (val & 0x3f) as usize;
reader.read_exact(&mut byte).map_err(BioFormatsError::Io)?;
let runval = byte[0];
for _ in 0..len {
if pt >= total {
break;
}
b[pt] = runval;
pt += 1;
if pt % bytes_per_line == 0 {
break;
}
}
} else {
b[pt] = val as u8;
pt += 1;
}
}
let width_usize = size_x as usize;
let height_usize = size_y as usize;
let plane_len = width_usize * height_usize * n_color_planes;
let mut pixels = vec![0u8; plane_len];
for row in 0..height_usize {
let mut row_offset = row * n_color_planes * bytes_per_line;
for c in 0..n_color_planes {
let src = row_offset;
let dst = c * width_usize * height_usize + row * width_usize;
pixels[dst..dst + width_usize].copy_from_slice(&b[src..src + width_usize]);
row_offset += bytes_per_line;
}
}
let is_rgb = n_color_planes > 1;
let mut series_metadata = HashMap::new();
series_metadata.insert(
"Palette type".to_string(),
crate::common::metadata::MetadataValue::Int(palette_type as i64),
);
let meta = ImageMetadata {
size_x,
size_y,
size_z: 1,
size_c: n_color_planes as u32,
size_t: 1,
pixel_type: PixelType::Uint8,
bits_per_pixel: 8,
image_count: 1,
dimension_order: DimensionOrder::XYCZT,
is_rgb,
is_interleaved: false,
is_indexed,
is_little_endian: true,
resolution_count: 1,
series_metadata,
lookup_table,
modulo_z: None,
modulo_c: None,
modulo_t: None,
};
Ok((meta, pixels))
}
impl FormatReader for PcxReader {
fn is_this_type_by_name(&self, path: &Path) -> bool {
path.extension()
.and_then(|e| e.to_str())
.map(|e| e.eq_ignore_ascii_case("pcx"))
.unwrap_or(false)
}
fn is_this_type_by_bytes(&self, header: &[u8]) -> bool {
if header.len() < 2 {
return false;
}
header[0] == 0x0A && matches!(header[1], 0 | 2 | 3 | 5)
}
fn set_id(&mut self, path: &Path) -> Result<()> {
self.close()?;
let (meta, pixels) = load_pcx(path)?;
self.path = Some(path.to_path_buf());
self.meta = Some(meta);
self.pixels = Some(pixels);
Ok(())
}
fn close(&mut self) -> Result<()> {
self.path = None;
self.meta = None;
self.pixels = None;
Ok(())
}
fn series_count(&self) -> usize {
usize::from(self.meta.is_some())
}
fn set_series(&mut self, s: usize) -> Result<()> {
if self.meta.is_none() || s != 0 {
Err(BioFormatsError::SeriesOutOfRange(s))
} else {
Ok(())
}
}
fn series(&self) -> usize {
0
}
fn metadata(&self) -> &ImageMetadata {
self.meta
.as_ref()
.unwrap_or(crate::common::reader::uninitialized_metadata())
}
fn open_bytes(&mut self, plane_index: u32) -> Result<Vec<u8>> {
if plane_index != 0 {
return Err(BioFormatsError::PlaneOutOfRange(plane_index));
}
self.pixels.clone().ok_or(BioFormatsError::NotInitialized)
}
fn open_bytes_region(
&mut self,
plane_index: u32,
x: u32,
y: u32,
w: u32,
h: u32,
) -> Result<Vec<u8>> {
let full = self.open_bytes(plane_index)?;
let meta = self.meta.as_ref().unwrap();
let sw = meta.size_x as usize;
let sh = meta.size_y as usize;
let channels = meta.size_c as usize;
let (x, y, w, h) = (x as usize, y as usize, w as usize, h as usize);
if x.checked_add(w).is_none_or(|end| end > sw)
|| y.checked_add(h).is_none_or(|end| end > sh)
{
return Err(BioFormatsError::InvalidData(
"PCX: requested region out of bounds".into(),
));
}
let mut out = vec![0u8; channels * w * h];
for c in 0..channels {
for row in 0..h {
let src = c * sw * sh + (y + row) * sw + x;
let dst = c * w * h + row * w;
out[dst..dst + w].copy_from_slice(&full[src..src + w]);
}
}
Ok(out)
}
fn open_thumb_bytes(&mut self, plane_index: u32) -> Result<Vec<u8>> {
let meta = self.meta.as_ref().ok_or(BioFormatsError::NotInitialized)?;
let tw = meta.size_x.min(256);
let th = meta.size_y.min(256);
let tx = (meta.size_x - tw) / 2;
let ty = (meta.size_y - th) / 2;
self.open_bytes_region(plane_index, tx, ty, tw, th)
}
}