Maraiah/source/marathon/png.rs

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