//! QuickDraw PICT format loader. use durandal::image::*; use durandal::bin::*; const PACK_DEFAULT: u16 = 0; const PACK_NONE : u16 = 1; const PACK_NOALPHA: u16 = 2; const PACK_RLE16 : u16 = 3; const PACK_RLE32 : u16 = 4; struct PixMap { pack: u16, dept: u16, rle : bool, cmap: Vec, } impl PixMap { fn empty() -> PixMap {PixMap{pack: PACK_DEFAULT, dept: 1, rle: false, cmap: Vec::new()}} fn new(b: &[u8], packed: bool) -> (usize, PixMap) { // version = b_u16b(&b[ 0.. 2]); let pack = b_u16b(&b[ 2.. 4]); // packSize = b_u32b(&b[ 4.. 8]) as usize; // horzDPI = b_u32b(&b[ 8..12]); // vertDPI = b_u32b(&b[12..16]); // pixelType = b_u16b(&b[16..18]); let dept = b_u16b(&b[18..20]); // components = b_u16b(&b[20..22]); // compDepth = b_u16b(&b[22..24]); // planeOffs = b_u32b(&b[24..28]); // colorTable = b_u32b(&b[28..32]); // reserved = b_u32b(&b[32..36]); let mut px = PixMap{ pack, dept, rle: pack == PACK_DEFAULT || (dept == 16 && pack == PACK_RLE16) || (dept == 32 && pack == PACK_RLE32), cmap: Vec::new(), }; // if it's packed we need to grab the color map if packed { // table = b_u32b(&b[36..40]); let dev = b_u16b(&b[40..42]) & 0x8000 != 0; let colo = b_u16b(&b[42..44]) as usize + 1; px.cmap.resize(colo as usize, Color{r: 0, g: 0, b: 0, a: 0}); for i in 0..colo { let p = 44 + i * 8; let n = (b_u16b(&b[p+0..p+ 2]) & 0xff) as usize; let r = (b_u16b(&b[p+2..p+ 4]) / 257) as u8; let g = (b_u16b(&b[p+6..p+ 8]) / 257) as u8; let b = (b_u16b(&b[p+8..p+10]) / 257) as u8; // with device mapping, we ignore the index entirely let n = if dev {i} else {n}; px.cmap[n] = Color{r, g, b, a: 255}; } (44 + colo * 8, px) } else {(36, px)} } } /// Read run-length encoded data. fn read_rle(b: &[u8], long: bool, rd: F) -> Vec where T: Copy, F: Fn(&mut usize) -> T { let (st, sz) = if long {(2usize, b_u16b(&b[0..2]) as usize)} else {(1usize, b[0] as usize)}; let mut o = Vec::with_capacity(sz); let mut p = st; while p < sz { // size and flags are in one byte, we interpret it as a signed integer // because it's easier to handle let szf = b[(p, p += 1).0] as i8; let sz = if szf < 0 {-szf + 1} else {szf + 1}; o.reserve(sz as usize); // either repeated or unique data if szf < 0 {let d = rd(&mut p); for _ in 0..sz { o.push(d)}} else {for _ in 0..sz {let d = rd(&mut p); o.push(d)}} } o } fn read_rle8(b: &[u8], long: bool) -> Vec {read_rle(b, long, |p| (b[*p], *p += 1).0)} fn read_rle16(b: &[u8], long: bool) -> Vec {read_rle(b, long, |p| (b_u16b(&b[*p..*p+2]), *p += 2).0)} /// Expand packed pixel data based on bit depth. fn expand_data(b: Vec, depth: u16) -> Result, &'static str> { let mut o = Vec::with_capacity(match depth { 4 => b.len() * 2, 2 => b.len() * 4, 1 => b.len() * 8, _ => return Err("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("invalid bit depth") } } Ok(o) } /// Process a CopyBits operation. fn read_bitmap_area(mut im: Image, b: &[u8], packed: bool, clip: bool) -> Result { let mut p = if !packed {/*baseAddress = b_u32b(&b[0..4]);*/ 4} else {0}; // get pitch and flags, flags are packed into the upper 2 bits let pf = b_u16b(&b[p..p+2]); let pm = pf & 0x8000 != 0; let pt = pf & 0x3fff; let (w, h) = (im.w(), im.h()); let yb = b_u16b(&b[p+2..p+ 4]) as usize; let xb = b_u16b(&b[p+4..p+ 6]) as usize; let ye = b_u16b(&b[p+6..p+ 8]) as usize; let xe = b_u16b(&b[p+8..p+10]) as usize; if xe - xb < w || ye - yb < h {return Err("image size is incorrect")} let pxm = if pm {let (pp, pxm) = PixMap::new(&b[p+10..], packed); p += pp; pxm} else {PixMap::empty()}; if clip {let sz = b_u16b(&b[p..p+2]) as usize; p += sz} match pxm.dept { 1 | 2 | 4 | 8 => // uncompressed 8-bit colormap indices if pt < 8 && pxm.dept == 8 { for y in 0..h { for x in 0..w {im[(x, y)] = pxm.cmap[b[(p, p += 1).0] as usize].clone()} } Ok(im) } // RLE compressed 1, 2, 4 or 8 bit colormap indices else if pxm.rle { for y in 0..h { let d = read_rle8(&b[p..], pt > 250); let d = if pxm.dept < 8 {expand_data(d, pxm.dept)?} else {d}; for x in 0..w {im[(x, y)] = pxm.cmap[d[x] as usize].clone()} } Ok(im) } // invalid else {Err("invalid configuration")}, 16 => // uncompressed R5G5B5 if pt < 8 || pxm.pack == PACK_NONE { for y in 0..h { for x in 0..w {im[(x, y)] = Color::from_r5g5b5(b_u16b((&b[p..p+2], p += 2).0))} } Ok(im) } // RLE compressed R5G5B5 else if pxm.rle { for y in 0..h { let d = read_rle16(&b[p..], pt > 250); for x in 0..w {im[(x, y)] = Color::from_r5g5b5(d[x])} } Ok(im) } // invalid else {Err("invalid configuration")}, 32 => // uncompressed RGB8 or ARGB8 if pt < 8 || pxm.pack == PACK_NONE || pxm.pack == PACK_NOALPHA { for y in 0..h { for x in 0..w { let a = if pxm.pack != PACK_NOALPHA {(b[p], p += 1).0} else {255}; let r = b[p+0]; let g = b[p+1]; let b = b[p+2]; p += 3; im[(x, y)] = Color{r, g, b, a}; } } Ok(im) } // RLE compressed RGB8 else if pxm.rle { for y in 0..h { let d = read_rle8(&b[p..], pt > 250); for x in 0..w { let r = d[x + w * 0]; let g = d[x + w * 1]; let b = d[x + w * 2]; im[(x, y)] = Color{r, g, b, a: 255}; } } Ok(im) } // invalid else {Err("invalid configuration")}, _ => Err("invalid bit depth") } } /// Process a CompressedQuickTime operation. fn read_quicktime_c(_im: Image, _b: &[u8]) -> Result {Err("compressed quicktime format not implemented")} /// Load a PICT image. pub fn load_pict(b: &[u8]) -> Result { // size = b_u16b(&b[0.. 2]); // top = b_u16b(&b[2.. 4]); // left = b_u16b(&b[4.. 6]); let w = b_u16b(&b[6.. 8]) as usize; let h = b_u16b(&b[8..10]) as usize; let im = Image::new(w, h); let mut p = 10; while p < b.len() { let op = b_u16b(&b[p..p+2]); p += 2; 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 => p += 2, // TxFont 0x0004 => p += 2, // TxFace 0x0005 => p += 2, // TxMode 0x0008 => p += 2, // PnMode 0x000d => p += 2, // TxSize 0x0011 => p += 2, // VersionOp 0x0015 => p += 2, // PnLocHFrac 0x0016 => p += 2, // ChExtra 0x0023 => p += 2, // ShortLineFrom 0x00a0 => p += 2, // ShortComment 0x02ff => p += 2, // Version 0x0006 => p += 4, // SpExtra 0x0007 => p += 4, // PnSize 0x000b => p += 4, // OvSize 0x000c => p += 4, // Origin 0x000e => p += 4, // FgCol 0x000f => p += 4, // BkCol 0x0021 => p += 4, // LineFrom 0x001a => p += 6, // RGBFgCol 0x001b => p += 6, // RGBBkCol 0x001d => p += 6, // TxRatio 0x0022 => p += 6, // ShortLine 0x0002 => p += 8, // BkPat 0x0009 => p += 8, // PnPat 0x0010 => p += 8, // TxRatio 0x0020 => p += 8, // Line 0x002e => p += 8, // GlyphState 0x0030 => p += 8, // FrameRect 0x0031 => p += 8, // PaintRect 0x0032 => p += 8, // EraseRect 0x0033 => p += 8, // InvertRect 0x0034 => p += 8, // FillRect 0x002d => p += 10, // LineJustify 0x0c00 => p += 24, // HeaderOp _ => if op >= 0x100 && op <= 0x7fff {p += ((op as usize & 0xff00) >> 8) * 2} else {return Err("invalid opcode in PICT")} } } Err("no image in data") }