use crate::common::Endian;
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::prelude::*;
use std::io::Cursor;
use std::str;
#[derive(Debug, PartialEq)]
pub struct PFM {
pub width: usize,
pub height: usize,
pub color: bool,
pub scale_factor: f32,
pub endian: Endian,
pub data: Vec<f32>,
}
impl PFM {
pub fn read_from(reader: &mut impl Read) -> Result<PFM, &'static str> {
let mut buffer = Vec::new();
match reader.read_to_end(&mut buffer) {
Ok(bytes) => {
if bytes == 0 {
return Err("Empty file");
}
}
Err(_) => return Err("Unable to read from file"),
};
decode(&buffer)
}
pub fn write_into(&self, writer: &mut impl Write) -> Result<(), &'static str> {
let buffer = encode(&self)?;
match writer.write_all(&buffer) {
Ok(_) => match writer.flush() {
Err(_) => Err("Unable to flush data"),
_ => Ok(()),
},
Err(_) => Err("Unable to write into the writer"),
}
}
}
#[derive(Debug)]
pub struct PFMBuilder(PFM);
impl PFMBuilder {
pub fn new() -> PFMBuilder {
let pfm = PFM {
width: 0,
height: 0,
color: true,
scale_factor: 1.0,
endian: Endian::Little,
data: Vec::new(),
};
PFMBuilder(pfm)
}
pub fn size(mut self, width: usize, height: usize) -> PFMBuilder {
assert!(width > 0 && height > 0);
self.0.width = width;
self.0.height = height;
self
}
pub fn color(mut self, color: bool) -> PFMBuilder {
self.0.color = color;
self
}
pub fn scale(mut self, scale: f32) -> PFMBuilder {
assert!(scale != 0.0);
if scale > 0.0 {
self.0.endian = Endian::Big;
self.0.scale_factor = scale;
} else if scale < 0.0 {
self.0.endian = Endian::Little;
self.0.scale_factor = -scale;
}
self
}
pub fn data(mut self, data: Vec<f32>) -> PFMBuilder {
self.0.data = data;
self
}
pub fn build(self) -> Result<PFM, &'static str> {
let num_channels = if self.0.color { 3 } else { 1 };
let num_pixels = self.0.width * self.0.height;
if self.0.data.len() != num_channels * num_pixels {
return Err("The length of data is not equal to width * height * channels");
}
Ok(self.0)
}
}
fn encode(pfm: &PFM) -> Result<Vec<u8>, &'static str> {
if pfm.width == 0 || pfm.height == 0 {
return Err("Invalid width or height");
}
if pfm.scale_factor == 0.0 {
return Err("Invalid scaling factor");
}
let scale = match pfm.endian {
Endian::Little => -1.0 * pfm.scale_factor,
Endian::Big => pfm.scale_factor,
};
let header = if pfm.color { "PF" } else { "Pf" };
let num_channels = if pfm.color { 3 } else { 1 };
if pfm.width * pfm.height * num_channels != pfm.data.len() {
return Err("The length of image data is not equal to width * height * channels specified in the header");
}
let mut buffer = Vec::new();
buffer.extend_from_slice(header.as_bytes());
buffer.push(b'\n');
buffer.extend_from_slice(format!("{} {}\n", pfm.width, pfm.height).as_bytes());
buffer.extend_from_slice(format!("{}\n", scale).as_bytes());
buffer.reserve(pfm.width * pfm.height * num_channels * 4);
for row in (0..pfm.height).rev() {
for col in 0..(pfm.width * num_channels) {
let cursor = row * pfm.width * num_channels + col;
match pfm.endian {
Endian::Little => buffer.write_f32::<LittleEndian>(pfm.data[cursor]).unwrap(),
Endian::Big => buffer.write_f32::<BigEndian>(pfm.data[cursor]).unwrap(),
}
}
}
Ok(buffer)
}
fn decode(buffer: &[u8]) -> Result<PFM, &'static str> {
let (mut builder, buffer) = parse_header(buffer)?;
let endian = builder.0.endian;
let num_channels = if builder.0.color { 3 } else { 1 };
let height = builder.0.height;
let width = builder.0.width;
let num_pixels = width * height;
if num_pixels * num_channels != buffer.len() / 4 {
return Err("Broken file. The length of image data is not equal to width * height * channels specified in the header");
}
let mut data = vec![0.0f32; num_pixels * num_channels];
let mut buffer = Cursor::new(buffer);
match endian {
Endian::Little => match buffer.read_f32_into::<LittleEndian>(&mut data) {
Err(_) => return Err("File data is broken"),
_ => (),
},
Endian::Big => match buffer.read_f32_into::<BigEndian>(&mut data) {
Err(_) => return Err("File data is broken"),
_ => (),
},
};
for row in 0..height {
if row >= height - 1 - row {
break;
}
for col in 0..(width * num_channels) {
let a = row * width * num_channels + col;
let b = (height - 1 - row) * width * num_channels + col;
data.swap(a, b);
}
}
builder = builder.data(data);
builder.build()
}
fn parse_header(buffer: &[u8]) -> Result<(PFMBuilder, &[u8]), &'static str> {
let mut builder = PFMBuilder::new();
let (header_pf, buffer) = read_until_space(buffer)?;
if header_pf[0] != ('P' as u8) {
return Err("Tht first character must be 'P'");
}
if header_pf[1] == ('F' as u8) {
builder = builder.color(true);
} else if header_pf[1] == ('f' as u8) {
builder = builder.color(false);
} else {
return Err("Tht second character must be 'F' or 'f'");
}
let (header_width, buffer) = read_until_space(buffer)?;
let width: usize = parse_token(header_width, "Invalid width")?;
if width == 0 {
return Err("Invalid width");
}
let (header_height, buffer) = read_until_space(buffer)?;
let height: usize = parse_token(header_height, "Invalid height")?;
if height == 0 {
return Err("Invalid height");
}
builder = builder.size(width, height);
let (header_scale, buffer) = read_until_space(buffer)?;
let scale: f32 = parse_token(header_scale, "Invalid scale")?;;
if scale == 0.0 {
return Err("Invalid scale");
}
builder = builder.scale(scale);
Ok((builder, &buffer[1..]))
}
fn parse_token<T>(buffer: &[u8], err_msg: &'static str) -> Result<T, &'static str>
where
T: str::FromStr,
{
match str::from_utf8(buffer) {
Ok(s) => match s.parse() {
Ok(w) => Ok(w),
Err(_) => return Err(err_msg),
},
Err(_) => return Err(err_msg),
}
}
fn read_until_space(buffer: &[u8]) -> Result<(&[u8], &[u8]), &'static str> {
let mut start = 0;
while start < buffer.len() && (buffer[start] as char).is_ascii_whitespace() {
start += 1;
}
if start >= buffer.len() {
return Err("Reached EOF before finishing parsing");
}
let mut end = start;
while end < buffer.len() && !(buffer[end] as char).is_ascii_whitespace() {
end += 1;
}
if end > buffer.len() {
return Err("Reached EOF before finishing parsing");
}
Ok((&buffer[start..end], &buffer[end..]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_read_from() {
let mut buffer = Cursor::new(vec![
0x50, 0x46, 0x0A, 0x31, 0x20, 0x33, 0x0A, 0x2D, 0x31, 0x2E, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x0A, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80,
0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x3f, ]);
let pfm = PFM::read_from(&mut buffer).unwrap();
assert_eq!(pfm.color, true);
assert_eq!(pfm.endian, Endian::Little);
assert_eq!(pfm.scale_factor, 1.0);
assert_eq!(pfm.height, 3);
assert_eq!(pfm.width, 1);
assert_eq!(pfm.data, vec![0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0])
}
#[test]
fn test_write_into() {
let pfm = PFMBuilder::new()
.color(true)
.scale(-1.0)
.size(1, 3)
.data(vec![0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0])
.build()
.unwrap();
let mut buffer = Vec::new();
let buffer_gt = vec![
0x50, 0x46, 0x0A, 0x31, 0x20, 0x33, 0x0A, 0x2D, 0x31, 0x0A, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x80,
0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00,
0x3f, ];
pfm.write_into(&mut buffer).unwrap();
assert_eq!(buffer, buffer_gt);
}
#[test]
fn test_read_until_space() {
let buffer = " token1 token2 token3".as_bytes();
let (s, buffer) = read_until_space(buffer).unwrap();
assert_eq!(s, "token1".as_bytes());
assert_eq!(buffer, " token2 token3".as_bytes());
let (s, buffer) = read_until_space(buffer).unwrap();
assert_eq!(s, "token2".as_bytes());
assert_eq!(buffer, " token3".as_bytes());
let (s, buffer) = read_until_space(buffer).unwrap();
assert_eq!(s, "token3".as_bytes());
assert_eq!(buffer, "".as_bytes());
}
}