Skip to main content

image_extras/
otb.rs

1//! Decoding of OTB Images
2//!
3//! OTB (Over The air Bitmap) Format is an image format from Nokia's Smart Messaging specification.
4//!
5//! # Related Links
6//! * <https://en.wikipedia.org/wiki/OTA_bitmap> - OTA bitmap on Wikipedia
7//! * <https://web.archive.org/web/20151028024229/https://www.csoft.co.uk/documents/sms3_0_0.pdf> - Specification
8
9use std::fmt::{self, Display};
10use std::io::{BufRead, Seek, Write};
11
12use image::error::{DecodingError, EncodingError, ImageFormatHint, LimitError, LimitErrorKind};
13use image::{ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageResult};
14
15/// All errors that can occur when attempting to encode an image to OTB format
16#[derive(Debug, Clone)]
17enum EncoderError {
18    /// Specified image does not fit into OTB width / height constraints
19    ImageTooLarge,
20
21    /// ColorType of image is not supported
22    UnsupportedColorType,
23}
24
25impl Display for EncoderError {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            EncoderError::ImageTooLarge => f.write_fmt(format_args!(
29                "Specified image is too large for the OTB format (Max 255x255)"
30            )),
31            EncoderError::UnsupportedColorType => f.write_fmt(format_args!(
32                "Color type of specified image is not supported"
33            )),
34        }
35    }
36}
37impl std::error::Error for EncoderError {}
38
39impl From<EncoderError> for ImageError {
40    fn from(e: EncoderError) -> ImageError {
41        match e {
42            EncoderError::ImageTooLarge => {
43                ImageError::Limits(LimitError::from_kind(LimitErrorKind::DimensionError))
44            }
45            _ => ImageError::Encoding(EncodingError::new(ImageFormatHint::Name("otb".into()), e)),
46        }
47    }
48}
49
50/// Encoder for Otb images.
51pub struct OtbEncoder<W> {
52    writer: W,
53    threshold: u8,
54}
55
56impl<W: Write> OtbEncoder<W> {
57    pub fn new(writer: W) -> Self {
58        Self {
59            writer,
60            threshold: 127_u8,
61        }
62    }
63
64    pub fn with_threshold(mut self, threshold: u8) -> Self {
65        self.threshold = threshold;
66        self
67    }
68}
69
70impl<W: Write> ImageEncoder for OtbEncoder<W> {
71    fn write_image(
72        mut self,
73        buf: &[u8],
74        width: u32,
75        height: u32,
76        color_type: ExtendedColorType,
77    ) -> std::result::Result<(), ImageError> {
78        if width > 0xFF || height > 0xFF {
79            return Err(EncoderError::ImageTooLarge.into());
80        }
81        if color_type != ExtendedColorType::L8 {
82            return Err(EncoderError::UnsupportedColorType.into());
83        }
84
85        // Write Headers
86        let _ = self
87            .writer
88            .write(&[0x00, width as u8, height as u8, 0x01])?;
89
90        // Write the encoded image
91        let mut current_byte = 0_u8;
92        let mut bit = 0;
93        for buf_idx in 0..(width * height) {
94            if buf[buf_idx as usize] < self.threshold {
95                current_byte |= 1 << (7 - bit);
96            }
97            bit += 1;
98            if bit == 8 {
99                self.writer.write_all(&[current_byte])?;
100                current_byte = 0_u8;
101                bit = 0;
102            };
103        }
104        if bit != 0 {
105            self.writer.write_all(&[current_byte])?;
106        }
107
108        Ok(())
109    }
110}
111
112/// All errors that can occur when attempting to parse an OTB image
113#[derive(Debug, Clone)]
114enum DecoderError {
115    /// Info field in OTB image headers is unsupported
116    UnsupportedInfoField(u8),
117    /// Width in OTB image headers is zero
118    WidthZero,
119    /// Height in OTB image headers is zero
120    HeightZero,
121    /// Specified color depth is not supported
122    UnsupportedColorDepth(u8),
123}
124
125impl Display for DecoderError {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::UnsupportedInfoField(info) => {
129                f.write_fmt(format_args!("Unsupported value in info field {info:08b}"))
130            }
131            Self::WidthZero => f.write_fmt(format_args!("Width cannot be zero")),
132            Self::HeightZero => f.write_fmt(format_args!("Height cannot be zero")),
133            Self::UnsupportedColorDepth(depth) => f.write_fmt(format_args!(
134                "Unsupported color depth value in headers {depth}"
135            )),
136        }
137    }
138}
139impl std::error::Error for DecoderError {}
140
141impl From<DecoderError> for ImageError {
142    fn from(e: DecoderError) -> ImageError {
143        ImageError::Decoding(DecodingError::new(ImageFormatHint::Name("otb".into()), e))
144    }
145}
146
147/// Decoder for Otb images.
148pub struct OtbDecoder<R> {
149    reader: R,
150    dimensions: (u32, u32),
151}
152
153impl<R> OtbDecoder<R>
154where
155    R: BufRead + Seek,
156{
157    /// Create a new `OtbDecoder`.
158    pub fn new(reader: R) -> Result<OtbDecoder<R>, ImageError> {
159        let mut decoder = Self::new_decoder(reader);
160        decoder.read_metadata()?;
161        Ok(decoder)
162    }
163
164    fn new_decoder(reader: R) -> OtbDecoder<R> {
165        Self {
166            reader,
167            dimensions: (0, 0),
168        }
169    }
170
171    fn read_metadata(&mut self) -> Result<(), ImageError> {
172        let mut buf = [0_u8; 1];
173
174        self.reader.read_exact(&mut buf)?;
175        let info_field = buf[0];
176
177        // InfoField
178        // 00 for u8 width/height values
179        // 10 for u16 values
180        if info_field != 0x0 && info_field != 0x10 {
181            return Err(DecoderError::UnsupportedInfoField(info_field).into());
182        }
183
184        // Width
185        self.reader.read_exact(&mut buf)?;
186        let mut width = buf[0] as u16;
187        if info_field == 0x10 {
188            self.reader.read_exact(&mut buf)?;
189            width = width << 8 | buf[0] as u16;
190        }
191        if width == 0 {
192            return Err(DecoderError::WidthZero.into());
193        }
194
195        // Height
196        self.reader.read_exact(&mut buf)?;
197        let mut height = buf[0] as u16;
198        if info_field == 0x10 {
199            self.reader.read_exact(&mut buf)?;
200            height = height << 8 | buf[0] as u16;
201        }
202        if height == 0 {
203            return Err(DecoderError::HeightZero.into());
204        }
205
206        // Depth
207        self.reader.read_exact(&mut buf)?;
208        let depth = buf[0];
209        if depth != 1 {
210            return Err(DecoderError::UnsupportedColorDepth(depth).into());
211        }
212
213        self.dimensions = (width as u32, height as u32);
214
215        Ok(())
216    }
217}
218
219impl<R: BufRead + Seek> ImageDecoder for OtbDecoder<R> {
220    fn dimensions(&self) -> (u32, u32) {
221        self.dimensions
222    }
223
224    fn color_type(&self) -> ColorType {
225        ColorType::L8
226    }
227
228    fn original_color_type(&self) -> ExtendedColorType {
229        ExtendedColorType::L1
230    }
231
232    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
233        let (width, height) = (self.dimensions.0 as usize, self.dimensions.1 as usize);
234
235        assert_eq!(buf.len(), width * height, "Invalid buffer length");
236
237        // Read entire image data into a buffer
238        let mut byte_buf = vec![0_u8; (width * height).div_ceil(8)].into_boxed_slice();
239        self.reader.read_exact(&mut byte_buf)?;
240
241        // Set a byte in buf for every bit in the image data
242        for (i, &byte) in byte_buf.iter().enumerate() {
243            for bit in 0..8 {
244                let buf_idx = 8 * i + bit;
245                if buf_idx >= buf.len() {
246                    break;
247                }
248
249                buf[buf_idx] = if (byte >> (7 - bit)) & 1 == 0 {
250                    0xFF
251                } else {
252                    0x00
253                };
254            }
255        }
256        Ok(())
257    }
258
259    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
260        (*self).read_image(buf)
261    }
262}
263
264mod test {
265    #[test]
266    fn test_decode_image() {
267        use image::ImageDecoder;
268        use std::io::Cursor;
269        let otb_data = vec![
270            // Headers
271            0x00, 0x48, 0x1C, 0x01, // End Headers
272            // Image Data
273            0x7F, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xFB, 0xFF, 0xFE, 0x40, 0x3F, 0xE8, 0x38, 0x2F,
274            0xFF, 0xFB, 0xFF, 0xFE, 0x48, 0x3F, 0xA8, 0x38, 0x2F, 0x9F, 0xFB, 0xFF, 0xFE, 0x4C,
275            0xFF, 0xA9, 0xFF, 0x2F, 0x8F, 0xFA, 0xDA, 0xDA, 0x4E, 0xFF, 0x29, 0x01, 0x2F, 0x80,
276            0xFA, 0x52, 0x52, 0x5E, 0x7F, 0x69, 0x31, 0x2F, 0xBF, 0x7B, 0x07, 0x06, 0x4F, 0xFF,
277            0x69, 0x79, 0x2F, 0xBE, 0xFB, 0x77, 0x76, 0x47, 0xFF, 0x69, 0x79, 0x2F, 0xBE, 0x7B,
278            0x07, 0x06, 0x47, 0xFE, 0xEF, 0x7D, 0xEF, 0xBE, 0x7B, 0xFF, 0xFE, 0x47, 0xFC, 0xEF,
279            0x7D, 0xE7, 0xBC, 0xF1, 0xFF, 0xFC, 0x40, 0xF0, 0xEF, 0x7D, 0xE7, 0x7C, 0xF1, 0xED,
280            0xBC, 0x21, 0xE7, 0xC9, 0x79, 0x27, 0x98, 0xF1, 0xE5, 0x3C, 0x21, 0xE7, 0xC9, 0x39,
281            0x27, 0xC8, 0xF1, 0xF0, 0x7C, 0x16, 0x6F, 0x89, 0x39, 0x23, 0xE6, 0xE0, 0xF7, 0x78,
282            0x15, 0x2F, 0x88, 0x82, 0x23, 0xF3, 0xE0, 0xF0, 0x78, 0x08, 0x3F, 0x04, 0x44, 0x43,
283            0xD7, 0xE0, 0xFF, 0xF8, 0x04, 0x3E, 0x02, 0x28, 0x81, 0xEF, 0xC0, 0x7F, 0xF0, 0x02,
284            0x3C, 0x01, 0x39, 0x00, 0xFF, 0x80, 0x3F, 0xE0, 0x01, 0x38, 0x00, 0xBA, 0x00, 0x7F,
285            0x00, 0x1F, 0xC0, 0x00, 0xF0, 0x00, 0x7C, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0xFF, 0xC0,
286            0x00, 0x38, 0x00, 0x1C, 0x00, 0x07, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
287            0xFF, 0xAA, 0x2A, 0xF3, 0x87, 0x87, 0x3F, 0x1E, 0x67, 0x0F, 0x54, 0x15, 0xF3, 0x93,
288            0x9F, 0x3E, 0x4E, 0x27, 0x27, 0xA8, 0x2A, 0xF3, 0x87, 0x8F, 0x3E, 0x4E, 0x07, 0x27,
289            0x54, 0x55, 0xF3, 0x93, 0x9F, 0x3E, 0x0E, 0x47, 0x27, 0xAA, 0xFF, 0xF3, 0x9B, 0x87,
290            0x0E, 0x4E, 0x67, 0x0F, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
291            0x00, // End Image Data
292        ];
293        let decoder = crate::otb::OtbDecoder::new(Cursor::new(otb_data)).unwrap();
294        let (width, height) = decoder.dimensions();
295        assert!(width == 0x48);
296        assert!(height == 0x1C);
297        let mut img_bytes = vec![0; 2016];
298        decoder.read_image(&mut img_bytes).unwrap();
299    }
300
301    #[test]
302    fn test_decoder_irregular_width() {
303        use image::ImageDecoder;
304        use std::io::Cursor;
305        let expected_data = [
306            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row0
307            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row1
308            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row2
309            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row3
310            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row4
311            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row5
312            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row6
313            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row7
314            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row8
315            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row9
316        ];
317        #[allow(clippy::unusual_byte_groupings)]
318        let image_data: [u8; 17] = [
319            0,
320            10,
321            10,
322            1, // headers
323            0b_00000000,
324            0b00_000011,
325            0b0000_0001,
326            0b001000_00,
327            0b10000100_,
328            0b_01000000,
329            0b10_010000,
330            0b0010_0010,
331            0b000100_00,
332            0b01001000_,
333            0b_00001100,
334            0b00_000000,
335            0b0000_0000,
336        ];
337        let decoder = crate::otb::OtbDecoder::new(Cursor::new(image_data)).unwrap();
338        let (width, height) = decoder.dimensions();
339        assert!(width == 10);
340        assert!(height == 10);
341        let mut img_bytes = vec![0; 100];
342        decoder.read_image(&mut img_bytes).unwrap();
343        img_bytes.iter().enumerate().for_each(|(i, byte)| {
344            assert_eq!(*byte, expected_data[i]);
345        });
346    }
347
348    #[test]
349    fn test_decoder() {
350        use image::ImageDecoder;
351        use std::io::Cursor;
352        let expected_data = [
353            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row1
354            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row2
355            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row3
356            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row4
357            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row5
358            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row6
359            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row7
360            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row8
361        ];
362        let image_data: [u8; 12] = [
363            0, 8, 8, 1,          // headers
364            0b00011000, // row1
365            0b00100100, // row2
366            0b01000010, // row3
367            0b10000001, // row4
368            0b10000001, // row5
369            0b01000010, // row6
370            0b00100100, // row7
371            0b00011000, // row8
372        ];
373        let decoder = crate::otb::OtbDecoder::new(Cursor::new(image_data)).unwrap();
374        let (width, height) = decoder.dimensions();
375        assert!(width == 8);
376        assert!(height == 8);
377        let mut img_bytes = vec![0; 64];
378        decoder.read_image(&mut img_bytes).unwrap();
379        img_bytes.iter().enumerate().for_each(|(i, byte)| {
380            assert_eq!(*byte, expected_data[i]);
381        });
382    }
383
384    #[test]
385    fn test_encoder() {
386        use image::ImageEncoder;
387        let img_data = [
388            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row1
389            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row2
390            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row3
391            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row4
392            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row5
393            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row6
394            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row7
395            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row8
396        ];
397        let expected_data: [u8; 12] = [
398            0, 8, 8, 1,          // headers
399            0b00011000, // row1
400            0b00100100, // row2
401            0b01000010, // row3
402            0b10000001, // row4
403            0b10000001, // row5
404            0b01000010, // row6
405            0b00100100, // row7
406            0b00011000, // row8
407        ];
408        let mut encoded_data = Vec::<u8>::with_capacity(expected_data.len());
409        let encoder = crate::otb::OtbEncoder::new(&mut encoded_data);
410        encoder
411            .write_image(&img_data, 8, 8, image::ExtendedColorType::L8)
412            .unwrap();
413        encoded_data.iter().enumerate().for_each(|(i, byte)| {
414            assert_eq!(*byte, expected_data[i]);
415        });
416    }
417
418    #[test]
419    fn test_encoder_irregular_width() {
420        use image::ImageEncoder;
421        let img_data = [
422            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row0
423            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row1
424            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row2
425            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row3
426            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row4
427            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row5
428            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row6
429            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row7
430            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row8
431            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row9
432        ];
433        #[allow(clippy::unusual_byte_groupings)]
434        let expected_data: [u8; 17] = [
435            0,
436            10,
437            10,
438            1, // headers
439            0b_00000000,
440            0b00_000011,
441            0b0000_0001,
442            0b001000_00,
443            0b10000100_,
444            0b_01000000,
445            0b10_010000,
446            0b0010_0010,
447            0b000100_00,
448            0b01001000_,
449            0b_00001100,
450            0b00_000000,
451            0b0000_0000,
452        ];
453        let mut encoded_data = Vec::<u8>::with_capacity(expected_data.len());
454        let encoder = crate::otb::OtbEncoder::new(&mut encoded_data);
455        encoder
456            .write_image(&img_data, 10, 10, image::ExtendedColorType::L8)
457            .unwrap();
458        encoded_data.iter().enumerate().for_each(|(i, byte)| {
459            assert_eq!(*byte, expected_data[i]);
460        });
461    }
462}