//! QuickDraw PICT format loader. use crate::durandal::{bin::*, err::*, image::*}; use generic_array::*; /// Reads a PixMap header. fn read_pm_header<'a>(b: &'a [u8], pack: bool, clip: bool, im: &Image8) -> ResultS<(&'a [u8], Header)> { let pt_fl = c_u16b(b, 0)?; let top = c_u16b(b, 2)? as usize; let left = c_u16b(b, 4)? as usize; let bottom = c_u16b(b, 6)? as usize; let right = c_u16b(b, 8)? as usize; let pack_t = c_u16b(b, 12)?; let depth = c_u16b(b, 28)?; let pack_t = PackType::from_repr(pack_t)?; if pt_fl & 0x8000 == 0 { bail!("PICT1 not supported"); } if right - left != im.w() || bottom - top != im.h() { bail!("image bounds are incorrect"); } let mut p = 46; // get CLUT if packed let clut = if pack { let (clut, sz) = get_clut(c_data(b, p..)?)?; p += sz; Some(clut) } else { None }; p += 18; // srcRect, dstRect, mode if clip { p += c_u16b(b, p)? as usize; // maskRgn } let rle = pack_t == PackType::Default || pack_t == PackType::Rle16 && depth == 16 || pack_t == PackType::Rle32 && depth == 32; let pitch = (pt_fl & 0x3FFF) as usize; Ok((&b[p..], Header{pitch, pack_t, depth, clut, rle})) } /// Reads an indexed PixMap. fn read_pm_ind(mut im: Image8, b: &[u8], hdr: Header) -> ResultS { let clut = ok!(hdr.clut, "no CLUT in indexed mode")?; let mut p = 0; if hdr.pitch < 8 && hdr.depth == 8 { // uncompressed 8-bit colormap indices for _ in 0..im.h() { for _ in 0..im.w() { let idx = c_byte(b, p)? as usize; im.cr.push(ok!(clut.get(idx), "invalid index")?.clone()); p += 1; } } Ok(im) } else if hdr.rle { // RLE compressed 1, 2, 4 or 8 bit colormap indices for _ in 0..im.h() { let (d, pp) = read_rle(c_data(b, p..)?, hdr.pitch, false)?; let d = if hdr.depth < 8 {expand_data(d, hdr.depth)?} else {d}; p += pp; for x in 0..im.w() { let idx = c_byte(&d, x)? as usize; im.cr.push(ok!(clut.get(idx), "invalid index")?.clone()); } } Ok(im) } else { bail!("invalid configuration") } } /// Reads a R5G5B5 PixMap. fn read_pm_16(mut im: Image8, b: &[u8], hdr: Header) -> ResultS { let mut p = 0; if hdr.pitch < 8 || hdr.pack_t == PackType::None { // uncompressed R5G5B5 for _ in 0..im.h() { for _ in 0..im.w() { im.cr.push(r5g5b5_to_rgb8(c_u16b(b, p)?)); p += 2; } } Ok(im) } else if hdr.rle { // RLE compressed R5G5B5 for _ in 0..im.h() { let (d, pp) = read_rle(c_data(b, p..)?, hdr.pitch, true)?; p += pp; for x in 0..im.w() { im.cr.push(r5g5b5_to_rgb8(c_u16b(&d, x * 2)?)); } } Ok(im) } else { bail!("invalid configuration") } } /// Reads a RGB8 PixMap. fn read_pm_32(mut im: Image8, b: &[u8], hdr: Header) -> ResultS { let mut p = 0; if hdr.pitch < 8 || hdr.pack_t == PackType::None || hdr.pack_t == PackType::NoPad { // uncompressed RGB8 or XRGB8 for _ in 0..im.h() { for _ in 0..im.w() { if hdr.pack_t != PackType::NoPad { p += 1; } let r = c_byte(b, p)?; let g = c_byte(b, p + 1)?; let b = c_byte(b, p + 2)?; p += 3; im.cr.push(Color8::new(r, g, b)); } } Ok(im) } else if hdr.rle { // RLE compressed RGB8 let pitch = hdr.pitch - im.w(); // remove padding byte from pitch for _ in 0..im.h() { let (d, pp) = read_rle(c_data(b, p..)?, pitch, false)?; p += pp; for x in 0..im.w() { let r = c_byte(&d, x + im.w())?; let g = c_byte(&d, x + im.w() * 2)?; let b = c_byte(&d, x + im.w() * 3)?; im.cr.push(Color8::new(r, g, b)); } } Ok(im) } else { bail!("invalid configuration") } } /// Process a CopyBits operation. fn read_pm_area(im: Image8, b: &[u8], pack: bool, clip: bool) -> ResultS { let p = if pack {0} else {4}; let (b, hdr) = read_pm_header(&b[p..], pack, clip, &im)?; match hdr.depth { 1 | 2 | 4 | 8 => read_pm_ind(im, b, hdr), 16 => read_pm_16(im, b, hdr), 32 => read_pm_32(im, b, hdr), _ => bail!("invalid bit depth"), } } /// Process a CompressedQuickTime operation. fn read_quicktime_c(_im: Image8, _b: &[u8]) -> ResultS { unimplemented!() } /// Load a PICT image. pub fn load_pict(b: &[u8]) -> ResultS { let h = c_u16b(b, 6)? as usize; let w = c_u16b(b, 8)? as usize; let im = Image8::new(w, h); let mut p = 10; // size of header while p < b.len() { let op = c_u16b(b, p)?; p += 2; match op { 0x0098 => { // PackBitsRect return read_pm_area(im, c_data(b, p..)?, true, false); } 0x0099 => { // PackBitsRgn return read_pm_area(im, c_data(b, p..)?, true, true); } 0x009a => { // DirectBitsRect return read_pm_area(im, c_data(b, p..)?, false, false); } 0x009b => { // DirectBitsRgn return read_pm_area(im, c_data(b, p..)?, false, true); } 0x8200 => { // CompressedQuickTime return read_quicktime_c(im, c_data(b, p..)?); } 0x00ff => { // OpEndPic break; } // help i'm trapped in an awful metafile format from the 80s 0x0000 | // NoOp 0x001c | // HiliteMode 0x001e | // DefHilite 0x0038 | // FrameSameRect 0x0039 | // PaintSameRect 0x003a | // EraseSameRect 0x003b | // InvertSameRect 0x003c | // FillSameRect 0x8000 | // Reserved 0x8100 => (), // Reserved 0x0003 | // TxFont 0x0004 | // TxFace 0x0005 | // TxMode 0x0008 | // PnMode 0x000d | // TxSize 0x0011 | // VersionOp 0x0015 | // PnLocHFrac 0x0016 | // ChExtra 0x0023 | // ShortLineFrom 0x00a0 => p += 2, // ShortComment 0x0006 | // SpExtra 0x0007 | // PnSize 0x000b | // OvSize 0x000c | // Origin 0x000e | // FgCol 0x000f | // BkCol 0x0021 => p += 4, // LineFrom 0x001a | // RGBFgCol 0x001b | // RGBBkCol 0x001d | // TxRatio 0x0022 => p += 6, // ShortLine 0x0002 | // BkPat 0x0009 | // PnPat 0x0010 | // TxRatio 0x0020 | // Line 0x002e | // GlyphState 0x0030 | // FrameRect 0x0031 | // PaintRect 0x0032 | // EraseRect 0x0033 | // InvertRect 0x0034 => p += 8, // FillRect 0x002d => p += 10, // LineJustify 0x0001 => p += (c_u16b(b, p )? & !1) as usize, // Clip 0x00a1 => p += (c_u16b(b, p+2)? & !1) as usize + 2, // LongComment 0x100..= 0x7fff => p += (op >> 8) as usize * 2, // Reserved _ => { bail!("invalid op in PICT"); } } } Err(err_msg("no image in data")) } /// Read a colorTable structure. pub fn get_clut(b: &[u8]) -> ResultS<(Vec, usize)> { let dev = c_u16b(b, 4)? & 0x8000 != 0; let num = c_u16b(b, 6)? as usize + 1; let mut p = 8; let mut clut = vec![Color8::new(0, 0, 0); num]; for i in 0..num { // with device mapping, we ignore the index entirely let n = if !dev {c_byte(b, p + 1)? as usize} else {i}; let r = c_byte(b, p + 2)?; let g = c_byte(b, p + 4)?; let b = c_byte(b, p + 6)?; if n >= clut.len() { bail!("bad clut index"); } *ok!(clut.get_mut(n), "invalid index")? = Color8::new(r, g, b); p += 8; } Ok((clut, p)) } /// Read run-length encoded data. pub fn read_rle(b: &[u8], pitch: usize, ln: bool) -> ResultS<(Vec, usize)> { let mut p = 0; let mut o = Vec::with_capacity(pitch); let sz = if pitch > 250 { (c_u16b(b, 0)? as usize + 2, p += 2).0 } else { (c_byte(b, 0)? as usize + 1, p += 1).0 }; while p < sz { let szf = c_byte(b, p)?; let cmp = szf & 0x80 != 0; let len = if cmp {!szf + 2} else {szf + 1} as usize; p += 1; o.reserve(len); if ln { read_rle_data(cmp, len, &mut o, || (arr![u8; b[p], b[p+1]], p += 2).0); } else { read_rle_data(cmp, len, &mut o, || (arr![u8; b[p] ], p += 1).0); } } if o.len() == pitch { Ok((o, p)) } else { Err(err_msg("incorrect size for compressed scanline")) } } /// Read a sequence of packed RLE data. fn read_rle_data(cmp: bool, len: usize, out: &mut Vec, mut read: F) where F: FnMut() -> GenericArray, N: ArrayLength { if cmp { let d = read(); for _ in 0..len { for v in d.iter() { out.push(*v); } } } else { for _ in 0..len { let d = read(); for v in d.iter() { out.push(*v); } } } } /// Expand packed pixel data based on bit depth. pub fn expand_data(b: Vec, depth: u16) -> ResultS> { let mut o = Vec::with_capacity(match depth { 4 => b.len() * 2, 2 => b.len() * 4, 1 => b.len() * 8, _ => bail!("invalid bit depth"), }); for ch in b { match depth { 4 => {for i in (0..=1).rev() {o.push(ch >> (i * 4) & 0xFu8);}} 2 => {for i in (0..=3).rev() {o.push(ch >> (i * 2) & 0x3u8);}} 1 => {for i in (0..=7).rev() {o.push(ch >> i & 0x1u8);}} _ => bail!("invalid bit depth"), } } Ok(o) } struct Header { pitch: usize, pack_t: PackType, depth: u16, clut: Option>, rle: bool, } c_enum! { #[derive(PartialEq)] enum PackType: u16 { 0 => Default, 1 => None, 2 => NoPad, 3 => Rle16, 4 => Rle32, } } // EOF