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 header_buf = [0_u8; 4];
173        self.reader.read_exact(&mut header_buf)?;
174
175        let [info_field, width, height, depth] = header_buf;
176
177        // InfoField - 00 for single byte width/height values
178        if info_field != 0 {
179            return Err(DecoderError::UnsupportedInfoField(info_field).into());
180        }
181
182        // Width
183        if width == 0 {
184            return Err(DecoderError::WidthZero.into());
185        }
186
187        // Height
188        if height == 0 {
189            return Err(DecoderError::HeightZero.into());
190        }
191
192        // Depth
193        if depth != 1 {
194            return Err(DecoderError::UnsupportedColorDepth(depth).into());
195        }
196
197        self.dimensions = (width as u32, height as u32);
198
199        Ok(())
200    }
201}
202
203impl<R: BufRead + Seek> ImageDecoder for OtbDecoder<R> {
204    fn dimensions(&self) -> (u32, u32) {
205        self.dimensions
206    }
207
208    fn color_type(&self) -> ColorType {
209        ColorType::L8
210    }
211
212    fn original_color_type(&self) -> ExtendedColorType {
213        ExtendedColorType::L1
214    }
215
216    fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> {
217        let (width, height) = (self.dimensions.0 as usize, self.dimensions.1 as usize);
218
219        assert_eq!(buf.len(), width * height, "Invalid buffer length");
220
221        // Read entire image data into a buffer
222        let mut byte_buf = vec![0_u8; (width * height).div_ceil(8)].into_boxed_slice();
223        self.reader.read_exact(&mut byte_buf)?;
224
225        // Set a byte in buf for every bit in the image data
226        for (i, &byte) in byte_buf.iter().enumerate() {
227            for bit in 0..8 {
228                let buf_idx = 8 * i + bit;
229                if buf_idx >= buf.len() {
230                    break;
231                }
232
233                buf[buf_idx] = if (byte >> (7 - bit)) & 1 == 0 {
234                    0xFF
235                } else {
236                    0x00
237                };
238            }
239        }
240        Ok(())
241    }
242
243    fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
244        (*self).read_image(buf)
245    }
246}
247
248mod test {
249    #[test]
250    fn test_decode_image() {
251        use image::ImageDecoder;
252        use std::io::Cursor;
253        let otb_data = vec![
254            // Headers
255            0x00, 0x48, 0x1C, 0x01, // End Headers
256            // Image Data
257            0x7F, 0xFF, 0xEF, 0xFF, 0xEF, 0xFF, 0xFB, 0xFF, 0xFE, 0x40, 0x3F, 0xE8, 0x38, 0x2F,
258            0xFF, 0xFB, 0xFF, 0xFE, 0x48, 0x3F, 0xA8, 0x38, 0x2F, 0x9F, 0xFB, 0xFF, 0xFE, 0x4C,
259            0xFF, 0xA9, 0xFF, 0x2F, 0x8F, 0xFA, 0xDA, 0xDA, 0x4E, 0xFF, 0x29, 0x01, 0x2F, 0x80,
260            0xFA, 0x52, 0x52, 0x5E, 0x7F, 0x69, 0x31, 0x2F, 0xBF, 0x7B, 0x07, 0x06, 0x4F, 0xFF,
261            0x69, 0x79, 0x2F, 0xBE, 0xFB, 0x77, 0x76, 0x47, 0xFF, 0x69, 0x79, 0x2F, 0xBE, 0x7B,
262            0x07, 0x06, 0x47, 0xFE, 0xEF, 0x7D, 0xEF, 0xBE, 0x7B, 0xFF, 0xFE, 0x47, 0xFC, 0xEF,
263            0x7D, 0xE7, 0xBC, 0xF1, 0xFF, 0xFC, 0x40, 0xF0, 0xEF, 0x7D, 0xE7, 0x7C, 0xF1, 0xED,
264            0xBC, 0x21, 0xE7, 0xC9, 0x79, 0x27, 0x98, 0xF1, 0xE5, 0x3C, 0x21, 0xE7, 0xC9, 0x39,
265            0x27, 0xC8, 0xF1, 0xF0, 0x7C, 0x16, 0x6F, 0x89, 0x39, 0x23, 0xE6, 0xE0, 0xF7, 0x78,
266            0x15, 0x2F, 0x88, 0x82, 0x23, 0xF3, 0xE0, 0xF0, 0x78, 0x08, 0x3F, 0x04, 0x44, 0x43,
267            0xD7, 0xE0, 0xFF, 0xF8, 0x04, 0x3E, 0x02, 0x28, 0x81, 0xEF, 0xC0, 0x7F, 0xF0, 0x02,
268            0x3C, 0x01, 0x39, 0x00, 0xFF, 0x80, 0x3F, 0xE0, 0x01, 0x38, 0x00, 0xBA, 0x00, 0x7F,
269            0x00, 0x1F, 0xC0, 0x00, 0xF0, 0x00, 0x7C, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0xFF, 0xC0,
270            0x00, 0x38, 0x00, 0x1C, 0x00, 0x07, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
271            0xFF, 0xAA, 0x2A, 0xF3, 0x87, 0x87, 0x3F, 0x1E, 0x67, 0x0F, 0x54, 0x15, 0xF3, 0x93,
272            0x9F, 0x3E, 0x4E, 0x27, 0x27, 0xA8, 0x2A, 0xF3, 0x87, 0x8F, 0x3E, 0x4E, 0x07, 0x27,
273            0x54, 0x55, 0xF3, 0x93, 0x9F, 0x3E, 0x0E, 0x47, 0x27, 0xAA, 0xFF, 0xF3, 0x9B, 0x87,
274            0x0E, 0x4E, 0x67, 0x0F, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
275            0x00, // End Image Data
276        ];
277        let decoder = crate::otb::OtbDecoder::new(Cursor::new(otb_data)).unwrap();
278        let (width, height) = decoder.dimensions();
279        assert!(width == 0x48);
280        assert!(height == 0x1C);
281        let mut img_bytes = vec![0; 2016];
282        decoder.read_image(&mut img_bytes).unwrap();
283    }
284
285    #[test]
286    fn test_decoder_irregular_width() {
287        use image::ImageDecoder;
288        use std::io::Cursor;
289        let expected_data = [
290            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row0
291            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row1
292            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row2
293            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row3
294            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row4
295            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row5
296            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row6
297            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row7
298            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row8
299            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row9
300        ];
301        #[allow(clippy::unusual_byte_groupings)]
302        let image_data: [u8; 17] = [
303            0,
304            10,
305            10,
306            1, // headers
307            0b_00000000,
308            0b00_000011,
309            0b0000_0001,
310            0b001000_00,
311            0b10000100_,
312            0b_01000000,
313            0b10_010000,
314            0b0010_0010,
315            0b000100_00,
316            0b01001000_,
317            0b_00001100,
318            0b00_000000,
319            0b0000_0000,
320        ];
321        let decoder = crate::otb::OtbDecoder::new(Cursor::new(image_data)).unwrap();
322        let (width, height) = decoder.dimensions();
323        assert!(width == 10);
324        assert!(height == 10);
325        let mut img_bytes = vec![0; 100];
326        decoder.read_image(&mut img_bytes).unwrap();
327        img_bytes.iter().enumerate().for_each(|(i, byte)| {
328            assert_eq!(*byte, expected_data[i]);
329        });
330    }
331
332    #[test]
333    fn test_decoder() {
334        use image::ImageDecoder;
335        use std::io::Cursor;
336        let expected_data = [
337            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row1
338            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row2
339            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row3
340            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row4
341            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row5
342            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row6
343            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row7
344            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row8
345        ];
346        let image_data: [u8; 12] = [
347            0, 8, 8, 1,          // headers
348            0b00011000, // row1
349            0b00100100, // row2
350            0b01000010, // row3
351            0b10000001, // row4
352            0b10000001, // row5
353            0b01000010, // row6
354            0b00100100, // row7
355            0b00011000, // row8
356        ];
357        let decoder = crate::otb::OtbDecoder::new(Cursor::new(image_data)).unwrap();
358        let (width, height) = decoder.dimensions();
359        assert!(width == 8);
360        assert!(height == 8);
361        let mut img_bytes = vec![0; 64];
362        decoder.read_image(&mut img_bytes).unwrap();
363        img_bytes.iter().enumerate().for_each(|(i, byte)| {
364            assert_eq!(*byte, expected_data[i]);
365        });
366    }
367
368    #[test]
369    fn test_encoder() {
370        use image::ImageEncoder;
371        let img_data = [
372            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row1
373            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row2
374            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row3
375            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row4
376            0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, // row5
377            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row6
378            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row7
379            0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, // row8
380        ];
381        let expected_data: [u8; 12] = [
382            0, 8, 8, 1,          // headers
383            0b00011000, // row1
384            0b00100100, // row2
385            0b01000010, // row3
386            0b10000001, // row4
387            0b10000001, // row5
388            0b01000010, // row6
389            0b00100100, // row7
390            0b00011000, // row8
391        ];
392        let mut encoded_data = Vec::<u8>::with_capacity(expected_data.len());
393        let encoder = crate::otb::OtbEncoder::new(&mut encoded_data);
394        encoder
395            .write_image(&img_data, 8, 8, image::ExtendedColorType::L8)
396            .unwrap();
397        encoded_data.iter().enumerate().for_each(|(i, byte)| {
398            assert_eq!(*byte, expected_data[i]);
399        });
400    }
401
402    #[test]
403    fn test_encoder_irregular_width() {
404        use image::ImageEncoder;
405        let img_data = [
406            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row0
407            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row1
408            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row2
409            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row3
410            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row4
411            0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, // row5
412            0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, // row6
413            0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, // row7
414            0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, // row8
415            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row9
416        ];
417        #[allow(clippy::unusual_byte_groupings)]
418        let expected_data: [u8; 17] = [
419            0,
420            10,
421            10,
422            1, // headers
423            0b_00000000,
424            0b00_000011,
425            0b0000_0001,
426            0b001000_00,
427            0b10000100_,
428            0b_01000000,
429            0b10_010000,
430            0b0010_0010,
431            0b000100_00,
432            0b01001000_,
433            0b_00001100,
434            0b00_000000,
435            0b0000_0000,
436        ];
437        let mut encoded_data = Vec::<u8>::with_capacity(expected_data.len());
438        let encoder = crate::otb::OtbEncoder::new(&mut encoded_data);
439        encoder
440            .write_image(&img_data, 10, 10, image::ExtendedColorType::L8)
441            .unwrap();
442        encoded_data.iter().enumerate().for_each(|(i, byte)| {
443            assert_eq!(*byte, expected_data[i]);
444        });
445    }
446}