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