325 lines
9.5 KiB
Rust
325 lines
9.5 KiB
Rust
//! QuickDraw PICT format loader.
|
|
|
|
use generic_array::*;
|
|
|
|
use durandal::image::*;
|
|
use durandal::bin::*;
|
|
|
|
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;
|
|
|
|
/// Read a colorTable structure.
|
|
fn get_clut(b: &[u8]) -> Vec<Color>
|
|
{
|
|
// = b_u32b(&b[ ..4]); ctSeed
|
|
let dev = b_u16b(&b[4..6]) & 0x8000 != 0; // ctFlags
|
|
let num = b_u16b(&b[6..8]) as usize + 1; // ctSize
|
|
let mut map = Vec::new();
|
|
|
|
map.resize(num, Color{r: 0, g: 0, b: 0, a: 0});
|
|
|
|
for i in 0..num
|
|
{
|
|
let p = 8 + i * 8;
|
|
let n = (b_u16b(&b[p ..p+2]) & 0xff) as usize;
|
|
let r = (b_u16b(&b[p+2..p+4]) >> 8 ) as u8;
|
|
let g = (b_u16b(&b[p+4..p+6]) >> 8 ) as u8;
|
|
let b = (b_u16b(&b[p+6..p+8]) >> 8 ) as u8;
|
|
|
|
// with device mapping, we ignore the index entirely
|
|
map[if dev {i} else {n}] = Color{r, g, b, a: 255};
|
|
}
|
|
|
|
map
|
|
}
|
|
|
|
/// Read a sequence of packed RLE data.
|
|
fn read_rle_data<F, N>(cmp: bool, len: usize, o: &mut Vec<u8>, mut rd: F)
|
|
where F: FnMut() -> GenericArray<u8, N>,
|
|
N: ArrayLength<u8>
|
|
{
|
|
if cmp
|
|
{
|
|
let d = rd();
|
|
for _ in 0..len {for v in d.iter() {o.push(*v)}}
|
|
}
|
|
else
|
|
{for _ in 0..len {let d = rd(); for v in d.iter() {o.push(*v)}}}
|
|
}
|
|
|
|
/// Read run-length encoded data.
|
|
fn read_rle(b: &[u8], pt: usize, ln: bool) -> Result<(Vec<u8>, usize), &str>
|
|
{
|
|
let mut p = 0;
|
|
let mut o = Vec::with_capacity(pt);
|
|
let sz = if pt > 250 {(b_u16b(&b[0..2]) 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() == pt {Ok((o, p))}
|
|
else {Err("incorrect size for compressed scanline")}
|
|
}
|
|
|
|
/// Expand packed pixel data based on bit depth.
|
|
fn expand_data(b: Vec<u8>, depth: u16) -> Result<Vec<u8>, &'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<Image, &str>
|
|
{
|
|
let mut p = if !packed {4} else {0}; // baseAddr
|
|
|
|
let (w, h) = (im.w(), im.h());
|
|
|
|
let pf = b_u16b(&b[p ..p+2]); // rowBytes
|
|
let yb = b_u16b(&b[p+ 2..p+ 4]) as usize; // Bounds
|
|
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; // 〃
|
|
// = b_u16b(&b[p+10..p+12]); pmVersion
|
|
let pack = b_u16b(&b[p+12..p+14]); // packType
|
|
// = b_u32b(&b[p+14..p+18]); packSize
|
|
// = b_u32b(&b[p+18..p+22]); hRes
|
|
// = b_u32b(&b[p+22..p+26]); vRes
|
|
// = b_u16b(&b[p+26..p+28]); pixelType
|
|
let dept = b_u16b(&b[p+28..p+30]); // pixelSize
|
|
// = b_u16b(&b[p+30..p+32]); cmpCount
|
|
// = b_u16b(&b[p+32..p+34]); cmpSize
|
|
// = b_u32b(&b[p+34..p+38]); planeBytes
|
|
// = b_u32b(&b[p+38..p+42]); pmTable
|
|
// = b_u32b(&b[p+42..p+46]); pmReserved
|
|
|
|
p += 46;
|
|
|
|
if pf & 0x8000 == 0 {return Err("PICT1 not supported")}
|
|
if xe - xb != w || ye - yb != h {return Err("image bounds are incorrect")}
|
|
|
|
let map = if packed {get_clut(&b[p..])} else {Vec::new()};
|
|
let pt = (pf & 0x3fff) as usize;
|
|
let rle = pack == PACK_DEFAULT ||
|
|
(pack == PACK_RLE16 && dept == 16) ||
|
|
(pack == PACK_RLE32 && dept == 32);
|
|
|
|
p += 18 + if packed {8 + map.len() * 8} else {0}; // srcRect, dstRect, mode
|
|
|
|
if clip {let sz = b_u16b(&b[p..p+2]) as usize; p += sz} // maskRgn
|
|
|
|
match dept {
|
|
1 | 2 | 4 | 8 =>
|
|
// uncompressed 8-bit colormap indices
|
|
if pt < 8 && dept == 8
|
|
{
|
|
for _ in 0..h {
|
|
for _ in 0..w
|
|
{im.cr.push(map[b[(p, p += 1).0] as usize].clone())}
|
|
}
|
|
|
|
Ok(im)
|
|
}
|
|
|
|
// RLE compressed 1, 2, 4 or 8 bit colormap indices
|
|
else if rle
|
|
{
|
|
for _ in 0..h
|
|
{
|
|
let (d, pp) = read_rle(&b[p..], pt, false)?;
|
|
let d = if dept < 8 {expand_data(d, dept)?} else {d};
|
|
|
|
p += pp;
|
|
|
|
for x in 0..w {im.cr.push(map[d[x] as usize].clone())}
|
|
}
|
|
|
|
Ok(im)
|
|
}
|
|
|
|
// invalid
|
|
else {Err("invalid configuration")},
|
|
16 =>
|
|
// uncompressed R5G5B5
|
|
if pt < 8 || pack == PACK_NONE
|
|
{
|
|
for _ in 0..h {
|
|
for _ in 0..w
|
|
{im.cr.push(Color::from_r5g5b5(b_u16b((&b[p..p+2], p += 2).0)))}
|
|
}
|
|
|
|
Ok(im)
|
|
}
|
|
|
|
// RLE compressed R5G5B5
|
|
else if rle
|
|
{
|
|
for _ in 0..h
|
|
{
|
|
let (d, pp) = read_rle(&b[p..], pt, true)?;
|
|
|
|
p += pp;
|
|
|
|
for x in 0..w
|
|
{im.cr.push(Color::from_r5g5b5(b_u16b(&d[x*2..x*2+2])))}
|
|
}
|
|
|
|
Ok(im)
|
|
}
|
|
|
|
// invalid
|
|
else {Err("invalid configuration")},
|
|
32 =>
|
|
// uncompressed RGB8 or XRGB8
|
|
if pt < 8 || pack == PACK_NONE || pack == PACK_NOPAD
|
|
{
|
|
for _ in 0..h {
|
|
for _ in 0..w
|
|
{
|
|
if pack != 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)
|
|
}
|
|
|
|
// RLE compressed RGB8
|
|
else if rle
|
|
{
|
|
let pt = pt - w; // remove padding byte from pitch
|
|
for _ in 0..h
|
|
{
|
|
let (d, pp) = read_rle(&b[p..], pt, 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)
|
|
}
|
|
|
|
// invalid
|
|
else {Err("invalid configuration")},
|
|
_ => Err("invalid bit depth")
|
|
}
|
|
}
|
|
|
|
/// Process a CompressedQuickTime operation.
|
|
fn read_quicktime_c(_im: Image, _b: &[u8]) -> Result<Image, &str>
|
|
{Err("compressed quicktime format not implemented")}
|
|
|
|
/// Load a PICT image.
|
|
pub fn load_pict(b: &[u8]) -> Result<Image, &str>
|
|
{
|
|
// size = b_u16b(&b[0.. 2]);
|
|
// top = b_u16b(&b[2.. 4]);
|
|
// left = b_u16b(&b[4.. 6]);
|
|
let h = b_u16b(&b[6.. 8]) as usize;
|
|
let w = 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).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 => 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
|
|
0x0001 => p += (b_u16b(&b[p ..p+2]) & !1) as usize, // Clip
|
|
0x00a1 => p += (b_u16b(&b[p+2..p+4]) & !1) as usize + 2, // LongComment
|
|
0x100..=
|
|
0x7fff => p += (op >> 8) as usize * 2, // Reserved
|
|
_ => return Err("invalid op in PICT")
|
|
}
|
|
}
|
|
|
|
Err("no image in data")
|
|
}
|