resource-fork-types 0.1.1

Support for reading common resource fork types in rust
Documentation
use binrw::{BinRead, BinReaderExt};
use image::{ImageBuffer, Rgba};
use macintosh_utils::Point;
use pict::shared::{ColorTable, PixMap};
use resource_fork::Resource;

#[derive(Resource, BinRead, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[resource(code = "CURS")]
#[br(big)]
pub struct Cursor {
    pixels: [u8; Self::DATA_LEN],
    mask: [u8; Self::DATA_LEN],
    hotspot: (u16, u16),
}

impl Cursor {
    pub const DATA_LEN: usize = Self::HEIGHT * Self::WIDTH / 8;
    pub const HEIGHT: usize = 16;
    pub const WIDTH: usize = 16;

    pub const fn width(&self) -> usize {
        Self::WIDTH
    }

    pub const fn height(&self) -> usize {
        Self::HEIGHT
    }

    pub fn hotspot_x(&self) -> u16 {
        self.hotspot.0
    }

    pub fn hotspot_y(&self) -> u16 {
        self.hotspot.1
    }

    pub fn pixels(&self) -> &[u8; Self::DATA_LEN] {
        &self.pixels
    }

    pub fn mask(&self) -> &[u8; Self::DATA_LEN] {
        &self.mask
    }
}

#[derive(Debug, BinRead)]
#[br(big, repr=u16)]
pub enum ColorCursorType {
    Monochrome = 0x8000,
    Color = 0x8001,
}

#[derive(Debug, BinRead)]
#[br(big)]
pub struct ColorCursorHeader {
    pub cursor_type: ColorCursorType,
    pub pixmap_offset: u32,
    pub pixel_data_offset: u32,
    pub expanded_size: u32,
    pub expanded_depth: u16,
    pub unused: u32,
    pub bitmap_and_mask: [u8; 0x40],
    pub hotspot: Point,
    pub color_table_offset: u32,
    pub cursor_id: u32,
}

#[derive(Debug, Resource)]
#[resource(code = "crsr", include_size = true)]
pub struct ColorCursor {
    pub header: ColorCursorHeader,
    pub hotspot: Point,
    pub image: image::ImageBuffer<image::Rgba<u8>, Vec<u8>>,
}

impl BinRead for ColorCursor {
    type Args<'a> = (usize,);

    fn read_options<R: std::io::Read + std::io::Seek>(
        reader: &mut R,
        _endian: binrw::Endian,
        _args: Self::Args<'_>,
    ) -> binrw::BinResult<Self> {
        let offset = reader.stream_position()?;
        let header: ColorCursorHeader = reader.read_be()?;

        let width = 16u32;
        let height = 16u32;
        let bytes_per_row = (width >> 3) as usize;
        let image_byte_count = ((width >> 3) * height) as usize;

        let mut mask_image = image::ImageBuffer::new(width, height);
        for y in 0..height {
            for x in (0..width).step_by(8) {
                let mut row =
                    header.bitmap_and_mask[y as usize * bytes_per_row + (x >> 3) as usize];
                let mut mask_row = header.bitmap_and_mask
                    [y as usize * bytes_per_row + (x >> 3) as usize + image_byte_count];
                let z_limit = if (x + 8) <= width { 8 } else { width - x };
                for z in 0..z_limit {
                    let value: u8 = if (row & 0x80) != 0 { 0 } else { 0xff };
                    let mask_value: u8 = if (mask_row & 0x80) != 0 { 0xff } else { 0x00 };
                    row <<= 1;
                    mask_row <<= 1;
                    *mask_image.get_pixel_mut(x + z, y) =
                        image::Rgba([value, value, value, mask_value]);
                }
            }
        }

        reader.seek(std::io::SeekFrom::Start(
            offset + header.pixmap_offset as u64 + 4,
        ))?;

        let high_byte_and_row_flags: u8 = reader.read_be()?;
        let pix_map: PixMap = reader.read_be_args((high_byte_and_row_flags,))?;

        reader.seek(std::io::SeekFrom::Start(offset + pix_map.pm_table as u64))?;
        let color_table: ColorTable = reader.read_be()?;

        reader.seek(std::io::SeekFrom::Start(
            offset + header.pixel_data_offset as u64,
        ))?;
        let mut pixels =
            vec![0u8; pix_map.bytes_per_row() as usize * pix_map.bounds.height() as usize];
        reader.read_exact(&mut pixels)?;

        let mut image: ImageBuffer<Rgba<u8>, Vec<u8>> =
            pict::drawing_context::decode_pixmap(&pix_map, &color_table, &pixels);
        for (x, y, px) in image.enumerate_pixels_mut() {
            let [r, g, b, _] = px.0;
            let [_, _, _, a] = mask_image.get_pixel(x, y).0;

            *px = image::Rgba([r, g, b, a]);
        }

        Ok(Self {
            hotspot: header.hotspot,
            image,
            header,
        })
    }
}