Implement Farbfeld format

This commit is contained in:
cdo committed 2026-10-25 22:24:52 +00:00
1 parent cf1e5b165a
commit 25f2db9765
2 files changed
+231

No files matched your search

+4
View File
@@ -1,3 +1,7 @@
pub mod farbfeld;
pub use farbfeld::*;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Pixel {
pub r: f32,
+227
View File
@@ -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]);
}
}