Maraiah/src/durandal/pict.rs

337 lines
9.5 KiB
Rust

//! 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<Color>,
}
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<T, F>(b: &[u8], long: bool, rd: F) -> Vec<T>
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<u8>
{read_rle(b, long, |p| (b[*p], *p += 1).0)}
fn read_rle16(b: &[u8], long: bool) -> Vec<u16>
{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<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 {/*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<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 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")
}