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)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Pixel {
|
pub struct Pixel {
|
||||||
pub r: f32,
|
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