415 lines
11 KiB
Rust
415 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)>
|
|
{
|
|
let pt_fl = c_u16b(b, 0)?;
|
|
let top = c_u16b(b, 2)? as usize;
|
|
let left = c_u16b(b, 4)? as usize;
|
|
let bottom = c_u16b(b, 6)? as usize;
|
|
let right = c_u16b(b, 8)? as usize;
|
|
let pack_t = c_u16b(b, 12)?;
|
|
let depth = c_u16b(b, 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(c_data(b, p..)?)?;
|
|
p += sz;
|
|
Some(clut)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
p += 18; // srcRect, dstRect, mode
|
|
|
|
if clip {
|
|
p += c_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 = c_byte(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(c_data(b, p..)?, hdr.pitch, false)?;
|
|
let d = if hdr.depth < 8 {expand_data(d, hdr.depth)?} else {d};
|
|
|
|
p += pp;
|
|
|
|
for x in 0..im.w() {
|
|
let idx = c_byte(&d, x)? as usize;
|
|
im.cr.push(ok!(clut.get(idx), "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() {
|
|
im.cr.push(r5g5b5_to_rgb8(c_u16b(b, p)?));
|
|
p += 2;
|
|
}
|
|
}
|
|
|
|
Ok(im)
|
|
} else if hdr.rle {
|
|
// RLE compressed R5G5B5
|
|
for _ in 0..im.h() {
|
|
let (d, pp) = read_rle(c_data(b, p..)?, hdr.pitch, true)?;
|
|
|
|
p += pp;
|
|
|
|
for x in 0..im.w() {
|
|
im.cr.push(r5g5b5_to_rgb8(c_u16b(&d, x * 2)?));
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
let r = c_byte(b, p)?;
|
|
let g = c_byte(b, p + 1)?;
|
|
let b = c_byte(b, p + 2)?;
|
|
p += 3;
|
|
im.cr.push(Color8::new(r, g, b));
|
|
}
|
|
}
|
|
|
|
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(c_data(b, p..)?, pitch, false)?;
|
|
|
|
p += pp;
|
|
|
|
for x in 0..im.w() {
|
|
let r = c_byte(&d, x + im.w())?;
|
|
let g = c_byte(&d, x + im.w() * 2)?;
|
|
let b = c_byte(&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"),
|
|
}
|
|
}
|
|
|
|
/// Process a CompressedQuickTime operation.
|
|
fn read_quicktime_c(_im: Image8, _b: &[u8]) -> ResultS<Image8>
|
|
{
|
|
unimplemented!()
|
|
}
|
|
|
|
/// Load a PICT image.
|
|
pub fn load_pict(b: &[u8]) -> ResultS<Image8>
|
|
{
|
|
let h = c_u16b(b, 6)? as usize;
|
|
let w = c_u16b(b, 8)? as usize;
|
|
let im = Image8::new(w, h);
|
|
|
|
let mut p = 10; // size of header
|
|
|
|
while p < b.len() {
|
|
let op = c_u16b(b, p)?;
|
|
p += 2;
|
|
|
|
match op {
|
|
0x0098 => {
|
|
// PackBitsRect
|
|
return read_pm_area(im, c_data(b, p..)?, true, false);
|
|
}
|
|
0x0099 => {
|
|
// PackBitsRgn
|
|
return read_pm_area(im, c_data(b, p..)?, true, true);
|
|
}
|
|
0x009a => {
|
|
// DirectBitsRect
|
|
return read_pm_area(im, c_data(b, p..)?, false, false);
|
|
}
|
|
0x009b => {
|
|
// DirectBitsRgn
|
|
return read_pm_area(im, c_data(b, p..)?, false, true);
|
|
}
|
|
0x8200 => {
|
|
// CompressedQuickTime
|
|
return read_quicktime_c(im, c_data(b, p..)?);
|
|
}
|
|
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 += (c_u16b(b, p )? & !1) as usize, // Clip
|
|
0x00a1 => p += (c_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)>
|
|
{
|
|
let dev = c_u16b(b, 4)? & 0x8000 != 0;
|
|
let num = c_u16b(b, 6)? as usize + 1;
|
|
|
|
let mut p = 8;
|
|
let mut clut = vec![Color8::new(0, 0, 0); num];
|
|
|
|
for i in 0..num {
|
|
// with device mapping, we ignore the index entirely
|
|
let n = if !dev {c_byte(b, p + 1)? as usize} else {i};
|
|
let r = c_byte(b, p + 2)?;
|
|
let g = c_byte(b, p + 4)?;
|
|
let b = c_byte(b, p + 6)?;
|
|
|
|
if n >= clut.len() {
|
|
bail!("bad clut index");
|
|
}
|
|
|
|
*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 {
|
|
(c_u16b(b, 0)? as usize + 2, p += 2).0
|
|
} else {
|
|
(c_byte(b, 0)? as usize + 1, p += 1).0
|
|
};
|
|
|
|
while p < sz {
|
|
let szf = c_byte(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) & 0xFu8);}}
|
|
2 => {for i in (0..=3).rev() {o.push(ch >> (i * 2) & 0x3u8);}}
|
|
1 => {for i in (0..=7).rev() {o.push(ch >> i & 0x1u8);}}
|
|
_ => 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
|