mag_image_decoder/
lib.rs

1//! MAG image decoder
2//!
3//! [MAG format](https://ja.wikipedia.org/?curid=115972) is also known as MAKI02, Maki-chan Graphics.
4//!
5//! # Examples
6//! ```no_run
7//! use std::fs::File;
8//! use std::io::BufReader;
9//! use mag_image_decoder::Decoder;
10//!
11//! let file = File::open("SAMPLE.MAG").unwrap();
12//! let decoder = Decoder::new(BufReader::new(file)).unwrap();
13//! let header = decoder.info();
14//! println!("{:?}", header);
15//! let img = decoder.decode().unwrap();
16//! img.save("SAMPLE.png").unwrap();
17//! ```
18
19use std::io::{Cursor, Read, Seek, SeekFrom};
20
21use byteorder::{LittleEndian as LE, ReadBytesExt};
22use encoding_rs::*;
23use log::debug;
24
25pub use crate::error::*;
26use std::ops::Range;
27use image::{ImageBuffer, RgbImage, Rgb, imageops, FilterType};
28use bit_vec::BitVec;
29
30pub mod error;
31
32/// Represents metadata of an image.
33#[derive(Clone, Debug, PartialEq)]
34pub struct ImageInfo {
35    /// The machine name (max 4 characters).
36    /// e.g. PC98, PC88, ESEQ, X68K, MSX2
37    pub machine_code: String,
38    /// The author's name
39    pub user_name: String,
40    /// The author's memo
41    pub memo: String,
42    /// The x position
43    pub x: u16,
44    /// The y position
45    pub y: u16,
46    /// The width of the image, in pixels
47    pub width: u16,
48    /// The height of the image, in pixels
49    pub height: u16,
50    /// The number of colors, 16 or 256
51    pub num_colors: u16,
52    /// The rectangular pixel aspect ratio flag
53    pub is_200_line_mode: bool,
54}
55
56#[derive(Copy, Clone, Debug)]
57enum ColorMode { Palette16, Palette256 }
58
59/// MAG decoder
60pub struct Decoder {
61    info: ImageInfo,
62    header_offset: u32,
63    color_mode: ColorMode,
64    buf: Vec<u8>,
65}
66
67// TODO: 最初に並べ替えておく
68struct Palette {
69    grb_colors: Vec<u8>,
70}
71
72impl Palette {
73    pub fn new(grb_colors: &[u8]) -> Palette {
74        Palette { grb_colors: grb_colors.to_owned() }
75    }
76
77    pub fn rgb(&self, index: u8) -> Rgb<u8> {
78        let index = index as usize * 3;
79        let g = self.grb_colors[index];
80        let r = self.grb_colors[index + 1];
81        let b = self.grb_colors[index + 2];
82        Rgb([r, g, b])
83    }
84}
85
86const MAGIC_NUMBER: &[u8; 8] = b"MAKI02  ";
87const TEXT_ENCODING: &str = "Shift_JIS";
88const HEADER_SIZE: u32 = 32;
89
90
91fn range(start: u32, size: u32) -> Range<usize> {
92    start as usize..(start + size) as usize
93}
94
95fn pixel_unit(c: ColorMode) -> u16 {
96    match c {
97        ColorMode::Palette16 => 8,
98        ColorMode::Palette256 => 4,
99    }
100}
101
102fn nibble_high(b: u8) -> u8 {
103    b >> 4
104}
105
106fn nibble_low(b: u8) -> u8 {
107    b & 0xf
108}
109
110impl Decoder {
111    /// Creates a new `Decoder` using the reader `reader`.
112    pub fn new<R: Read>(mut reader: R) -> Result<Decoder> {
113        let mut buf = Vec::new();
114        reader.read_to_end(&mut buf).unwrap();
115
116        let encoding = Encoding::for_label(TEXT_ENCODING.as_bytes())
117            .ok_or_else(|| other_err(format!("Unknown encoding; {}", TEXT_ENCODING)))?;
118
119        if &buf[..8] != MAGIC_NUMBER {
120            return Err(Error::InvalidFormat("Magic number mismatch".into()));
121        }
122
123        let machine_code = String::from_utf8(buf[8..12].to_owned()).unwrap();
124        let (user_name, _, _) = encoding.decode(&buf[range(12, 19)]);
125        debug!("machine_code: '{}', user_name: '{}'", machine_code, user_name);
126
127        let memo = &buf.iter().skip(31).take_while(|&b| *b != 0x1au8)
128            .cloned().collect::<Vec<u8>>();
129        let header_offset = 31 + memo.len() as u32 + 1;
130        debug!("header_offset: {}", header_offset);
131        let mut header_buf = Cursor::new(buf[range(header_offset, HEADER_SIZE)].to_owned());
132        let (memo, _, _) = encoding.decode(&memo);
133        debug!("memo: '{}'", memo);
134
135        if header_buf.read_u8()? != 0 {
136            return Err(Error::InvalidFormat("header offset 0x00".into()));
137        }
138        header_buf.seek(SeekFrom::Current(2))?;
139        let screen_mode = header_buf.read_u8()?;
140        let color_mode =
141            if screen_mode & 0x80 != 0 { ColorMode::Palette256 } else { ColorMode::Palette16 };
142        debug!("screen_mode: {}, color_mode: {:?}", screen_mode, color_mode);
143
144        let x = header_buf.read_u16::<LE>()?;
145        let y = header_buf.read_u16::<LE>()?;
146        let end_x = header_buf.read_u16::<LE>()?;
147        let end_y = header_buf.read_u16::<LE>()?;
148        debug!("x: {}, y: {}, end_x: {}, end_y: {}", x, y, end_x, end_y);
149        let pixel_unit = pixel_unit(color_mode);
150
151        Ok(Decoder {
152            info: ImageInfo {
153                machine_code,
154                user_name: user_name.to_string(),
155                memo: memo.to_string(),
156                x,
157                y,
158                width: (((end_x / pixel_unit) - (x / pixel_unit)) + 1) * pixel_unit,
159                height: end_y - y + 1,
160                num_colors: match color_mode {
161                    ColorMode::Palette16 => 16,
162                    ColorMode::Palette256 => 256,
163                },
164                is_200_line_mode: screen_mode & 1 != 0,
165            },
166            header_offset,
167            color_mode,
168            buf,
169        })
170    }
171
172    /// Gets metadata
173    pub fn info(&self) -> &ImageInfo {
174        &self.info
175    }
176
177    /// Decodes to RGB image buffer
178    pub fn decode(&self) -> Result<RgbImage> {
179        let buf = &self.buf;
180        let mut header_buf = Cursor::new(buf[range(self.header_offset, HEADER_SIZE)].to_owned());
181        header_buf.seek(SeekFrom::Start(12))?;
182
183        let flag_a_offset = header_buf.read_u32::<LE>()?;
184        let flag_b_offset = header_buf.read_u32::<LE>()?;
185        let flag_b_size = header_buf.read_u32::<LE>()?;
186        let flag_a_size = flag_b_offset - flag_a_offset;
187        let pixel_offset = header_buf.read_u32::<LE>()?;
188        let pixel_size = header_buf.read_u32::<LE>()?;
189        debug!("flag_a_offset: {}, flag_b_offset: {}, flag_a_size: {}, flag_b_size: {}, pixel_offset: {}, pixel_size: {}",
190               flag_a_offset, flag_b_offset, flag_a_size, flag_b_size, pixel_offset, pixel_size);
191        assert_eq!(header_buf.position() as u32, HEADER_SIZE);
192
193        let palette = &buf[range(self.header_offset + HEADER_SIZE, u32::from(self.info.num_colors * 3))];
194        let flag_a = &buf[range(self.header_offset + flag_a_offset, flag_a_size)];
195        let flag_b = &buf[range(self.header_offset + flag_b_offset, flag_b_size)];
196        let pixels = &buf[range(self.header_offset + pixel_offset, pixel_size)];
197//        dbg!(flag_a.len(), flag_b.len(), pixels.len());
198//        dbg!(buf.len() - (self.header_offset + pixel_offset + pixel_size) as usize);
199
200        let mut img: RgbImage = ImageBuffer::new(u32::from(self.info.width), u32::from(self.info.height));
201        let pixel_unit = pixel_unit(self.color_mode);
202        let num_x_units = self.info.width / pixel_unit;
203
204        let mut flag_a_bits = BitVec::from_bytes(flag_a).into_iter();
205        let mut flag_b = Cursor::new(flag_b);
206        let mut pixels = Cursor::new(pixels);
207        let palette = Palette::new(palette);
208        let mut line_flags = vec![0u8; num_x_units as usize];
209        let copy_vec = self.init_copy_vec();
210
211        for y in 0..u32::from(self.info.height) {
212            for x in 0..num_x_units as usize {
213                if let Some(true) = flag_a_bits.next() {
214                    line_flags[x] ^= flag_b.read_u8()?;
215                }
216            }
217
218            let mut dst_x = 0;
219            let mut decode_nibble = |flag: u8| {
220                if flag == 0 {
221                    match self.color_mode {
222                        ColorMode::Palette16 => {
223                            for _ in 0..2 {
224                                let pixel_byte = pixels.read_u8().unwrap();
225                                img.put_pixel(dst_x, y, palette.rgb(nibble_high(pixel_byte)));
226                                dst_x += 1;
227                                img.put_pixel(dst_x, y, palette.rgb(nibble_low(pixel_byte)));
228                                dst_x += 1;
229                            }
230                        }
231                        ColorMode::Palette256 => {
232                            for _ in 0..2 {
233                                let pixel_byte = pixels.read_u8().unwrap();
234                                img.put_pixel(dst_x, y, palette.rgb(pixel_byte));
235                                dst_x += 1;
236                            }
237                        }
238                    }
239                } else {
240                    dst_x += self.copy_pixel_unit(&mut img, dst_x, y, copy_vec[flag as usize]);
241                }
242            };
243
244            for x in 0..num_x_units as usize {
245                let flag = line_flags[x];
246                decode_nibble(nibble_high(flag));
247                decode_nibble(nibble_low(flag));
248            }
249        }
250
251        if self.info.is_200_line_mode {
252            Ok(imageops::resize(&img, u32::from(self.info.width), u32::from(self.info.height) * 2,
253                                FilterType::Nearest))
254        } else {
255            Ok(img)
256        }
257    }
258
259    fn init_copy_vec(&self) -> Vec<(u32, u32)> {
260        let x = [0, 1, 2, 4, 0, 1, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0];
261        let y = [0, 0, 0, 0, 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16];
262        x.iter().zip(y.iter()).map(|(a, b)| (*a, *b)).collect()
263    }
264
265    fn copy_pixel_unit(&self, img: &mut RgbImage, x: u32, y: u32, copy_from: (u32, u32)) -> u32 {
266        let copy_pixels = match self.color_mode {
267            ColorMode::Palette16 => 4,
268            ColorMode::Palette256 => 2,
269        };
270        let src_x = x - (copy_from.0 * copy_pixels);
271        let src_y = y - copy_from.1;
272
273        for i in 0..copy_pixels {
274            img.put_pixel(x + i, y, img[(src_x + i, src_y)]);
275        }
276
277        copy_pixels
278    }
279}