Coverage Report

Created: 2026-01-10 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/image/src/codecs/farbfeld.rs
Line
Count
Source
1
//! Decoding of farbfeld images
2
//!
3
//! farbfeld is a lossless image format which is easy to parse, pipe and compress.
4
//!
5
//! It has the following format:
6
//!
7
//! | Bytes  | Description                                             |
8
//! |--------|---------------------------------------------------------|
9
//! | 8      | "farbfeld" magic value                                  |
10
//! | 4      | 32-Bit BE unsigned integer (width)                      |
11
//! | 4      | 32-Bit BE unsigned integer (height)                     |
12
//! | (2222) | 4⋅16-Bit BE unsigned integers (RGBA) / pixel, row-major |
13
//!
14
//! The RGB-data should be sRGB for best interoperability and not alpha-premultiplied.
15
//!
16
//! # Related Links
17
//! * <https://tools.suckless.org/farbfeld/> - the farbfeld specification
18
19
use std::io::{self, Read, Seek, SeekFrom, Write};
20
21
use crate::color::ExtendedColorType;
22
use crate::error::{
23
    DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind,
24
};
25
use crate::{ColorType, ImageDecoder, ImageEncoder, ImageFormat};
26
27
/// farbfeld Reader
28
pub struct FarbfeldReader<R: Read> {
29
    width: u32,
30
    height: u32,
31
    inner: R,
32
    /// Relative to the start of the pixel data
33
    current_offset: u64,
34
    cached_byte: Option<u8>,
35
}
36
37
impl<R: Read> FarbfeldReader<R> {
38
2
    fn new(mut buffered_read: R) -> ImageResult<FarbfeldReader<R>> {
39
4
        fn read_dimm<R: Read>(from: &mut R) -> ImageResult<u32> {
40
4
            let mut buf = [0u8; 4];
41
4
            from.read_exact(&mut buf).map_err(|err| {
42
0
                ImageError::Decoding(DecodingError::new(ImageFormat::Farbfeld.into(), err))
43
0
            })?;
44
4
            Ok(u32::from_be_bytes(buf))
45
4
        }
46
47
2
        let mut magic = [0u8; 8];
48
2
        buffered_read.read_exact(&mut magic).map_err(|err| {
49
0
            ImageError::Decoding(DecodingError::new(ImageFormat::Farbfeld.into(), err))
50
0
        })?;
51
2
        if &magic != b"farbfeld" {
52
0
            return Err(ImageError::Decoding(DecodingError::new(
53
0
                ImageFormat::Farbfeld.into(),
54
0
                format!("Invalid magic: {magic:02x?}"),
55
0
            )));
56
2
        }
57
58
2
        let reader = FarbfeldReader {
59
2
            width: read_dimm(&mut buffered_read)?,
60
2
            height: read_dimm(&mut buffered_read)?,
61
2
            inner: buffered_read,
62
            current_offset: 0,
63
2
            cached_byte: None,
64
        };
65
66
2
        if crate::utils::check_dimension_overflow(
67
2
            reader.width,
68
2
            reader.height,
69
            // ExtendedColorType is always rgba16
70
            8,
71
        ) {
72
0
            return Err(ImageError::Unsupported(
73
0
                UnsupportedError::from_format_and_kind(
74
0
                    ImageFormat::Farbfeld.into(),
75
0
                    UnsupportedErrorKind::GenericFeature(format!(
76
0
                        "Image dimensions ({}x{}) are too large",
77
0
                        reader.width, reader.height
78
0
                    )),
79
0
                ),
80
0
            ));
81
2
        }
82
83
2
        Ok(reader)
84
2
    }
85
}
86
87
impl<R: Read> Read for FarbfeldReader<R> {
88
2
    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
89
2
        let mut bytes_written = 0;
90
2
        if let Some(byte) = self.cached_byte.take() {
91
0
            buf[0] = byte;
92
0
            buf = &mut buf[1..];
93
0
            bytes_written = 1;
94
0
            self.current_offset += 1;
95
2
        }
96
97
2
        if buf.len() == 1 {
98
0
            buf[0] = cache_byte(&mut self.inner, &mut self.cached_byte)?;
99
0
            bytes_written += 1;
100
0
            self.current_offset += 1;
101
        } else {
102
205
            for channel_out in buf.chunks_exact_mut(2) {
103
205
                consume_channel(&mut self.inner, channel_out)?;
104
203
                bytes_written += 2;
105
203
                self.current_offset += 2;
106
            }
107
        }
108
109
0
        Ok(bytes_written)
110
2
    }
111
}
112
113
impl<R: Read + Seek> Seek for FarbfeldReader<R> {
114
0
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
115
0
        fn parse_offset(original_offset: u64, end_offset: u64, pos: SeekFrom) -> Option<i64> {
116
0
            match pos {
117
0
                SeekFrom::Start(off) => i64::try_from(off)
118
0
                    .ok()?
119
0
                    .checked_sub(i64::try_from(original_offset).ok()?),
120
0
                SeekFrom::End(off) => {
121
0
                    if off < i64::try_from(end_offset).unwrap_or(i64::MAX) {
122
0
                        None
123
                    } else {
124
0
                        Some(i64::try_from(end_offset.checked_sub(original_offset)?).ok()? + off)
125
                    }
126
                }
127
0
                SeekFrom::Current(off) => {
128
0
                    if off < i64::try_from(original_offset).unwrap_or(i64::MAX) {
129
0
                        None
130
                    } else {
131
0
                        Some(off)
132
                    }
133
                }
134
            }
135
0
        }
136
137
0
        let original_offset = self.current_offset;
138
0
        let end_offset = u64::from(self.width) * u64::from(self.height) * 2;
139
0
        let offset_from_current =
140
0
            parse_offset(original_offset, end_offset, pos).ok_or_else(|| {
141
0
                io::Error::new(
142
0
                    io::ErrorKind::InvalidInput,
143
                    "invalid seek to a negative or overflowing position",
144
                )
145
0
            })?;
146
147
        // TODO: convert to seek_relative() once that gets stabilised
148
0
        self.inner.seek(SeekFrom::Current(offset_from_current))?;
149
0
        self.current_offset = if offset_from_current < 0 {
150
0
            original_offset.checked_sub(offset_from_current.wrapping_neg() as u64)
151
        } else {
152
0
            original_offset.checked_add(offset_from_current as u64)
153
        }
154
0
        .expect("This should've been checked above");
155
156
0
        if self.current_offset < end_offset && self.current_offset % 2 == 1 {
157
0
            let curr = self.inner.seek(SeekFrom::Current(-1))?;
158
0
            cache_byte(&mut self.inner, &mut self.cached_byte)?;
159
0
            self.inner.seek(SeekFrom::Start(curr))?;
160
0
        } else {
161
0
            self.cached_byte = None;
162
0
        }
163
164
0
        Ok(original_offset)
165
0
    }
166
}
167
168
205
fn consume_channel<R: Read>(from: &mut R, mut to: &mut [u8]) -> io::Result<()> {
169
205
    let mut ibuf = [0u8; 2];
170
205
    from.read_exact(&mut ibuf)?;
171
203
    to.write_all(&u16::from_be_bytes(ibuf).to_ne_bytes())?;
172
173
203
    Ok(())
174
205
}
175
176
0
fn cache_byte<R: Read>(from: &mut R, cached_byte: &mut Option<u8>) -> io::Result<u8> {
177
0
    let mut obuf = [0u8; 2];
178
0
    consume_channel(from, &mut obuf)?;
179
0
    *cached_byte = Some(obuf[1]);
180
0
    Ok(obuf[0])
181
0
}
182
183
/// farbfeld decoder
184
pub struct FarbfeldDecoder<R: Read> {
185
    reader: FarbfeldReader<R>,
186
}
187
188
impl<R: Read> FarbfeldDecoder<R> {
189
    /// Creates a new decoder that decodes from the stream ```r```
190
2
    pub fn new(buffered_read: R) -> ImageResult<FarbfeldDecoder<R>> {
191
        Ok(FarbfeldDecoder {
192
2
            reader: FarbfeldReader::new(buffered_read)?,
193
        })
194
2
    }
195
}
196
197
impl<R: Read> ImageDecoder for FarbfeldDecoder<R> {
198
10
    fn dimensions(&self) -> (u32, u32) {
199
10
        (self.reader.width, self.reader.height)
200
10
    }
201
202
8
    fn color_type(&self) -> ColorType {
203
8
        ColorType::Rgba16
204
8
    }
205
206
2
    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
207
2
        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
208
2
        self.reader.read_exact(buf)?;
209
0
        Ok(())
210
2
    }
211
212
2
    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
213
2
        (*self).read_image(buf)
214
2
    }
215
}
216
217
/// farbfeld encoder
218
pub struct FarbfeldEncoder<W: Write> {
219
    w: W,
220
}
221
222
impl<W: Write> FarbfeldEncoder<W> {
223
    /// Create a new encoder that writes its output to ```w```. The writer should be buffered.
224
0
    pub fn new(buffered_writer: W) -> FarbfeldEncoder<W> {
225
0
        FarbfeldEncoder { w: buffered_writer }
226
0
    }
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<_>>::new
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::new
227
228
    /// Encodes the image `data` (native endian) that has dimensions `width` and `height`.
229
    ///
230
    /// # Panics
231
    ///
232
    /// Panics if `width * height * 8 != data.len()`.
233
    #[track_caller]
234
0
    pub fn encode(self, data: &[u8], width: u32, height: u32) -> ImageResult<()> {
235
0
        let expected_buffer_len = (u64::from(width) * u64::from(height)).saturating_mul(8);
236
0
        assert_eq!(
237
            expected_buffer_len,
238
0
            data.len() as u64,
239
0
            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
240
0
            data.len(),
241
        );
242
0
        self.encode_impl(data, width, height)?;
243
0
        Ok(())
244
0
    }
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<_>>::encode
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode
245
246
0
    fn encode_impl(mut self, data: &[u8], width: u32, height: u32) -> io::Result<()> {
247
0
        self.w.write_all(b"farbfeld")?;
248
249
0
        self.w.write_all(&width.to_be_bytes())?;
250
0
        self.w.write_all(&height.to_be_bytes())?;
251
252
0
        for channel in data.chunks_exact(2) {
253
0
            self.w
254
0
                .write_all(&u16::from_ne_bytes(channel.try_into().unwrap()).to_be_bytes())?;
255
        }
256
257
0
        Ok(())
258
0
    }
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<_>>::encode_impl
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::encode_impl
259
}
260
261
impl<W: Write> ImageEncoder for FarbfeldEncoder<W> {
262
    #[track_caller]
263
0
    fn write_image(
264
0
        self,
265
0
        buf: &[u8],
266
0
        width: u32,
267
0
        height: u32,
268
0
        color_type: ExtendedColorType,
269
0
    ) -> ImageResult<()> {
270
0
        if color_type != ExtendedColorType::Rgba16 {
271
0
            return Err(ImageError::Unsupported(
272
0
                UnsupportedError::from_format_and_kind(
273
0
                    ImageFormat::Farbfeld.into(),
274
0
                    UnsupportedErrorKind::Color(color_type),
275
0
                ),
276
0
            ));
277
0
        }
278
279
0
        self.encode(buf, width, height)
280
0
    }
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<_> as image::io::encoder::ImageEncoder>::write_image
Unexecuted instantiation: <image::codecs::farbfeld::FarbfeldEncoder<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>> as image::io::encoder::ImageEncoder>::write_image
281
}
282
283
#[cfg(test)]
284
mod tests {
285
    use crate::codecs::farbfeld::FarbfeldDecoder;
286
    use std::io::Cursor;
287
288
    #[test]
289
    fn dimension_overflow() {
290
        let header = b"farbfeld\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
291
292
        assert!(FarbfeldDecoder::new(Cursor::new(header)).is_err());
293
    }
294
}