Implement Farbfeld format
This commit is contained in:
1 parent
cf1e5b165a
commit
25f2db9765
2 files changed
+231
No files matched your search
@@ -1,3 +1,7 @@
|
||||
pub mod farbfeld;
|
||||
|
||||
pub use farbfeld::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Pixel {
|
||||
pub r: f32,
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
use super::*;
|
||||
|
||||
pub struct Farbfeld;
|
||||
|
||||
fn read_header<I>(input: &mut I) -> Result<Extent, Error>
|
||||
where
|
||||
I: std::io::Read,
|
||||
{
|
||||
let mut bytes: [u8; 16] = [0; 16];
|
||||
if input.read(&mut bytes)? < 16 || &bytes[0..8] != b"farbfeld" {
|
||||
Err(Error::Invalid)
|
||||
} else {
|
||||
Ok(Extent {
|
||||
x: u32::from_be_bytes(bytes[8..12].try_into().unwrap()),
|
||||
y: u32::from_be_bytes(bytes[12..16].try_into().unwrap()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn read_pixels<I>(input: &mut I, extent: Extent) -> Result<Vec<Pixel>, Error>
|
||||
where
|
||||
I: std::io::Read,
|
||||
{
|
||||
let mut pixels = Vec::with_capacity(extent.size() as usize);
|
||||
for _ in 0..extent.size() {
|
||||
pixels.push(read_pixel(input)?);
|
||||
}
|
||||
Ok(pixels)
|
||||
}
|
||||
|
||||
fn read_pixel<I>(input: &mut I) -> Result<Pixel, Error>
|
||||
where
|
||||
I: std::io::Read,
|
||||
{
|
||||
let mut bytes: [u8; 8] = [0; 8];
|
||||
if input.read(&mut bytes)? < 8 {
|
||||
Err(Error::Invalid)
|
||||
} else {
|
||||
Ok(Pixel {
|
||||
r: to_channel(bytes[0..2].try_into().unwrap()),
|
||||
g: to_channel(bytes[2..4].try_into().unwrap()),
|
||||
b: to_channel(bytes[4..6].try_into().unwrap()),
|
||||
a: to_channel(bytes[6..8].try_into().unwrap()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn to_channel(bytes: [u8; 2]) -> f32 {
|
||||
u16::from_be_bytes(bytes) as f32 / u16::MAX as f32
|
||||
}
|
||||
|
||||
fn from_channel(channel: f32) -> [u8; 2] {
|
||||
((channel * u16::MAX as f32) as u16).to_be_bytes()
|
||||
}
|
||||
|
||||
impl Format for Farbfeld {
|
||||
fn read<I>(input: &mut I) -> Result<Image, Error>
|
||||
where
|
||||
I: std::io::Read,
|
||||
{
|
||||
let extent = read_header(input)?;
|
||||
Ok(Image {
|
||||
extent,
|
||||
pixels: read_pixels(input, extent)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn write<O>(output: &mut O, img: &Image) -> std::io::Result<()>
|
||||
where
|
||||
O: std::io::Write,
|
||||
{
|
||||
output.write_all(b"farbfeld")?;
|
||||
output.write_all(&img.extent.x.to_be_bytes())?;
|
||||
output.write_all(&img.extent.y.to_be_bytes())?;
|
||||
for pixel in &img.pixels {
|
||||
output.write_all(&from_channel(pixel.r))?;
|
||||
output.write_all(&from_channel(pixel.g))?;
|
||||
output.write_all(&from_channel(pixel.b))?;
|
||||
output.write_all(&from_channel(pixel.a))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const EPSILON: f32 = 1e-3;
|
||||
|
||||
macro_rules! assert_matches {
|
||||
( $act:expr, $pat:pat ) => {
|
||||
match $act {
|
||||
$pat => (),
|
||||
_ => panic!(
|
||||
"assertion `left matches right` failed\n\
|
||||
{:>6}: {:?}\n{:>6}: {}\n",
|
||||
"left",
|
||||
$act,
|
||||
"right",
|
||||
stringify!($pat)
|
||||
),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_approx {
|
||||
( $act:expr, $exp:expr ) => {
|
||||
if ($act - $exp).abs() >= EPSILON {
|
||||
panic!(
|
||||
"assertion `left ~= right` failed\n\
|
||||
{:>6}: {:?}\n{:>6}: {:?}\n",
|
||||
"left", $act, "right", $exp
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_invalid_magic() {
|
||||
let mut ff = std::io::Cursor::new(
|
||||
[&b"forbfeld"[..], &[0, 0, 0, 1], &[0, 0, 0, 1], &[0; 8]]
|
||||
.concat(),
|
||||
);
|
||||
let err = Farbfeld::read(&mut ff).unwrap_err();
|
||||
assert_matches!(err, Error::Invalid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_extent_1x2() {
|
||||
let mut ff = std::io::Cursor::new(
|
||||
[&b"farbfeld"[..], &[0, 0, 0, 1], &[0, 0, 0, 2], &[0; 16]]
|
||||
.concat(),
|
||||
);
|
||||
let img = Farbfeld::read(&mut ff).unwrap();
|
||||
assert_eq!(img.extent.x, 1);
|
||||
assert_eq!(img.extent.y, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_incomplete_header() {
|
||||
let mut ff = std::io::Cursor::new(b"farbfeld\x00\x00\x00\x01");
|
||||
let err = Farbfeld::read(&mut ff).unwrap_err();
|
||||
assert_matches!(err, Error::Invalid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_too_few_pixels() {
|
||||
let mut ff = std::io::Cursor::new(
|
||||
[&b"farbfeld"[..], &[0, 0, 0, 2], &[0, 0, 0, 2], &[0; 24]]
|
||||
.concat(),
|
||||
);
|
||||
let err = Farbfeld::read(&mut ff).unwrap_err();
|
||||
assert_matches!(err, Error::Invalid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_single_pixel() {
|
||||
let mut ff = std::io::Cursor::new(
|
||||
[
|
||||
&b"farbfeld"[..],
|
||||
&[0, 0, 0, 1],
|
||||
&[0, 0, 0, 1],
|
||||
&[0x00, 0x00, 0x40, 0x00, 0x80, 0x00, 0xff, 0xff],
|
||||
]
|
||||
.concat(),
|
||||
);
|
||||
let img = Farbfeld::read(&mut ff).unwrap();
|
||||
assert_eq!(img.pixels.len(), 1);
|
||||
assert_approx!(img.pixels[0].r, 0.0);
|
||||
assert_approx!(img.pixels[0].g, 0.25);
|
||||
assert_approx!(img.pixels[0].b, 0.5);
|
||||
assert_approx!(img.pixels[0].a, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_magic() {
|
||||
let img = Image {
|
||||
extent: Extent { x: 1, y: 1 },
|
||||
pixels: vec![Pixel {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}],
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
Farbfeld::write(&mut std::io::Cursor::new(&mut out), &img).unwrap();
|
||||
assert_eq!(&out[0..8], b"farbfeld");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_extent() {
|
||||
let img = Image {
|
||||
extent: Extent { x: 1, y: 2 },
|
||||
pixels: vec![Pixel {
|
||||
r: 0.0,
|
||||
g: 0.0,
|
||||
b: 0.0,
|
||||
a: 1.0,
|
||||
}],
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
Farbfeld::write(&mut std::io::Cursor::new(&mut out), &img).unwrap();
|
||||
assert_eq!(out[8..12], [0, 0, 0, 1]);
|
||||
assert_eq!(out[12..16], [0, 0, 0, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_pixel() {
|
||||
let img = Image {
|
||||
extent: Extent { x: 1, y: 1 },
|
||||
pixels: vec![Pixel {
|
||||
r: 0.0,
|
||||
g: 0.25,
|
||||
b: 0.5,
|
||||
a: 1.0,
|
||||
}],
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
Farbfeld::write(&mut std::io::Cursor::new(&mut out), &img).unwrap();
|
||||
assert_eq!(out[16..18], [0, 0]);
|
||||
assert_eq!(out[18..20], [0x3f, 0xff]);
|
||||
assert_eq!(out[20..22], [0x7f, 0xff]);
|
||||
assert_eq!(out[22..24], [0xff, 0xff]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user