Maraiah/src/marathon/pict.rs

440 lines
11 KiB
Rust

//! 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)>
{
read_data! {
36, BE in b =>
pt_fl = u16[0];
top = u16[2] as usize;
left = u16[4] as usize;
bottom = u16[6] as usize;
right = u16[8] as usize;
pack_t = u16[12];
depth = u16[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(&b[p..])?;
p += sz;
Some(clut)
} else {
None
};
p += 18; // srcRect, dstRect, mode
if clip {
p += 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<Image8>
{
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 = 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(&b[p..], hdr.pitch, false)?;
let d = if hdr.depth < 8 {
expand_data(d, hdr.depth)?
} else {
d
};
p += pp;
for &idx in &d {
im.cr
.push(ok!(clut.get(idx as usize), "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<Image8>
{
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() {
let cr = u16b(&b[p..]);
im.cr.push(r5g5b5_to_rgb8(cr));
p += 2;
}
}
Ok(im)
} else if hdr.rle {
// RLE compressed R5G5B5
for _ in 0..im.h() {
let (d, pp) = read_rle(&b[p..], hdr.pitch, true)?;
p += pp;
for x in 0..im.w() {
let cr = u16b(&d[x * 2..]);
im.cr.push(r5g5b5_to_rgb8(cr));
}
}
Ok(im)
} else {
bail!("invalid configuration")
}
}
/// Reads a RGB8 `PixMap`.
fn read_pm_32(mut im: Image8, b: &[u8], hdr: Header) -> ResultS<Image8>
{
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;
}
read_data! {
p + 3, BE in b =>
r = u8[p];
g = u8[p + 1];
b = u8[p + 2];
}
im.cr.push(Color8::new(r, g, b));
p += 3;
}
}
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(&b[p..], pitch, false)?;
p += pp;
for x in 0..im.w() {
let r = d[x + im.w()];
let g = d[x + im.w() * 2];
let b = 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<Image8>
{
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"),
}
}
/// Load a `PICT` image.
pub fn load_pict(b: &[u8]) -> ResultS<Image8>
{
read_data! {
10, BE in b =>
h = u16[6] as usize;
w = u16[8] as usize;
}
let im = Image8::new(w, h);
let mut p = 10; // size of header
while p < b.len() {
read_data! {
p + 2, BE in b =>
op = u16[p];
}
p += 2;
match op {
0x0098 => {
// PackBitsRect
return read_pm_area(im, &b[p..], true, false);
}
0x0099 => {
// PackBitsRgn
return read_pm_area(im, &b[p..], true, true);
}
0x009a => {
// DirectBitsRect
return read_pm_area(im, &b[p..], false, false);
}
0x009b => {
// DirectBitsRgn
return read_pm_area(im, &b[p..], false, true);
}
0x8200 => {
// CompressedQuickTime
unimplemented!();
}
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 += (u16b(&b[p.. ]) & !1) as usize, // Clip
0x00a1 => p += (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<Color8>, usize)>
{
read_data! {
8, BE in b =>
dev = u16[4];
num = u16[6] as usize;
}
let dev = dev & 0x8000 != 0;
let num = num + 1;
let mut p = 8;
let mut clut = vec![Color8::new(0, 0, 0); num];
for i in 0..num {
read_data! {
p + 8, BE in b =>
n = u16[p] as usize;
r = u8[p + 2];
g = u8[p + 4];
b = u8[p + 6];
}
// with device mapping, we ignore the index entirely
let n = if dev {i} else {n};
*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<u8>, usize)>
{
let mut p = 0;
let mut o = Vec::with_capacity(pitch);
let sz = if pitch > 250 {
(u16b(b) as usize + 2, p += 2).0
} else {
(b[0] as usize + 1, p += 1).0
};
while p < sz {
let szf = 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<F, N>(cmp: bool, len: usize, out: &mut Vec<u8>, mut read: F)
where F: FnMut() -> GenericArray<u8, N>,
N: ArrayLength<u8>
{
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<u8>, depth: u16) -> ResultS<Vec<u8>>
{
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) & 0xF_u8);}}
2 => {for i in (0..=3).rev() {o.push(ch >> (i * 2) & 0x3_u8);}}
1 => {for i in (0..=7).rev() {o.push(ch >> i & 0x1_u8);}}
_ => bail!("invalid bit depth"),
}
}
Ok(o)
}
struct Header
{
pitch: usize,
pack_t: PackType,
depth: u16,
clut: Option<Vec<Color8>>,
rle: bool,
}
c_enum! {
#[derive(PartialEq)]
enum PackType: u16
{
0 => Default,
1 => None,
2 => NoPad,
3 => Rle16,
4 => Rle32,
}
}
// EOF