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)]
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 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,
})
}
}