wml2 0.0.23

Pure Rust multi-format image decoding and encoding library supporting JPEG, PNG, GIF, WebP, TIFF and PC-98 legacy formats (MAG, MAKI, PI, PIC)
Documentation
//! MAKI decoder implementation.

use bin_rs::reader::BinaryReader;

use crate::draw::DecodeOptions;
use crate::error::ImgErrorKind;
use crate::metadata::DataMap;
use crate::retro::{ByteCursor, draw_indexed, err, read_all};
use crate::warning::ImgWarnings;

type Error = Box<dyn std::error::Error>;

struct MakiHeader {
    mode: usize,
    text: Vec<u8>,
    flag_b_size: usize,
    exp_flag: u16,
    start_x: usize,
    start_y: usize,
    width: usize,
    height: usize,
    palette: Vec<(u8, u8, u8)>,
    data_offset: usize,
}

fn parse_header(data: &[u8]) -> Result<MakiHeader, Error> {
    let mut reader = ByteCursor::new(data, 0);
    let header = reader.read_bytes(8)?;
    let text = reader.read_bytes(24)?.to_vec();
    let mode = match std::str::from_utf8(header).unwrap_or("") {
        "MAKI01A " => 2,
        "MAKI01B " => 4,
        _ => return Err(err(ImgErrorKind::IllegalData, "Not a MAKI image")),
    };

    let flag_b_size = reader.read_u16_be()? as usize;
    let _pixel_a_size = reader.read_u16_be()?;
    let _pixel_b_size = reader.read_u16_be()?;
    let exp_flag = reader.read_u16_be()?;
    let start_x = reader.read_u16_be()? as usize;
    let start_y = reader.read_u16_be()? as usize;
    let width = reader.read_u16_be()? as usize;
    let height = reader.read_u16_be()? as usize;

    let palette_bytes = reader.read_bytes(48)?;
    let mut palette = Vec::with_capacity(16);
    for i in 0..16 {
        palette.push((
            palette_bytes[i * 3 + 1],
            palette_bytes[i * 3],
            palette_bytes[i * 3 + 2],
        ));
    }

    Ok(MakiHeader {
        mode,
        text,
        flag_b_size,
        exp_flag,
        start_x,
        start_y,
        width,
        height,
        palette,
        data_offset: reader.tell(),
    })
}

pub fn decode<B: BinaryReader>(
    reader: &mut B,
    option: &mut DecodeOptions,
) -> Result<Option<ImgWarnings>, Error> {
    let data = read_all(reader)?;
    let header = parse_header(&data)?;
    if header.width == 0 || header.height == 0 {
        return Err(err(
            ImgErrorKind::IllegalData,
            "MAKI dimensions must be non-zero",
        ));
    }
    let mut cursor = ByteCursor::new(&data, header.data_offset);
    let ll = if (header.exp_flag & 0x01) != 0 {
        header.height.min(200)
    } else {
        header.height
    };

    let flag_a_size = (header.width * ll) / 256;
    let flag_a = cursor.read_bytes(flag_a_size)?.to_vec();
    let flag_b = cursor.read_bytes(header.flag_b_size)?.to_vec();

    let matrix_stride = header.width >> 4;
    let matrix_len = matrix_stride
        .checked_mul(ll)
        .ok_or_else(|| err(ImgErrorKind::IllegalData, "MAKI matrix size overflow"))?;
    let mut matrix = vec![0u8; matrix_len];
    let mut w = 0usize;
    let mut s = 0usize;
    let mut p = 0usize;

    for _ in (0..ll).step_by(4) {
        let mut t = s;
        for _ in (0..matrix_stride).step_by(4) {
            for mut i in [0x80u8, 0x20, 0x08, 0x02] {
                if p >= flag_a.len() {
                    break;
                }
                if (flag_a[p] & i) != 0 && w + 1 < flag_b.len() {
                    matrix[t] = flag_b[w] & 0xf0;
                    matrix[t + matrix_stride] = (flag_b[w] << 4) & 0xf0;
                    w += 1;
                    matrix[t + matrix_stride * 2] = flag_b[w] & 0xf0;
                    matrix[t + matrix_stride * 3] = (flag_b[w] << 4) & 0xf0;
                    w += 1;
                } else {
                    matrix[t] = 0;
                    matrix[t + matrix_stride] = 0;
                    matrix[t + matrix_stride * 2] = 0;
                    matrix[t + matrix_stride * 3] = 0;
                }
                i >>= 1;
                if (flag_a[p] & i) != 0 && w + 1 < flag_b.len() {
                    matrix[t] |= (flag_b[w] >> 4) & 0x0f;
                    matrix[t + matrix_stride] |= flag_b[w] & 0x0f;
                    w += 1;
                    matrix[t + matrix_stride * 2] |= (flag_b[w] >> 4) & 0x0f;
                    matrix[t + matrix_stride * 3] |= flag_b[w] & 0x0f;
                    w += 1;
                }
                t += 1;
            }
            p += 1;
        }
        s += matrix_stride * 4;
    }

    let mut lines = vec![vec![0u8; header.width]; 5];
    let pixel_count = header
        .width
        .checked_mul(header.height)
        .ok_or_else(|| err(ImgErrorKind::IllegalData, "MAKI pixel buffer size overflow"))?;
    let mut pixels = vec![0u8; pixel_count];
    let mut matrix_index = 0usize;

    for y in 0..ll {
        let mut x = 0usize;
        while x < header.width && matrix_index < matrix.len() {
            let value = matrix[matrix_index];
            for bit in (0..8).rev() {
                if x >= header.width {
                    break;
                }
                if ((value >> bit) & 1) != 0 {
                    let data = cursor.read_u8().unwrap_or(0);
                    lines[0][x] = ((data >> 4) & 0x0f) ^ lines[header.mode][x];
                    x += 1;
                    if x < header.width {
                        lines[0][x] = (data & 0x0f) ^ lines[header.mode][x];
                        x += 1;
                    }
                } else {
                    lines[0][x] = lines[header.mode][x];
                    x += 1;
                    if x < header.width {
                        lines[0][x] = lines[header.mode][x];
                        x += 1;
                    }
                }
            }
            matrix_index += 1;
        }

        let row = y * header.width;
        if row + header.width > pixels.len() {
            return Err(err(
                ImgErrorKind::IllegalData,
                "MAKI row output is out of range",
            ));
        }
        pixels[row..row + header.width].copy_from_slice(&lines[0]);
        lines.rotate_right(1);
    }

    option
        .drawer
        .set_metadata("Format", DataMap::Ascii("MAKI".to_string()))?;
    option
        .drawer
        .set_metadata("width", DataMap::UInt(header.width as u64))?;
    option
        .drawer
        .set_metadata("height", DataMap::UInt(header.height as u64))?;
    option
        .drawer
        .set_metadata("start x", DataMap::UInt(header.start_x as u64))?;
    option
        .drawer
        .set_metadata("start y", DataMap::UInt(header.start_y as u64))?;
    option
        .drawer
        .set_metadata("comment", DataMap::SJISString(header.text))?;

    draw_indexed(
        option,
        header.width,
        header.height,
        &pixels,
        &header.palette,
    )?;
    Ok(None)
}