Skip to main content

ai_image/codecs/
farbfeld.rs

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
19use alloc::{boxed::Box, format};
20use no_std_io::io::{self, Read, Seek, SeekFrom, Write};
21
22use crate::color::ExtendedColorType;
23use crate::error::{
24    DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind,
25};
26use crate::io::free_functions::load_rect;
27use crate::{ColorType, ImageDecoder, ImageDecoderRect, ImageEncoder, ImageFormat};
28
29/// farbfeld Reader
30pub struct FarbfeldReader<R: Read> {
31    width: u32,
32    height: u32,
33    inner: R,
34    /// Relative to the start of the pixel data
35    current_offset: u64,
36    cached_byte: Option<u8>,
37}
38
39impl<R: Read> FarbfeldReader<R> {
40    fn new(mut buffered_read: R) -> ImageResult<FarbfeldReader<R>> {
41        fn read_dimm<R: Read>(from: &mut R) -> ImageResult<u32> {
42            let mut buf = [0u8; 4];
43            from.read_exact(&mut buf).map_err(ImageError::IoError)?;
44            Ok(u32::from_be_bytes(buf))
45        }
46
47        let mut magic = [0u8; 8];
48        buffered_read
49            .read_exact(&mut magic)
50            .map_err(ImageError::IoError)?;
51        if &magic != b"farbfeld" {
52            return Err(ImageError::Decoding(DecodingError::new(
53                ImageFormat::Farbfeld.into(),
54                format!("Invalid magic: {magic:02x?}"),
55            )));
56        }
57
58        let reader = FarbfeldReader {
59            width: read_dimm(&mut buffered_read)?,
60            height: read_dimm(&mut buffered_read)?,
61            inner: buffered_read,
62            current_offset: 0,
63            cached_byte: None,
64        };
65
66        if crate::utils::check_dimension_overflow(
67            reader.width,
68            reader.height,
69            // ExtendedColorType is always rgba16
70            8,
71        ) {
72            return Err(ImageError::Unsupported(
73                UnsupportedError::from_format_and_kind(
74                    ImageFormat::Farbfeld.into(),
75                    UnsupportedErrorKind::GenericFeature(format!(
76                        "Image dimensions ({}x{}) are too large",
77                        reader.width, reader.height
78                    )),
79                ),
80            ));
81        }
82
83        Ok(reader)
84    }
85}
86
87impl<R: Read> Read for FarbfeldReader<R> {
88    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
89        let mut bytes_written = 0;
90        if let Some(byte) = self.cached_byte.take() {
91            buf[0] = byte;
92            buf = &mut buf[1..];
93            bytes_written = 1;
94            self.current_offset += 1;
95        }
96
97        if buf.len() == 1 {
98            buf[0] = cache_byte(&mut self.inner, &mut self.cached_byte)?;
99            bytes_written += 1;
100            self.current_offset += 1;
101        } else {
102            for channel_out in buf.as_chunks_mut::<2>().0 {
103                consume_channel(&mut self.inner, channel_out)?;
104                bytes_written += 2;
105                self.current_offset += 2;
106            }
107        }
108
109        Ok(bytes_written)
110    }
111}
112
113impl<R: Read + Seek> Seek for FarbfeldReader<R> {
114    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
115        fn parse_offset(original_offset: u64, end_offset: u64, pos: SeekFrom) -> Option<i64> {
116            match pos {
117                SeekFrom::Start(off) => i64::try_from(off)
118                    .ok()?
119                    .checked_sub(i64::try_from(original_offset).ok()?),
120                SeekFrom::End(off) => {
121                    if off < i64::try_from(end_offset).unwrap_or(i64::MAX) {
122                        None
123                    } else {
124                        Some(i64::try_from(end_offset.checked_sub(original_offset)?).ok()? + off)
125                    }
126                }
127                SeekFrom::Current(off) => {
128                    if off < i64::try_from(original_offset).unwrap_or(i64::MAX) {
129                        None
130                    } else {
131                        Some(off)
132                    }
133                }
134            }
135        }
136
137        let original_offset = self.current_offset;
138        let end_offset = u64::from(self.width) * u64::from(self.height) * 2;
139        let offset_from_current =
140            parse_offset(original_offset, end_offset, pos).ok_or_else(|| {
141                io::Error::new(
142                    io::ErrorKind::InvalidInput,
143                    "invalid seek to a negative or overflowing position",
144                )
145            })?;
146
147        // TODO: convert to seek_relative() once that gets stabilised
148        self.inner.seek(SeekFrom::Current(offset_from_current))?;
149        self.current_offset = if offset_from_current < 0 {
150            original_offset.checked_sub(offset_from_current.wrapping_neg() as u64)
151        } else {
152            original_offset.checked_add(offset_from_current as u64)
153        }
154        .expect("This should've been checked above");
155
156        if self.current_offset < end_offset && self.current_offset % 2 == 1 {
157            let curr = self.inner.seek(SeekFrom::Current(-1))?;
158            cache_byte(&mut self.inner, &mut self.cached_byte)?;
159            self.inner.seek(SeekFrom::Start(curr))?;
160        } else {
161            self.cached_byte = None;
162        }
163
164        Ok(original_offset)
165    }
166}
167
168fn consume_channel<R: Read>(from: &mut R, to: &mut [u8; 2]) -> io::Result<()> {
169    let mut ibuf = [0u8; 2];
170    from.read_exact(&mut ibuf)?;
171    to.copy_from_slice(&u16::from_be_bytes(ibuf).to_ne_bytes());
172
173    Ok(())
174}
175
176fn cache_byte<R: Read>(from: &mut R, cached_byte: &mut Option<u8>) -> io::Result<u8> {
177    let mut obuf = [0u8; 2];
178    consume_channel(from, &mut obuf)?;
179    *cached_byte = Some(obuf[1]);
180    Ok(obuf[0])
181}
182
183/// farbfeld decoder
184pub struct FarbfeldDecoder<R: Read> {
185    reader: FarbfeldReader<R>,
186}
187
188impl<R: Read> FarbfeldDecoder<R> {
189    /// Creates a new decoder that decodes from the stream ```r```
190    pub fn new(buffered_read: R) -> ImageResult<FarbfeldDecoder<R>> {
191        Ok(FarbfeldDecoder {
192            reader: FarbfeldReader::new(buffered_read)?,
193        })
194    }
195}
196
197impl<R: Read> ImageDecoder for FarbfeldDecoder<R> {
198    fn dimensions(&self) -> (u32, u32) {
199        (self.reader.width, self.reader.height)
200    }
201
202    fn color_type(&self) -> ColorType {
203        ColorType::Rgba16
204    }
205
206    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
207        assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes()));
208        self.reader.read_exact(buf)?;
209        Ok(())
210    }
211
212    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
213        (*self).read_image(buf)
214    }
215}
216
217impl<R: Read + Seek> ImageDecoderRect for FarbfeldDecoder<R> {
218    fn read_rect(
219        &mut self,
220        x: u32,
221        y: u32,
222        width: u32,
223        height: u32,
224        buf: &mut [u8],
225        row_pitch: usize,
226    ) -> ImageResult<()> {
227        // A "scanline" (defined as "shortest non-caching read" in the doc) is just one channel in this case
228
229        // no_std_io::Seek doesn't provide stream_position()
230        #[allow(clippy::seek_from_current)]
231        let start = self.reader.seek(SeekFrom::Current(0))?;
232        load_rect(
233            x,
234            y,
235            width,
236            height,
237            buf,
238            row_pitch,
239            self,
240            2,
241            |s, scanline| s.reader.seek(SeekFrom::Start(scanline * 2)).map(|_| ()),
242            |s, buf| s.reader.read_exact(buf),
243        )?;
244        self.reader.seek(SeekFrom::Start(start))?;
245        Ok(())
246    }
247}
248
249/// farbfeld encoder
250pub struct FarbfeldEncoder<W: Write> {
251    w: W,
252}
253
254impl<W: Write> FarbfeldEncoder<W> {
255    /// Create a new encoder that writes its output to ```w```. The writer should be buffered.
256    pub fn new(buffered_writer: W) -> FarbfeldEncoder<W> {
257        FarbfeldEncoder { w: buffered_writer }
258    }
259
260    /// Encodes the image `data` (native endian) that has dimensions `width` and `height`.
261    ///
262    /// # Panics
263    ///
264    /// Panics if `width * height * 8 != data.len()`.
265    #[track_caller]
266    pub fn encode(self, data: &[u8], width: u32, height: u32) -> ImageResult<()> {
267        let expected_buffer_len = (u64::from(width) * u64::from(height)).saturating_mul(8);
268        assert_eq!(
269            expected_buffer_len,
270            data.len() as u64,
271            "Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
272            data.len(),
273        );
274        self.encode_impl(data, width, height)?;
275        Ok(())
276    }
277
278    fn encode_impl(mut self, data: &[u8], width: u32, height: u32) -> io::Result<()> {
279        self.w.write_all(b"farbfeld")?;
280
281        self.w.write_all(&width.to_be_bytes())?;
282        self.w.write_all(&height.to_be_bytes())?;
283
284        for &channel in data.as_chunks::<2>().0 {
285            self.w
286                .write_all(&u16::from_ne_bytes(channel).to_be_bytes())?;
287        }
288
289        Ok(())
290    }
291}
292
293impl<W: Write> ImageEncoder for FarbfeldEncoder<W> {
294    #[track_caller]
295    fn write_image(
296        self,
297        buf: &[u8],
298        width: u32,
299        height: u32,
300        color_type: ExtendedColorType,
301    ) -> ImageResult<()> {
302        if color_type != ExtendedColorType::Rgba16 {
303            return Err(ImageError::Unsupported(
304                UnsupportedError::from_format_and_kind(
305                    ImageFormat::Farbfeld.into(),
306                    UnsupportedErrorKind::Color(color_type),
307                ),
308            ));
309        }
310
311        self.encode(buf, width, height)
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use crate::codecs::farbfeld::FarbfeldDecoder;
318    use crate::ImageDecoderRect;
319    use byteorder_lite::{ByteOrder, NativeEndian};
320    use no_std_io::io::{Cursor, Seek, SeekFrom};
321
322    static RECTANGLE_IN: &[u8] =     b"farbfeld\
323                                       \x00\x00\x00\x02\x00\x00\x00\x03\
324                                       \xFF\x01\xFE\x02\xFD\x03\xFC\x04\xFB\x05\xFA\x06\xF9\x07\xF8\x08\
325                                       \xF7\x09\xF6\x0A\xF5\x0B\xF4\x0C\xF3\x0D\xF2\x0E\xF1\x0F\xF0\x10\
326                                       \xEF\x11\xEE\x12\xED\x13\xEC\x14\xEB\x15\xEA\x16\xE9\x17\xE8\x18";
327
328    #[test]
329    fn read_rect_1x2() {
330        static RECTANGLE_OUT: &[u16] = &[
331            0xF30D, 0xF20E, 0xF10F, 0xF010, 0xEB15, 0xEA16, 0xE917, 0xE818,
332        ];
333
334        read_rect(1, 1, 1, 2, RECTANGLE_OUT);
335    }
336
337    #[test]
338    fn read_rect_2x2() {
339        static RECTANGLE_OUT: &[u16] = &[
340            0xFF01, 0xFE02, 0xFD03, 0xFC04, 0xFB05, 0xFA06, 0xF907, 0xF808, 0xF709, 0xF60A, 0xF50B,
341            0xF40C, 0xF30D, 0xF20E, 0xF10F, 0xF010,
342        ];
343
344        read_rect(0, 0, 2, 2, RECTANGLE_OUT);
345    }
346
347    #[test]
348    fn read_rect_2x1() {
349        static RECTANGLE_OUT: &[u16] = &[
350            0xEF11, 0xEE12, 0xED13, 0xEC14, 0xEB15, 0xEA16, 0xE917, 0xE818,
351        ];
352
353        read_rect(0, 2, 2, 1, RECTANGLE_OUT);
354    }
355
356    #[test]
357    fn read_rect_2x3() {
358        static RECTANGLE_OUT: &[u16] = &[
359            0xFF01, 0xFE02, 0xFD03, 0xFC04, 0xFB05, 0xFA06, 0xF907, 0xF808, 0xF709, 0xF60A, 0xF50B,
360            0xF40C, 0xF30D, 0xF20E, 0xF10F, 0xF010, 0xEF11, 0xEE12, 0xED13, 0xEC14, 0xEB15, 0xEA16,
361            0xE917, 0xE818,
362        ];
363
364        read_rect(0, 0, 2, 3, RECTANGLE_OUT);
365    }
366
367    #[test]
368    fn read_rect_in_stream() {
369        static RECTANGLE_OUT: &[u16] = &[0xEF11, 0xEE12, 0xED13, 0xEC14];
370
371        let mut input = vec![];
372        input.extend_from_slice(b"This is a 31-byte-long prologue");
373        input.extend_from_slice(RECTANGLE_IN);
374        let mut input_cur = Cursor::new(input);
375        input_cur.seek(SeekFrom::Start(31)).unwrap();
376
377        let mut out_buf = [0u8; 64];
378        FarbfeldDecoder::new(input_cur)
379            .unwrap()
380            .read_rect(0, 2, 1, 1, &mut out_buf, 8)
381            .unwrap();
382        let exp = degenerate_pixels(RECTANGLE_OUT);
383        assert_eq!(&out_buf[..exp.len()], &exp[..]);
384    }
385
386    #[test]
387    fn dimension_overflow() {
388        let header = b"farbfeld\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
389
390        assert!(FarbfeldDecoder::new(Cursor::new(header)).is_err());
391    }
392
393    fn read_rect(x: u32, y: u32, width: u32, height: u32, exp_wide: &[u16]) {
394        let mut out_buf = [0u8; 64];
395        FarbfeldDecoder::new(Cursor::new(RECTANGLE_IN))
396            .unwrap()
397            .read_rect(x, y, width, height, &mut out_buf, width as usize * 8)
398            .unwrap();
399        let exp = degenerate_pixels(exp_wide);
400        assert_eq!(&out_buf[..exp.len()], &exp[..]);
401    }
402
403    fn degenerate_pixels(exp_wide: &[u16]) -> Vec<u8> {
404        let mut exp = vec![0u8; exp_wide.len() * 2];
405        NativeEndian::write_u16_into(exp_wide, &mut exp);
406        exp
407    }
408}