//! QuickDraw PICT format loader. use crate::durandal::{bin::*, err::*, image::*}; use generic_array::*; const PACK_DEFAULT: u16 = 0; const PACK_NONE : u16 = 1; const PACK_NOPAD : u16 = 2; const PACK_RLE16 : u16 = 3; const PACK_RLE32 : u16 = 4; /// Process a CopyBits operation. pub fn read_bitmap_area(mut im: Image, b: &[u8], packed: bool, clip: bool) -> ResultS { let mut p = if !packed {4} else {0}; let (w, h) = (im.w(), im.h()); let pitch_fl = b.c_u16b(p )?; let top = b.c_u16b(p+ 2)? as usize; let left = b.c_u16b(p+ 4)? as usize; let bottom = b.c_u16b(p+ 6)? as usize; let right = b.c_u16b(p+ 8)? as usize; // version = b.c_u16b(p+10)?; let pack_typ = b.c_u16b(p+12)?; // pack_siz = b.c_u32b(p+14)?; // horz_dpi = b.c_u32b(p+18)?; // vert_dpi = b.c_u32b(p+22)?; // format = b.c_u16b(p+26)?; let depth = b.c_u16b(p+28)?; // comp_n = b.c_u16b(p+30)?; // comp_d = b.c_u16b(p+32)?; // planeofs = b.c_u32b(p+34)?; // clut_id = b.c_u32b(p+38)?; if pitch_fl & 0x8000 == 0 { return err_msg("PICT1 not supported"); } if right - left != w || bottom - top != h { return err_msg("image bounds are incorrect"); } p += 46; // size of header // get CLUT if packed let clut = if packed { let (clut, sz) = get_clut(&b[p..])?; p += sz; Some(clut) } else { None }; p += 18; // srcRect, dstRect, mode if clip {p += b.c_u16b(p)? as usize;} // maskRgn let rle = pack_typ == PACK_DEFAULT || (pack_typ == PACK_RLE16 && depth == 16) || (pack_typ == PACK_RLE32 && depth == 32); let pitch = (pitch_fl & 0x3fff) as usize; match depth { 1 | 2 | 4 | 8 => { let clut = clut.ok_or_else(|| err_msg_v("no clut in indexed mode"))?; if pitch < 8 && depth == 8 { // uncompressed 8-bit colormap indices for _ in 0..h { for _ in 0..w { im.cr.push(clut[b[(p, p += 1).0] as usize].clone()); } } Ok(im) } else if rle { // RLE compressed 1, 2, 4 or 8 bit colormap indices for _ in 0..h { let (d, pp) = read_rle(&b[p..], pitch, false)?; let d = if depth < 8 {expand_data(d, depth)?} else {d}; p += pp; for x in 0..w {im.cr.push(clut[d[x] as usize].clone());} } Ok(im) } else {err_msg("invalid configuration")} }, 16 => if pitch < 8 || pack_typ == PACK_NONE { // uncompressed R5G5B5 for _ in 0..h { for _ in 0..w { im.cr.push(Color::from_r5g5b5(b.c_u16b((p, p += 2).0)?)); } } Ok(im) } else if rle { // RLE compressed R5G5B5 for _ in 0..h { let (d, pp) = read_rle(&b[p..], pitch, true)?; p += pp; for x in 0..w {im.cr.push(Color::from_r5g5b5(d.c_u16b(x*2)?));} } Ok(im) } else {err_msg("invalid configuration")}, 32 => if pitch < 8 || pack_typ == PACK_NONE || pack_typ == PACK_NOPAD { // uncompressed RGB8 or XRGB8 for _ in 0..h { for _ in 0..w { if pack_typ != PACK_NOPAD {p += 1;} let (r, g, b) = (b[p], b[p+1], b[p+2]); p += 3; im.cr.push(Color{r, g, b, a: 255}); } } Ok(im) } else if rle { // RLE compressed RGB8 let pitch = pitch - w; // remove padding byte from pitch for _ in 0..h { let (d, pp) = read_rle(&b[p..], pitch, false)?; p += pp; for x in 0..w { let (r, g, b) = (d[x+w*0], d[x+w*1], d[x+w*2]); im.cr.push(Color{r, g, b, a: 255}); } } Ok(im) } else {err_msg("invalid configuration")}, _ => err_msg("invalid bit depth") } } /// Process a CompressedQuickTime operation. pub fn read_quicktime_c(_im: Image, _b: &[u8]) -> ResultS { err_msg("compressed quicktime format not implemented") } /// Load a PICT image. pub fn load_pict(b: &[u8]) -> ResultS { // size = b.c_u16b(0)?; // top = b.c_u16b(2)?; // left = b.c_u16b(4)?; let h = b.c_u16b(6)? as usize; let w = b.c_u16b(8)? as usize; let im = Image::new(w, h); let mut p = 10; // size of header while p < b.len() { let op = b.c_u16b((p, p += 2).0)?; match op { 0x0098 => return read_bitmap_area(im, &b[p..], true, false), // PackBitsRect 0x0099 => return read_bitmap_area(im, &b[p..], true, true ), // PackBitsRgn 0x009a => return read_bitmap_area(im, &b[p..], false, false), // DirectBitsRect 0x009b => return read_bitmap_area(im, &b[p..], false, true ), // DirectBitsRgn 0x8200 => return read_quicktime_c(im, &b[p..]), // CompressedQuickTime 0x00ff => break, // OpEndPic // 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 | // ShortComment 0x02ff => p += 2, // Version 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 0x0c00 => p += 24, // HeaderOp 0x0001 => p += (b.c_u16b(p )? & !1) as usize, // Clip 0x00a1 => p += (b.c_u16b(p+2)? & !1) as usize + 2, // LongComment 0x100..= 0x7fff => p += (op >> 8) as usize * 2, // Reserved _ => return err_msg("invalid op in PICT") } } err_msg("no image in data") } /// Read a colorTable structure. pub fn get_clut(b: &[u8]) -> ResultS<(Vec, usize)> { // sed = b.c_u32b(0)?; let dev = b.c_u16b(4)? & 0x8000 != 0; let num = b.c_u16b(6)? as usize + 1; let mut p = 8; let mut clut = vec![Color{r: 0, g: 0, b: 0, a: 0}; num]; for i in 0..num { // with device mapping, we ignore the index entirely let n = if !dev {b[p + 1] as usize} else {i}; let r = b[p+2]; let g = b[p+4]; let b = b[p+6]; if n >= clut.len() {return err_msg("bad clut index");} clut[n] = Color{r, g, b, a: 255}; 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 {(b.c_u16b(0)? as usize + 2, p += 2).0} else {(b[0] as usize + 1, p += 1).0}; while p < sz { let szf = b[(p, p += 1).0]; let cmp = szf & 0x80 != 0; let len = if cmp {!szf + 2} else {szf + 1} as usize; 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_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, _ => return err_msg("invalid bit depth") }); for ch in b { match depth { 4 => for i in 1..=0 {o.push(ch >> i * 4 & 0xfu8);}, // 2 nibbles 2 => for i in 3..=0 {o.push(ch >> i * 2 & 0x3u8);}, // 4 dibits 1 => for i in 7..=0 {o.push(ch >> i * 1 & 0x1u8);}, // 8 bits _ => return err_msg("invalid bit depth") } } Ok(o) } // EOF