335 lines
6.9 KiB
Rust
335 lines
6.9 KiB
Rust
//! Portable Network Graphics loader.
|
|
|
|
use crate::{durandal::{bin::*, cksum, err::*, image},
|
|
marathon::defl};
|
|
|
|
pub fn load_png(b: &[u8]) -> ResultS<image::Image16>
|
|
{
|
|
read_data! {
|
|
8, BE in b =>
|
|
magic = u8[0..8] slice;
|
|
}
|
|
|
|
if magic != b"\x89PNG\r\n\x1A\n" {
|
|
bail!("invalid magic number");
|
|
}
|
|
|
|
let mut p = 8;
|
|
|
|
// header chunk always comes first
|
|
let (len, cnk) = load_chunk(&b[p..])?;
|
|
p += len;
|
|
|
|
if cnk.typ != b"IHDR" {
|
|
bail!("IHDR not first chunk");
|
|
}
|
|
|
|
let hdr = load_cnk_ihdr(cnk)?;
|
|
|
|
let mut pal = None;
|
|
let mut bmp = Vec::new();
|
|
|
|
// loop over all the chunks, mainly grabbing IDAT chunks
|
|
loop {
|
|
let (len, cnk) = load_chunk(&b[p..])?;
|
|
p += len;
|
|
|
|
match &cnk.typ.0 {
|
|
b"PLTE" => pal = Some(load_cnk_plte(cnk)?),
|
|
b"IDAT" => bmp.extend_from_slice(&cnk.dat),
|
|
b"IEND" => break,
|
|
_ => {
|
|
if !cnk.aux {
|
|
bail!("no handler for vital chunk");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// inflate the bitmap
|
|
let bmp = &bmp[defl::load_zlib_header(&bmp)?..];
|
|
let bmp = defl::load_deflate(bmp)?.1;
|
|
|
|
let im = interlaced_adam7(&hdr, &bmp)?;
|
|
|
|
dbg!(im);
|
|
|
|
unimplemented!();
|
|
}
|
|
|
|
pub fn interlaced_adam7(hdr: &Header, i_bmp: &[u8]) -> ResultS<image::Image16>
|
|
{
|
|
const INCR_X: [usize; 7] = [8, 8, 4, 4, 2, 2, 1];
|
|
const INCR_Y: [usize; 7] = [8, 8, 8, 4, 4, 2, 2];
|
|
const BEGN_X: [usize; 7] = [0, 4, 0, 2, 0, 1, 0];
|
|
const BEGN_Y: [usize; 7] = [0, 0, 4, 0, 2, 0, 1];
|
|
|
|
let mut im = image::Image16::new_empty(hdr.width, hdr.heigh);
|
|
let mut p = 0;
|
|
|
|
for pass in 0..7 {
|
|
let w = hdr.width / INCR_X[pass];
|
|
let h = hdr.heigh / INCR_Y[pass];
|
|
|
|
if w == 0 || h == 0 {
|
|
continue;
|
|
}
|
|
|
|
let scanl = std::cmp::max(w * usize::from(hdr.pbits), 8) / 8 + 1;
|
|
|
|
let pp = p;
|
|
p += scanl * h;
|
|
|
|
let mut last = Vec::new();
|
|
let block = ok!(i_bmp.get(pp..p), "not enough data")?;
|
|
|
|
for y in 0..h {
|
|
let beg = y * scanl;
|
|
let end = beg + scanl;
|
|
|
|
let llin = if last.is_empty() {None} else {Some(last.as_slice())};
|
|
|
|
let line = &block[beg..end];
|
|
let line = unfilter_line(hdr, line, llin)?;
|
|
|
|
last = line;
|
|
}
|
|
}
|
|
|
|
Ok(im)
|
|
}
|
|
|
|
pub fn unfilter_line(hdr: &Header, line: &[u8], last: Option<&[u8]>)
|
|
-> ResultS<Vec<u8>>
|
|
{
|
|
let filt = FilterType::from_repr(line[0])?;
|
|
let line = &line[1..];
|
|
|
|
let mut out = line.to_vec();
|
|
|
|
let pbytes = usize::from(std::cmp::max(hdr.pbits / 8, 1));
|
|
|
|
match filt {
|
|
FilterType::None => {}
|
|
FilterType::Sub => {
|
|
for x in 0..line.len() {
|
|
if x > pbytes {
|
|
out[x] = out[x].wrapping_add(out[x - pbytes]);
|
|
}
|
|
}
|
|
}
|
|
FilterType::Up => {
|
|
if let Some(last) = last {
|
|
for x in 0..line.len() {
|
|
out[x] = out[x].wrapping_add(last[x]);
|
|
}
|
|
}
|
|
}
|
|
FilterType::Average => {
|
|
for x in 0..line.len() {
|
|
let mut n = 0;
|
|
if x > pbytes {
|
|
n = out[x - pbytes];
|
|
}
|
|
if let Some(last) = last {
|
|
n = n.wrapping_add(last[x]);
|
|
}
|
|
out[x] = out[x].wrapping_add(n / 2);
|
|
}
|
|
}
|
|
FilterType::Paeth => {
|
|
for x in 0..line.len() {
|
|
let u = if x > pbytes {out[x - pbytes]} else {0};
|
|
let v = if let Some(last) = last {last[x]} else {0};
|
|
let s = if let Some(last) = last {
|
|
if x > pbytes {last[x - pbytes]} else {0}
|
|
} else {
|
|
0
|
|
};
|
|
out[x] = out[x].wrapping_add(paeth_predictor(u, v, s));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(out)
|
|
}
|
|
|
|
/// The Paeth prediction function.
|
|
pub fn paeth_predictor(a: u8, b: u8, c: u8) -> u8
|
|
{
|
|
let ia = i16::from(a);
|
|
let ib = i16::from(b);
|
|
let ic = i16::from(c);
|
|
|
|
let p = ia + ib - ic;
|
|
|
|
let pa = (p - ia).abs();
|
|
let pb = (p - ib).abs();
|
|
let pc = (p - ic).abs();
|
|
|
|
if pa <= pb && pa <= pc {
|
|
a
|
|
} else if pb <= pc {
|
|
b
|
|
} else {
|
|
c
|
|
}
|
|
}
|
|
|
|
pub fn load_chunk(b: &[u8]) -> ResultS<(usize, Chunk)>
|
|
{
|
|
read_data! {
|
|
4, BE in b =>
|
|
len = u32[0] usize;
|
|
}
|
|
|
|
read_data! {
|
|
len + 12, BE in b =>
|
|
typ = Ident[4];
|
|
dat = u8[8..8 + len] slice;
|
|
crc = u32[8 + len];
|
|
}
|
|
|
|
let computed_crc = cksum::crc32(&typ.0, !0);
|
|
let computed_crc = cksum::crc32(dat, !computed_crc);
|
|
|
|
if crc != computed_crc {
|
|
bail!("bad CRC in chunk");
|
|
}
|
|
|
|
let dat = dat.to_vec();
|
|
let aux = typ.0[0] & 0x20 != 0;
|
|
let cpy = typ.0[3] & 0x20 != 0;
|
|
|
|
Ok((len + 12, Chunk{typ, dat, aux, cpy}))
|
|
}
|
|
|
|
pub fn load_cnk_ihdr(cnk: Chunk) -> ResultS<Header>
|
|
{
|
|
read_data! {
|
|
13, BE in cnk.dat =>
|
|
width = u32[0] usize;
|
|
heigh = u32[4] usize;
|
|
depth = u8[8];
|
|
ctype = u8[9];
|
|
compr = u8[10];
|
|
filtr = u8[11];
|
|
xlace = u8[12];
|
|
}
|
|
|
|
if compr != 0 {bail!("unrecognized compression method");}
|
|
if filtr != 0 {bail!("unrecognized filter method");}
|
|
if width == 0 {bail!("zero width");}
|
|
if heigh == 0 {bail!("zero height");}
|
|
|
|
let depth = BitDepth::from_repr(depth)?;
|
|
let ctype = ColorType::from_repr(ctype)?;
|
|
let xlace = XlaceMode::from_repr(xlace)?;
|
|
let pbits = depth.to_repr();
|
|
let pbits = match ctype {
|
|
ColorType::Grayscale => pbits,
|
|
ColorType::Truecolor => pbits * 3,
|
|
ColorType::Indexed => pbits,
|
|
ColorType::GrayscaleAlpha => pbits * 2,
|
|
ColorType::TruecolorAlpha => pbits * 4,
|
|
};
|
|
|
|
Ok(Header{width, heigh, pbits, depth, ctype, xlace})
|
|
}
|
|
|
|
pub fn load_cnk_plte(cnk: Chunk) -> ResultS<ColorMap>
|
|
{
|
|
let mut colors = [image::Color8::new(0, 0, 0); 256];
|
|
let mut p = 0;
|
|
|
|
for col in colors.iter_mut() {
|
|
read_data! {
|
|
p + 3, BE in cnk.dat =>
|
|
r = u8[p];
|
|
g = u8[p + 1];
|
|
b = u8[p + 2];
|
|
}
|
|
|
|
p += 3;
|
|
|
|
*col = image::Color8::new(r, g, b);
|
|
|
|
if p == cnk.dat.len() {
|
|
break;
|
|
} else if p > cnk.dat.len() {
|
|
bail!("bad PLTE length");
|
|
}
|
|
}
|
|
|
|
Ok(colors)
|
|
}
|
|
|
|
pub type ColorMap = [image::Color8; 256];
|
|
|
|
#[derive(Debug)]
|
|
pub struct Header
|
|
{
|
|
width: usize,
|
|
heigh: usize,
|
|
pbits: u8,
|
|
depth: BitDepth,
|
|
ctype: ColorType,
|
|
xlace: XlaceMode,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Chunk
|
|
{
|
|
typ: Ident,
|
|
dat: Vec<u8>,
|
|
aux: bool,
|
|
cpy: bool,
|
|
}
|
|
|
|
c_enum! {
|
|
#[derive(Debug)]
|
|
enum BitDepth: u8
|
|
{
|
|
1 => Bits1,
|
|
2 => Bits2,
|
|
4 => Bits4,
|
|
8 => Bits8,
|
|
16 => Bits16,
|
|
}
|
|
}
|
|
|
|
c_enum! {
|
|
#[derive(Debug)]
|
|
enum ColorType: u8
|
|
{
|
|
0 => Grayscale,
|
|
2 => Truecolor,
|
|
3 => Indexed,
|
|
4 => GrayscaleAlpha,
|
|
6 => TruecolorAlpha,
|
|
}
|
|
}
|
|
|
|
c_enum! {
|
|
#[derive(Debug)]
|
|
enum FilterType: u8
|
|
{
|
|
0 => None,
|
|
1 => Sub,
|
|
2 => Up,
|
|
3 => Average,
|
|
4 => Paeth,
|
|
}
|
|
}
|
|
|
|
c_enum! {
|
|
#[derive(Debug)]
|
|
enum XlaceMode: u8
|
|
{
|
|
0 => None,
|
|
1 => Adam7,
|
|
}
|
|
}
|
|
|
|
// EOF
|