diff options
-rw-r--r-- | src/decode.rs | 37 | ||||
-rw-r--r-- | src/encode.rs | 47 | ||||
-rw-r--r-- | src/error.rs | 45 | ||||
-rw-r--r-- | src/lib.rs | 49 |
4 files changed, 139 insertions, 39 deletions
diff --git a/src/decode.rs b/src/decode.rs index 59dae97..a9abd37 100644 --- a/src/decode.rs +++ b/src/decode.rs | |||
@@ -1,15 +1,22 @@ | |||
1 | use std::io::{self, Cursor, Read}; | 1 | use std::io::{Cursor, Read}; |
2 | 2 | ||
3 | use bitvec::prelude::*; | 3 | use bitvec::prelude::*; |
4 | use byteorder::{LittleEndian, ReadBytesExt}; | 4 | use byteorder::{LittleEndian, ReadBytesExt}; |
5 | 5 | ||
6 | use crate::error::{OBIError, OBIResult}; | ||
6 | use crate::{FileHeader, Image, ImageInfoHeader}; | 7 | use crate::{FileHeader, Image, ImageInfoHeader}; |
7 | 8 | ||
8 | pub fn decode_image(obi_data: &mut Cursor<Vec<u8>>) -> io::Result<Image> { | 9 | pub fn decode_image(obi_data: &mut Cursor<Vec<u8>>) -> OBIResult<Image> { |
9 | // file header | 10 | // file header |
10 | let version = obi_data.read_u16::<LittleEndian>()?; | 11 | let version = obi_data |
11 | let file_size = obi_data.read_u32::<LittleEndian>()?; | 12 | .read_u16::<LittleEndian>() |
12 | let data_offset = obi_data.read_u32::<LittleEndian>()?; | 13 | .map_err(|_| OBIError::Decode)?; |
14 | let file_size = obi_data | ||
15 | .read_u32::<LittleEndian>() | ||
16 | .map_err(|_| OBIError::Decode)?; | ||
17 | let data_offset = obi_data | ||
18 | .read_u32::<LittleEndian>() | ||
19 | .map_err(|_| OBIError::Decode)?; | ||
13 | let file_header = FileHeader { | 20 | let file_header = FileHeader { |
14 | version, | 21 | version, |
15 | file_size, | 22 | file_size, |
@@ -17,10 +24,18 @@ pub fn decode_image(obi_data: &mut Cursor<Vec<u8>>) -> io::Result<Image> { | |||
17 | }; | 24 | }; |
18 | 25 | ||
19 | // image info header | 26 | // image info header |
20 | let width = obi_data.read_u32::<LittleEndian>()?; | 27 | let width = obi_data |
21 | let height = obi_data.read_u32::<LittleEndian>()?; | 28 | .read_u32::<LittleEndian>() |
22 | let compression_type = obi_data.read_u32::<LittleEndian>()?; | 29 | .map_err(|_| OBIError::Decode)?; |
23 | let post_compression_size = obi_data.read_u32::<LittleEndian>()?; | 30 | let height = obi_data |
31 | .read_u32::<LittleEndian>() | ||
32 | .map_err(|_| OBIError::Decode)?; | ||
33 | let compression_type = obi_data | ||
34 | .read_u32::<LittleEndian>() | ||
35 | .map_err(|_| OBIError::Decode)?; | ||
36 | let post_compression_size = obi_data | ||
37 | .read_u32::<LittleEndian>() | ||
38 | .map_err(|_| OBIError::Decode)?; | ||
24 | let image_info_header = ImageInfoHeader { | 39 | let image_info_header = ImageInfoHeader { |
25 | width, | 40 | width, |
26 | height, | 41 | height, |
@@ -30,7 +45,9 @@ pub fn decode_image(obi_data: &mut Cursor<Vec<u8>>) -> io::Result<Image> { | |||
30 | 45 | ||
31 | // pixmap data | 46 | // pixmap data |
32 | let mut data_bytes = vec![]; | 47 | let mut data_bytes = vec![]; |
33 | obi_data.read_to_end(&mut data_bytes)?; | 48 | obi_data |
49 | .read_to_end(&mut data_bytes) | ||
50 | .map_err(|_| OBIError::Decode)?; | ||
34 | let data = data_bytes | 51 | let data = data_bytes |
35 | .iter() | 52 | .iter() |
36 | .map(|&b| { | 53 | .map(|&b| { |
diff --git a/src/encode.rs b/src/encode.rs index e5760a8..a8c58e0 100644 --- a/src/encode.rs +++ b/src/encode.rs | |||
@@ -1,34 +1,55 @@ | |||
1 | use std::io; | 1 | use std::borrow::Borrow; |
2 | 2 | ||
3 | use bitvec::{prelude::*, vec::BitVec}; | 3 | use bitvec::{prelude::*, vec::BitVec}; |
4 | use byteorder::{LittleEndian, WriteBytesExt}; | 4 | use byteorder::{LittleEndian, WriteBytesExt}; |
5 | 5 | ||
6 | use crate::error::{OBIError, OBIResult}; | ||
6 | use crate::Image; | 7 | use crate::Image; |
7 | 8 | ||
8 | pub fn encode_image(obi_image: Image) -> io::Result<Vec<u8>> { | 9 | pub fn encode_image<I>(obi_image: I) -> OBIResult<Vec<u8>> |
10 | where | ||
11 | I: Borrow<Image>, | ||
12 | { | ||
13 | let obi_image = obi_image.borrow(); | ||
9 | let mut obi_data = Vec::with_capacity(obi_image.file_header.file_size as usize); | 14 | let mut obi_data = Vec::with_capacity(obi_image.file_header.file_size as usize); |
10 | 15 | ||
11 | // file header | 16 | // file header |
12 | let file_header = obi_image.file_header; | 17 | let file_header = &obi_image.file_header; |
13 | obi_data.write_u16::<LittleEndian>(file_header.version)?; | 18 | obi_data |
14 | obi_data.write_u32::<LittleEndian>(file_header.file_size)?; | 19 | .write_u16::<LittleEndian>(file_header.version) |
15 | obi_data.write_u32::<LittleEndian>(file_header.data_offset)?; | 20 | .map_err(|_| OBIError::Encode)?; |
21 | obi_data | ||
22 | .write_u32::<LittleEndian>(file_header.file_size) | ||
23 | .map_err(|_| OBIError::Encode)?; | ||
24 | obi_data | ||
25 | .write_u32::<LittleEndian>(file_header.data_offset) | ||
26 | .map_err(|_| OBIError::Encode)?; | ||
16 | 27 | ||
17 | // image info header | 28 | // image info header |
18 | let image_info_header = obi_image.image_info_header; | 29 | let image_info_header = &obi_image.image_info_header; |
19 | obi_data.write_u32::<LittleEndian>(image_info_header.width)?; | 30 | obi_data |
20 | obi_data.write_u32::<LittleEndian>(image_info_header.height)?; | 31 | .write_u32::<LittleEndian>(image_info_header.width) |
21 | obi_data.write_u32::<LittleEndian>(image_info_header.compression_type)?; | 32 | .map_err(|_| OBIError::Encode)?; |
22 | obi_data.write_u32::<LittleEndian>(image_info_header.post_compression_size)?; | 33 | obi_data |
34 | .write_u32::<LittleEndian>(image_info_header.height) | ||
35 | .map_err(|_| OBIError::Encode)?; | ||
36 | obi_data | ||
37 | .write_u32::<LittleEndian>(image_info_header.compression_type) | ||
38 | .map_err(|_| OBIError::Encode)?; | ||
39 | obi_data | ||
40 | .write_u32::<LittleEndian>(image_info_header.post_compression_size) | ||
41 | .map_err(|_| OBIError::Encode)?; | ||
23 | 42 | ||
24 | // pixmap data | 43 | // pixmap data |
25 | let pixmap = obi_image.data; | 44 | let pixmap = &obi_image.data; |
26 | for byte in pixmap.chunks(8) { | 45 | for byte in pixmap.chunks(8) { |
27 | let mut bv = BitVec::<Lsb0, u8>::new(); | 46 | let mut bv = BitVec::<Lsb0, u8>::new(); |
28 | for &b in byte { | 47 | for &b in byte { |
29 | bv.push(b); | 48 | bv.push(b); |
30 | } | 49 | } |
31 | obi_data.write_u8(bv.load::<u8>())?; | 50 | obi_data |
51 | .write_u8(bv.load::<u8>()) | ||
52 | .map_err(|_| OBIError::Encode)?; | ||
32 | } | 53 | } |
33 | 54 | ||
34 | return Ok(obi_data); | 55 | return Ok(obi_data); |
diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..8c2f096 --- /dev/null +++ b/src/error.rs | |||
@@ -0,0 +1,45 @@ | |||
1 | use std::{error, fmt}; | ||
2 | |||
3 | pub type OBIResult<T> = Result<T, OBIError>; | ||
4 | |||
5 | #[derive(Debug)] | ||
6 | pub enum OBIError { | ||
7 | Encode, | ||
8 | Decode, | ||
9 | Image, | ||
10 | } | ||
11 | |||
12 | impl fmt::Display for OBIError { | ||
13 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
14 | match self { | ||
15 | OBIError::Encode => writeln!(f, "Encoding error"), | ||
16 | OBIError::Decode => writeln!(f, "Decoding error"), | ||
17 | OBIError::Image => writeln!(f, "Image manipulation error"), | ||
18 | } | ||
19 | } | ||
20 | } | ||
21 | |||
22 | impl error::Error for OBIError {} | ||
23 | |||
24 | #[derive(Debug)] | ||
25 | enum Encode { | ||
26 | Metadata(MetadataError), | ||
27 | ChunkError(u32), | ||
28 | } | ||
29 | |||
30 | #[derive(Debug)] | ||
31 | enum Decode { | ||
32 | Metadata(MetadataError), | ||
33 | ChunkError(u32), | ||
34 | } | ||
35 | |||
36 | #[derive(Debug)] | ||
37 | enum MetadataError { | ||
38 | VersionError, | ||
39 | FileSizeError, | ||
40 | DataOffsetError, | ||
41 | WidthError, | ||
42 | HeightError, | ||
43 | CompressionTypeError, | ||
44 | PostCompressionSizeError, | ||
45 | } | ||
@@ -1,8 +1,13 @@ | |||
1 | #![allow(unreachable_patterns)] | 1 | #![allow(unreachable_patterns)] |
2 | #![allow(non_snake_case)] | 2 | #![allow(non_snake_case)] |
3 | 3 | ||
4 | pub mod decode; | 4 | use std::io; |
5 | pub mod encode; | 5 | |
6 | mod decode; | ||
7 | mod encode; | ||
8 | pub mod error; | ||
9 | |||
10 | use crate::error::{OBIError, OBIResult}; | ||
6 | 11 | ||
7 | #[non_exhaustive] | 12 | #[non_exhaustive] |
8 | #[derive(Copy, Clone, Debug, PartialEq)] | 13 | #[derive(Copy, Clone, Debug, PartialEq)] |
@@ -59,6 +64,7 @@ impl ImageInfoHeader { | |||
59 | } | 64 | } |
60 | 65 | ||
61 | #[non_exhaustive] | 66 | #[non_exhaustive] |
67 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
62 | pub enum CompressionType { | 68 | pub enum CompressionType { |
63 | RLE, | 69 | RLE, |
64 | Kosinki, | 70 | Kosinki, |
@@ -96,23 +102,34 @@ impl Image { | |||
96 | data, | 102 | data, |
97 | } | 103 | } |
98 | } | 104 | } |
99 | } | ||
100 | 105 | ||
101 | #[cfg(test)] | 106 | pub fn width(&self) -> u32 { |
102 | mod tests { | 107 | self.image_info_header.width |
103 | use super::*; | 108 | } |
104 | use std::mem::size_of; | 109 | |
110 | pub fn height(&self) -> u32 { | ||
111 | self.image_info_header.height | ||
112 | } | ||
113 | |||
114 | fn to_index(&self, x: u32, y: u32) -> usize { | ||
115 | (y * self.width() + x) as usize | ||
116 | } | ||
117 | |||
118 | pub fn set_pixel(&mut self, x: u32, y: u32, val: bool) -> OBIResult<()> { | ||
119 | if x >= self.width() || y > self.height() { | ||
120 | Err(OBIError::Image) | ||
121 | } else { | ||
122 | let index = self.to_index(x, y); | ||
123 | self.data[index] = val; | ||
124 | Ok(()) | ||
125 | } | ||
126 | } | ||
105 | 127 | ||
106 | #[test] | 128 | pub fn encode(&self) -> OBIResult<Vec<u8>> { |
107 | fn size_of_image_info_header() { | 129 | encode::encode_image(self) |
108 | let file_header_size = size_of::<ImageInfoHeader>(); | ||
109 | assert_eq!(16, file_header_size); | ||
110 | } | 130 | } |
111 | 131 | ||
112 | #[test] | 132 | pub fn decode(data: &mut io::Cursor<Vec<u8>>) -> OBIResult<Image> { |
113 | fn encode_decode() { | 133 | decode::decode_image(data) |
114 | let img = Image::new(100, 80); | ||
115 | let encoded = encode::encode_image(img).unwrap(); | ||
116 | assert_eq!(encoded.len(), 1026); | ||
117 | } | 134 | } |
118 | } | 135 | } |