use std::io::{self, Read, Seek, SeekFrom};
use std::iter::{repeat};
use super::{
Image, Info, ColFmt, ColType, error,
copy_memory, converter,
u32_from_le, i32_from_le, u16_from_le, IFRead,
};
pub fn read_info<R: Read+Seek>(reader: &mut R) -> io::Result<Info> {
let hdr = try!(read_header(reader));
Ok(Info {
w: hdr.width.abs() as usize,
h: hdr.height.abs() as usize,
ct: if let Some(mask) = hdr.dib_v3_alpha_mask {
if mask != 0 { ColType::ColorAlpha } else { ColType::Color }
} else {
ColType::Color
},
})
}
#[derive(Debug)]
struct BmpHeader {
pub file_size : u32,
pub pixel_data_offset : usize,
pub dib_size : usize,
pub width : isize,
pub height : isize,
pub planes : u16,
pub dib_v1 : Option<DibV1>,
pub dib_v2 : Option<DibV2>,
pub dib_v3_alpha_mask : Option<u32>,
pub dib_v4 : Option<DibV4>,
pub dib_v5 : Option<DibV5>,
}
#[derive(Debug)]
struct DibV1 {
pub bits_pp : usize,
pub compression : u32,
pub idat_size : usize,
pub pixels_per_meter_x : usize,
pub pixels_per_meter_y : usize,
pub palette_length : usize, pub important_color_count : u32,
}
#[derive(Debug)]
struct DibV2 {
pub red_mask : u32,
pub green_mask : u32,
pub blue_mask : u32,
}
#[derive(Debug)]
struct DibV4 {
pub color_space_type : u32,
pub color_space_endpoints : Vec<u8>, pub gamma_red : u32,
pub gamma_green : u32,
pub gamma_blue : u32,
pub intent : u32,
}
#[derive(Debug)]
struct DibV5 {
pub icc_profile_data : u32,
pub icc_profile_size : u32,
}
fn read_header<R: Read+Seek>(reader: &mut R) -> io::Result<BmpHeader> {
let mut bmp_header = [0u8; 18]; try!(reader.read_exact(&mut bmp_header[..]));
if &bmp_header[0..2] != [0x42, 0x4d] {
return error("corrupt bmp header");
}
let dib_size = u32_from_le(&bmp_header[14..18]) as usize;
let dib_version = match dib_size {
12 => 0,
40 => 1,
52 => 2,
56 => 3,
108 => 4,
124 => 5,
_ => return error("unsupported dib version"),
};
let mut dib_header: Vec<u8> = repeat(0).take(dib_size-4).collect();
try!(reader.read_exact(&mut dib_header[..]));
if 6 <= dib_version {
return error("bmp info header version 6 or above not supported")
}
Ok(BmpHeader {
file_size : u32_from_le(&bmp_header[2..6]),
pixel_data_offset : u32_from_le(&bmp_header[10..14]) as usize,
width : i32_from_le(&dib_header[0..4]) as isize,
height : i32_from_le(&dib_header[4..8]) as isize,
planes : u16_from_le(&dib_header[8..10]),
dib_size : dib_size,
dib_v1:
if 1 <= dib_version {
Some(DibV1 {
bits_pp : u16_from_le(&dib_header[10..12]) as usize,
compression : u32_from_le(&dib_header[12..16]),
idat_size : u32_from_le(&dib_header[16..20]) as usize,
pixels_per_meter_x : u32_from_le(&dib_header[20..24]) as usize,
pixels_per_meter_y : u32_from_le(&dib_header[24..28]) as usize,
palette_length : u32_from_le(&dib_header[28..32]) as usize,
important_color_count : u32_from_le(&dib_header[32..36]),
})
} else {
None
},
dib_v2:
if 2 <= dib_version {
Some(DibV2 {
red_mask : u32_from_le(&dib_header[36..40]),
green_mask : u32_from_le(&dib_header[40..44]),
blue_mask : u32_from_le(&dib_header[44..48]),
})
} else {
None
},
dib_v3_alpha_mask:
if 3 <= dib_version {
Some(u32_from_le(&dib_header[48..52]))
} else {
None
},
dib_v4:
if 4 <= dib_version {
let mut color_space_endpoints = Vec::with_capacity(36); color_space_endpoints.extend((&dib_header[56..92]).iter().map(|&b| b));
Some(DibV4 {
color_space_type : u32_from_le(&dib_header[52..56]),
color_space_endpoints : color_space_endpoints,
gamma_red : u32_from_le(&dib_header[92..96]),
gamma_green : u32_from_le(&dib_header[96..100]),
gamma_blue : u32_from_le(&dib_header[100..104]),
intent : u32_from_le(&dib_header[104..108]),
})
} else {
None
},
dib_v5:
if 5 <= dib_version {
Some(DibV5 {
icc_profile_data : u32_from_le(&dib_header[108..112]),
icc_profile_size : u32_from_le(&dib_header[112..116]),
})
} else {
None
}
})
}
const CMP_RGB: u32 = 0;
const CMP_BITS: u32 = 3;
pub fn read<R: Read+Seek>(reader: &mut R, req_fmt: ColFmt) -> io::Result<Image> {
let hdr = try!(read_header(reader));
if hdr.width < 1 || hdr.height == 0 { return error("invalid dimensions") }
if hdr.pixel_data_offset < (14 + hdr.dib_size)
|| hdr.pixel_data_offset > 0xffffff {
return error("invalid pixel data offset")
}
if hdr.planes != 1 { return error("not supported") }
let (bytes_pp, paletted, palette_length, rgb_masked) =
if let Some(ref dv1) = hdr.dib_v1 {
if 256 < dv1.palette_length { return error("ivnalid palette length") }
if dv1.bits_pp <= 8
&& (dv1.palette_length == 0 || dv1.compression != CMP_RGB) {
return error("invalid format")
}
if dv1.compression != CMP_RGB && dv1.compression != CMP_BITS {
return error("unsupported compression")
}
let rgb_masked = dv1.compression == CMP_BITS;
match dv1.bits_pp {
8 => (1, true, dv1.palette_length, rgb_masked),
24 => (3, false, dv1.palette_length, rgb_masked),
32 => (4, false, dv1.palette_length, rgb_masked),
_ => return error("not supported (dv1)"),
}
} else {
(1, true, 256, false)
};
let pe_bytes_pp = if hdr.dib_v1.is_some() { 4 } else { 3 };
fn mask_to_idx(mask: u32) -> io::Result<usize> {
match mask {
0xff00_0000 => Ok(3),
0x00ff_0000 => Ok(2),
0x0000_ff00 => Ok(1),
0x0000_00ff => Ok(0),
_ => return error("unsupported mask")
}
}
let (redi, greeni, bluei) = match (rgb_masked, hdr.dib_v2) {
(true, Some(ref dv2)) => {
(try!(mask_to_idx(dv2.red_mask)),
try!(mask_to_idx(dv2.green_mask)),
try!(mask_to_idx(dv2.blue_mask)))
}
(false, _) => { (2, 1, 0) }
_ => return error("invalid format"),
};
let (alpha_masked, alphai) =
if let Some(mask) = hdr.dib_v3_alpha_mask {
if mask == 0 { (false, 0) } else { (true, try!(mask_to_idx(mask))) }
} else {
(false, 0)
};
let (palette, mut depaletted_line) =
if paletted {
let mut palette: Vec<u8> = repeat(0).take(palette_length * pe_bytes_pp)
.collect();
try!(reader.read_exact(&mut palette[..]));
(palette, repeat(0u8).take(hdr.width as usize * pe_bytes_pp).collect())
} else {
(Vec::new(), Vec::new())
};
try!(reader.seek(SeekFrom::Start(hdr.pixel_data_offset as u64)));
let tgt_fmt = {
use super::ColFmt::*;
match req_fmt {
Y | YA | RGB | RGBA | BGR | BGRA => req_fmt,
Auto => if alpha_masked { RGBA } else { RGB },
}
};
let convert = try!(converter(ColFmt::BGRA, tgt_fmt));
let src_linesize = hdr.width as usize * bytes_pp; let src_pad = if paletted { 0 } else { 3 - ((src_linesize-1) % 4) };
let tgt_bytespp = tgt_fmt.bytes_pp();
let tgt_linesize = (hdr.width as usize * tgt_bytespp) as isize;
let (tgt_stride, mut ti): (isize, isize) =
if hdr.height < 0 {
(tgt_linesize, 0)
} else {
(-tgt_linesize, (hdr.height-1) * tgt_linesize)
};
let mut src_line_buf: Vec<u8> = repeat(0).take(src_linesize + src_pad).collect();
let mut bgra_line_buf: Vec<u8> =
if paletted { Vec::new() }
else { repeat(0).take(hdr.width as usize * 4).collect() };
let mut result: Vec<u8> =
repeat(0).take(hdr.width as usize * hdr.height.abs() as usize * tgt_bytespp)
.collect();
for _ in (0 .. hdr.height.abs()) {
try!(reader.read_exact(&mut src_line_buf[..]));
let src_line = &src_line_buf[..src_linesize];
if paletted {
let ps = pe_bytes_pp;
let mut di = 0;
for &idx in src_line {
let idx = idx as usize * ps;
copy_memory(&palette[idx .. idx+ps], &mut depaletted_line[di .. di+ps]);
if ps == 4 {
depaletted_line[di+3] = 255;
}
di += ps;
}
convert(&depaletted_line, &mut result[ti as usize..(ti+tgt_linesize) as usize]);
} else {
let mut si = 0;
let mut di = 0;
while si < src_line.len() {
bgra_line_buf[di + 0] = src_line[si + bluei];
bgra_line_buf[di + 1] = src_line[si + greeni];
bgra_line_buf[di + 2] = src_line[si + redi];
bgra_line_buf[di + 3] = if alpha_masked { src_line[si + alphai] }
else { 255 };
si += bytes_pp;
di += 4;
}
convert(&bgra_line_buf, &mut result[ti as usize..(ti+tgt_linesize) as usize]);
}
ti += tgt_stride;
}
Ok(Image {
w : hdr.width as usize,
h : hdr.height.abs() as usize,
fmt : tgt_fmt,
buf : result,
})
}