Merge pull request #1 from Robzz/port_image_code
Import tiff code from image crate
This commit is contained in:
25 files changed
+1147
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "tiff"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [
|
||||||
|
"ccgn",
|
||||||
|
"bvssvni <bvssvni@gmail.com>",
|
||||||
|
"nwin",
|
||||||
|
"TyOverby <ty@pre-alpha.com>",
|
||||||
|
"HeroicKatora",
|
||||||
|
"Calum",
|
||||||
|
"CensoredUsername <cens.username@gmail.com>",
|
||||||
|
"Robzz"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
byteorder = "1.2.*"
|
||||||
|
lzw = "0.10.*"
|
||||||
|
num-derive = "0.2.*"
|
||||||
|
num-traits = "0.2.*"
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
//! Function for reading TIFF tags
|
||||||
|
|
||||||
|
use std::io::{self, Read, Seek};
|
||||||
|
use std::collections::{HashMap};
|
||||||
|
|
||||||
|
use super::stream::{ByteOrder, SmartReader, EndianReader};
|
||||||
|
use ::{TiffError, TiffResult};
|
||||||
|
|
||||||
|
use self::Value::{Unsigned, List};
|
||||||
|
|
||||||
|
macro_rules! tags {
|
||||||
|
{$(
|
||||||
|
$tag:ident
|
||||||
|
$val:expr;
|
||||||
|
)*} => {
|
||||||
|
|
||||||
|
/// TIFF tag
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
|
pub enum Tag {
|
||||||
|
$($tag,)*
|
||||||
|
Unknown(u16)
|
||||||
|
}
|
||||||
|
impl Tag {
|
||||||
|
pub fn from_u16(n: u16) -> Tag {
|
||||||
|
$(if n == $val { Tag::$tag } else)* {
|
||||||
|
Tag::Unknown(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||||
|
tags!{
|
||||||
|
// Baseline tags:
|
||||||
|
Artist 315; // TODO add support
|
||||||
|
// grayscale images PhotometricInterpretation 1 or 3
|
||||||
|
BitsPerSample 258;
|
||||||
|
CellLength 265; // TODO add support
|
||||||
|
CellWidth 264; // TODO add support
|
||||||
|
// palette-color images (PhotometricInterpretation 3)
|
||||||
|
ColorMap 320; // TODO add support
|
||||||
|
Compression 259; // TODO add support for 2 and 32773
|
||||||
|
Copyright 33_432; // TODO add support
|
||||||
|
DateTime 306; // TODO add support
|
||||||
|
ExtraSamples 338; // TODO add support
|
||||||
|
FillOrder 266; // TODO add support
|
||||||
|
FreeByteCounts 289; // TODO add support
|
||||||
|
FreeOffsets 288; // TODO add support
|
||||||
|
GrayResponseCurve 291; // TODO add support
|
||||||
|
GrayResponseUnit 290; // TODO add support
|
||||||
|
HostComputer 316; // TODO add support
|
||||||
|
ImageDescription 270; // TODO add support
|
||||||
|
ImageLength 257;
|
||||||
|
ImageWidth 256;
|
||||||
|
Make 271; // TODO add support
|
||||||
|
MaxSampleValue 281; // TODO add support
|
||||||
|
MinSampleValue 280; // TODO add support
|
||||||
|
Model 272; // TODO add support
|
||||||
|
NewSubfileType 254; // TODO add support
|
||||||
|
Orientation 274; // TODO add support
|
||||||
|
PhotometricInterpretation 262;
|
||||||
|
PlanarConfiguration 284;
|
||||||
|
ResolutionUnit 296; // TODO add support
|
||||||
|
RowsPerStrip 278;
|
||||||
|
SamplesPerPixel 277;
|
||||||
|
Software 305;
|
||||||
|
StripByteCounts 279;
|
||||||
|
StripOffsets 273;
|
||||||
|
SubfileType 255; // TODO add support
|
||||||
|
Threshholding 263; // TODO add support
|
||||||
|
XResolution 282;
|
||||||
|
YResolution 283;
|
||||||
|
// Advanced tags
|
||||||
|
Predictor 317;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, FromPrimitive)]
|
||||||
|
pub enum Type {
|
||||||
|
BYTE = 1,
|
||||||
|
ASCII = 2,
|
||||||
|
SHORT = 3,
|
||||||
|
LONG = 4,
|
||||||
|
RATIONAL = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[allow(unused_qualifications)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Value {
|
||||||
|
//Signed(i32),
|
||||||
|
Unsigned(u32),
|
||||||
|
List(Vec<Value>)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn into_u32(self) -> TiffResult<u32> {
|
||||||
|
match self {
|
||||||
|
Unsigned(val) => Ok(val),
|
||||||
|
val => Err(TiffError::FormatError(format!(
|
||||||
|
"Expected unsigned integer, {:?} found.", val
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> {
|
||||||
|
match self {
|
||||||
|
List(vec) => {
|
||||||
|
let mut new_vec = Vec::with_capacity(vec.len());
|
||||||
|
for v in vec {
|
||||||
|
new_vec.push(v.into_u32()?)
|
||||||
|
}
|
||||||
|
Ok(new_vec)
|
||||||
|
},
|
||||||
|
Unsigned(val) => Ok(vec![val]),
|
||||||
|
//_ => Err(::image::FormatError("Tag data malformed.".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Entry {
|
||||||
|
type_: Type,
|
||||||
|
count: u32,
|
||||||
|
offset: [u8; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Debug for Entry {
|
||||||
|
fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
|
||||||
|
fmt.write_str(&format!("Entry {{ type_: {:?}, count: {:?}, offset: {:?} }}",
|
||||||
|
self.type_,
|
||||||
|
self.count,
|
||||||
|
&self.offset
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
||||||
|
Entry { type_, count, offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a mem_reader for the offset/value field
|
||||||
|
fn r(&self, byte_order: ByteOrder) -> SmartReader<io::Cursor<Vec<u8>>> {
|
||||||
|
SmartReader::wrap(
|
||||||
|
io::Cursor::new(self.offset.to_vec()),
|
||||||
|
byte_order
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn val<R: Read + Seek>(&self, decoder: &mut super::Decoder<R>)
|
||||||
|
-> TiffResult<Value> {
|
||||||
|
let bo = decoder.byte_order();
|
||||||
|
match (self.type_, self.count) {
|
||||||
|
// TODO check if this could give wrong results
|
||||||
|
// at a different endianess of file/computer.
|
||||||
|
(Type::BYTE, 1) => Ok(Unsigned(u32::from(self.offset[0]))),
|
||||||
|
(Type::SHORT, 1) => Ok(Unsigned(u32::from(self.r(bo).read_u16()?))),
|
||||||
|
(Type::SHORT, 2) => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
Ok(List(vec![
|
||||||
|
Unsigned(u32::from(r.read_u16()?)),
|
||||||
|
Unsigned(u32::from(r.read_u16()?))
|
||||||
|
]))
|
||||||
|
},
|
||||||
|
(Type::SHORT, n) => {
|
||||||
|
let mut v = Vec::with_capacity(n as usize);
|
||||||
|
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
|
||||||
|
for _ in 0 .. n {
|
||||||
|
v.push(Unsigned(u32::from(decoder.read_short()?)))
|
||||||
|
}
|
||||||
|
Ok(List(v))
|
||||||
|
},
|
||||||
|
(Type::LONG, 1) => Ok(Unsigned(try!(self.r(bo).read_u32()))),
|
||||||
|
(Type::LONG, n) => {
|
||||||
|
let mut v = Vec::with_capacity(n as usize);
|
||||||
|
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
|
||||||
|
for _ in 0 .. n {
|
||||||
|
v.push(Unsigned(try!(decoder.read_long())))
|
||||||
|
}
|
||||||
|
Ok(List(v))
|
||||||
|
}
|
||||||
|
_ => Err(TiffError::UnsupportedError("Unsupported data type.".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type representing an Image File Directory
|
||||||
|
pub type Directory = HashMap<Tag, Entry>;
|
||||||
@@ -0,0 +1,541 @@
|
|||||||
|
use std::io::{self, Read, Seek};
|
||||||
|
use std::mem;
|
||||||
|
use num_traits::{FromPrimitive, Num};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use ::{ColorType, TiffError, TiffResult};
|
||||||
|
|
||||||
|
use self::ifd::Directory;
|
||||||
|
|
||||||
|
use self::stream::{
|
||||||
|
ByteOrder,
|
||||||
|
EndianReader,
|
||||||
|
SmartReader,
|
||||||
|
LZWReader,
|
||||||
|
PackBitsReader
|
||||||
|
};
|
||||||
|
|
||||||
|
mod ifd;
|
||||||
|
mod stream;
|
||||||
|
|
||||||
|
/// Result of a decoding process
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DecodingResult {
|
||||||
|
/// A vector of unsigned bytes
|
||||||
|
U8(Vec<u8>),
|
||||||
|
/// A vector of unsigned words
|
||||||
|
U16(Vec<u16>)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A buffer for image decoding
|
||||||
|
enum DecodingBuffer<'a> {
|
||||||
|
/// A slice of unsigned bytes
|
||||||
|
U8(&'a mut [u8]),
|
||||||
|
/// A slice of unsigned words
|
||||||
|
U16(&'a mut [u16])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, FromPrimitive)]
|
||||||
|
enum PhotometricInterpretation {
|
||||||
|
WhiteIsZero = 0,
|
||||||
|
BlackIsZero = 1,
|
||||||
|
RGB = 2,
|
||||||
|
RGBPalette = 3,
|
||||||
|
TransparencyMask = 4,
|
||||||
|
CMYK = 5,
|
||||||
|
YCbCr = 6,
|
||||||
|
CIELab = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, FromPrimitive)]
|
||||||
|
enum CompressionMethod {
|
||||||
|
None = 1,
|
||||||
|
Huffman = 2,
|
||||||
|
Fax3 = 3,
|
||||||
|
Fax4 = 4,
|
||||||
|
LZW = 5,
|
||||||
|
JPEG = 6,
|
||||||
|
PackBits = 0x8005
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, FromPrimitive)]
|
||||||
|
enum PlanarConfiguration {
|
||||||
|
Chunky = 1,
|
||||||
|
Planar = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, FromPrimitive)]
|
||||||
|
enum Predictor {
|
||||||
|
None = 1,
|
||||||
|
Horizontal = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The representation of a TIFF decoder
|
||||||
|
///
|
||||||
|
/// Currently does not support decoding of interlaced images
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Decoder<R> where R: Read + Seek {
|
||||||
|
reader: SmartReader<R>,
|
||||||
|
byte_order: ByteOrder,
|
||||||
|
next_ifd: Option<u32>,
|
||||||
|
ifd: Option<Directory>,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
bits_per_sample: Vec<u8>,
|
||||||
|
samples: u8,
|
||||||
|
photometric_interpretation: PhotometricInterpretation,
|
||||||
|
compression_method: CompressionMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Wrapping {
|
||||||
|
fn wrapping_add(&self, other: Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wrapping for u8 {
|
||||||
|
fn wrapping_add(&self, other: Self) -> Self {
|
||||||
|
u8::wrapping_add(*self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wrapping for u16 {
|
||||||
|
fn wrapping_add(&self, other: Self) -> Self {
|
||||||
|
u16::wrapping_add(*self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rev_hpredict_nsamp<T>(mut image: Vec<T>,
|
||||||
|
size: (u32, u32),
|
||||||
|
samples: usize)
|
||||||
|
-> Vec<T>
|
||||||
|
where T: Num + Copy + Wrapping {
|
||||||
|
let width = size.0 as usize;
|
||||||
|
let height = size.1 as usize;
|
||||||
|
for row in 0..height {
|
||||||
|
for col in samples..width * samples {
|
||||||
|
let prev_pixel = image[(row * width * samples + col - samples)];
|
||||||
|
let pixel = &mut image[(row * width * samples + col)];
|
||||||
|
*pixel = pixel.wrapping_add(prev_pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rev_hpredict(image: DecodingResult, size: (u32, u32), color_type: ColorType) -> TiffResult<DecodingResult> {
|
||||||
|
let samples = match color_type {
|
||||||
|
ColorType::Gray(8) | ColorType::Gray(16) => 1,
|
||||||
|
ColorType::RGB(8) | ColorType::RGB(16) => 3,
|
||||||
|
ColorType::RGBA(8) | ColorType::RGBA(16) => 4,
|
||||||
|
_ => return Err(TiffError::UnsupportedError(format!(
|
||||||
|
"Horizontal predictor for {:?} is unsupported.", color_type
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
Ok(match image {
|
||||||
|
DecodingResult::U8(buf) => {
|
||||||
|
DecodingResult::U8(rev_hpredict_nsamp(buf, size, samples))
|
||||||
|
},
|
||||||
|
DecodingResult::U16(buf) => {
|
||||||
|
DecodingResult::U16(rev_hpredict_nsamp(buf, size, samples))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> Decoder<R> {
|
||||||
|
/// Create a new decoder that decodes from the stream ```r```
|
||||||
|
pub fn new(r: R) -> TiffResult<Decoder<R>> {
|
||||||
|
Decoder {
|
||||||
|
reader: SmartReader::wrap(r, ByteOrder::LittleEndian),
|
||||||
|
byte_order: ByteOrder::LittleEndian,
|
||||||
|
next_ifd: None,
|
||||||
|
ifd: None,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
bits_per_sample: vec![1],
|
||||||
|
samples: 1,
|
||||||
|
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
|
||||||
|
compression_method: CompressionMethod::None
|
||||||
|
}.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> {
|
||||||
|
Ok((self.width, self.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn colortype(&mut self) -> TiffResult<ColorType> {
|
||||||
|
match self.photometric_interpretation {
|
||||||
|
// TODO: catch also [ 8, 8, 8, _] this does not work due to a bug in rust atm
|
||||||
|
PhotometricInterpretation::RGB if self.bits_per_sample == [8, 8, 8, 8] => Ok(ColorType::RGBA(8)),
|
||||||
|
PhotometricInterpretation::RGB if self.bits_per_sample == [8, 8, 8] => Ok(ColorType::RGB(8)),
|
||||||
|
PhotometricInterpretation::RGB if self.bits_per_sample == [16, 16, 16, 16] => Ok(ColorType::RGBA(16)),
|
||||||
|
PhotometricInterpretation::RGB if self.bits_per_sample == [16, 16, 16] => Ok(ColorType::RGB(16)),
|
||||||
|
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
||||||
|
if self.bits_per_sample.len() == 1 => Ok(ColorType::Gray(self.bits_per_sample[0])),
|
||||||
|
|
||||||
|
_ => Err(TiffError::UnsupportedError(format!(
|
||||||
|
"{:?} with {:?} bits per sample is unsupported", self.photometric_interpretation, self.bits_per_sample
|
||||||
|
))) // TODO: this is bad we should not fail at this point}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_header(&mut self) -> TiffResult<()> {
|
||||||
|
let mut endianess = Vec::with_capacity(2);
|
||||||
|
try!(self.reader.by_ref().take(2).read_to_end(&mut endianess));
|
||||||
|
match &*endianess {
|
||||||
|
b"II" => {
|
||||||
|
self.byte_order = ByteOrder::LittleEndian;
|
||||||
|
self.reader.byte_order = ByteOrder::LittleEndian; },
|
||||||
|
b"MM" => {
|
||||||
|
self.byte_order = ByteOrder::BigEndian;
|
||||||
|
self.reader.byte_order = ByteOrder::BigEndian; },
|
||||||
|
_ => return Err(TiffError::FormatError(
|
||||||
|
"TIFF signature not found.".to_string()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
if try!(self.read_short()) != 42 {
|
||||||
|
return Err(TiffError::FormatError("TIFF signature invalid.".to_string()))
|
||||||
|
}
|
||||||
|
self.next_ifd = match try!(self.read_long()) {
|
||||||
|
0 => None,
|
||||||
|
n => Some(n)
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the decoder.
|
||||||
|
pub fn init(mut self) -> TiffResult<Decoder<R>> {
|
||||||
|
try!(self.read_header());
|
||||||
|
self.next_image()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads in the next image.
|
||||||
|
/// If there is no further image in the TIFF file a format error is returned.
|
||||||
|
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
|
||||||
|
pub fn next_image(mut self) -> TiffResult<Decoder<R>> {
|
||||||
|
self.ifd = Some(try!(self.read_ifd()));
|
||||||
|
self.width = try!(self.get_tag_u32(ifd::Tag::ImageWidth));
|
||||||
|
self.height = try!(self.get_tag_u32(ifd::Tag::ImageLength));
|
||||||
|
self.photometric_interpretation = match FromPrimitive::from_u32(
|
||||||
|
try!(self.get_tag_u32(ifd::Tag::PhotometricInterpretation))
|
||||||
|
) {
|
||||||
|
Some(val) => val,
|
||||||
|
None => return Err(TiffError::UnsupportedError(
|
||||||
|
"The image is using an unknown photometric interpretation.".to_string()
|
||||||
|
))
|
||||||
|
};
|
||||||
|
if let Some(val) = try!(self.find_tag_u32(ifd::Tag::Compression)) {
|
||||||
|
match FromPrimitive::from_u32(val) {
|
||||||
|
Some(method) => {
|
||||||
|
self.compression_method = method
|
||||||
|
},
|
||||||
|
None => return Err(TiffError::UnsupportedError(
|
||||||
|
"Unknown compression method.".to_string()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(val) = try!(self.find_tag_u32(ifd::Tag::SamplesPerPixel)) {
|
||||||
|
self.samples = val as u8
|
||||||
|
}
|
||||||
|
match self.samples {
|
||||||
|
1 => {
|
||||||
|
if let Some(val) = try!(self.find_tag_u32(ifd::Tag::BitsPerSample)) {
|
||||||
|
self.bits_per_sample = vec![val as u8]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
3 | 4 => {
|
||||||
|
if let Some(val) = try!(self.find_tag_u32_vec(ifd::Tag::BitsPerSample)) {
|
||||||
|
self.bits_per_sample = val.iter().map(|&v| v as u8).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
_ => return Err(TiffError::UnsupportedError(
|
||||||
|
format!("{} samples per pixel is supported.", self.samples)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if there is at least one more image available.
|
||||||
|
pub fn more_images(&self) -> bool {
|
||||||
|
match self.next_ifd {
|
||||||
|
Some(_) => true,
|
||||||
|
None => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the byte_order
|
||||||
|
pub fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a TIFF short value
|
||||||
|
#[inline]
|
||||||
|
pub fn read_short(&mut self) -> Result<u16, io::Error> {
|
||||||
|
self.reader.read_u16()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a TIFF long value
|
||||||
|
#[inline]
|
||||||
|
pub fn read_long(&mut self) -> Result<u32, io::Error> {
|
||||||
|
self.reader.read_u32()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a TIFF IFA offset/value field
|
||||||
|
#[inline]
|
||||||
|
pub fn read_offset(&mut self) -> Result<[u8; 4], io::Error> {
|
||||||
|
let mut val = [0; 4];
|
||||||
|
try!(self.reader.read_exact(&mut val));
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the cursor to the specified offset
|
||||||
|
#[inline]
|
||||||
|
pub fn goto_offset(&mut self, offset: u32) -> io::Result<()> {
|
||||||
|
self.reader.seek(io::SeekFrom::Start(u64::from(offset))).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a IFD entry.
|
||||||
|
// An IFD entry has four fields:
|
||||||
|
//
|
||||||
|
// Tag 2 bytes
|
||||||
|
// Type 2 bytes
|
||||||
|
// Count 4 bytes
|
||||||
|
// Value 4 bytes either a pointer the value itself
|
||||||
|
fn read_entry(&mut self) -> TiffResult<Option<(ifd::Tag, ifd::Entry)>> {
|
||||||
|
let tag = ifd::Tag::from_u16(try!(self.read_short()));
|
||||||
|
let type_: ifd::Type = match FromPrimitive::from_u16(try!(self.read_short())) {
|
||||||
|
Some(t) => t,
|
||||||
|
None => {
|
||||||
|
// Unknown type. Skip this entry according to spec.
|
||||||
|
try!(self.read_long());
|
||||||
|
try!(self.read_long());
|
||||||
|
return Ok(None)
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Some((tag, ifd::Entry::new(
|
||||||
|
type_,
|
||||||
|
try!(self.read_long()), // count
|
||||||
|
try!(self.read_offset()) // offset
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the next IFD
|
||||||
|
fn read_ifd(&mut self) -> TiffResult<Directory> {
|
||||||
|
let mut dir: Directory = HashMap::new();
|
||||||
|
match self.next_ifd {
|
||||||
|
None => return Err(TiffError::FormatError(
|
||||||
|
"Image file directory not found.".to_string())
|
||||||
|
),
|
||||||
|
Some(offset) => try!(self.goto_offset(offset))
|
||||||
|
}
|
||||||
|
for _ in 0..try!(self.read_short()) {
|
||||||
|
let (tag, entry) = match try!(self.read_entry()) {
|
||||||
|
Some(val) => val,
|
||||||
|
None => continue // Unknown data type in tag, skip
|
||||||
|
};
|
||||||
|
dir.insert(tag, entry);
|
||||||
|
}
|
||||||
|
self.next_ifd = match try!(self.read_long()) {
|
||||||
|
0 => None,
|
||||||
|
n => Some(n)
|
||||||
|
};
|
||||||
|
Ok(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a tag.
|
||||||
|
/// Return `Ok(None)` if the tag is not present.
|
||||||
|
fn find_tag(&mut self, tag: ifd::Tag) -> TiffResult<Option<ifd::Value>> {
|
||||||
|
let ifd: &Directory = unsafe {
|
||||||
|
let ifd = self.ifd.as_ref().unwrap(); // Ok to fail
|
||||||
|
// Get a immutable borrow of self
|
||||||
|
// This is ok because entry val only changes the stream
|
||||||
|
// but not the directory.
|
||||||
|
mem::transmute_copy(&ifd)
|
||||||
|
};
|
||||||
|
match ifd.get(&tag) {
|
||||||
|
None => Ok(None),
|
||||||
|
Some(entry) => Ok(Some(try!(entry.val(self))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
fn find_tag_u32(&mut self, tag: ifd::Tag) -> TiffResult<Option<u32>> {
|
||||||
|
match self.find_tag(tag)? {
|
||||||
|
Some(val) => val.into_u32().map(Some),
|
||||||
|
None => Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
fn find_tag_u32_vec(&mut self, tag: ifd::Tag) -> TiffResult<Option<Vec<u32>>> {
|
||||||
|
match self.find_tag(tag)? {
|
||||||
|
Some(val) => val.into_u32_vec().map(Some),
|
||||||
|
None => Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a tag.
|
||||||
|
/// Returns an error if the tag is not present
|
||||||
|
fn get_tag(&mut self, tag: ifd::Tag) -> TiffResult<ifd::Value> {
|
||||||
|
match try!(self.find_tag(tag)) {
|
||||||
|
Some(val) => Ok(val),
|
||||||
|
None => Err(TiffError::FormatError(format!(
|
||||||
|
"Required tag `{:?}` not found.", tag
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
fn get_tag_u32(&mut self, tag: ifd::Tag) -> TiffResult<u32> {
|
||||||
|
self.get_tag(tag)?.into_u32()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a tag and convert it to the desired type.
|
||||||
|
fn get_tag_u32_vec(&mut self, tag: ifd::Tag) -> TiffResult<Vec<u32>> {
|
||||||
|
self.get_tag(tag)?.into_u32_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decompresses the strip into the supplied buffer.
|
||||||
|
/// Returns the number of bytes read.
|
||||||
|
fn expand_strip<'a>(&mut self, buffer: DecodingBuffer<'a>, offset: u32, length: u32, max_uncompressed_length: usize) -> TiffResult<usize> {
|
||||||
|
let color_type = try!(self.colortype());
|
||||||
|
try!(self.goto_offset(offset));
|
||||||
|
let (bytes, mut reader): (usize, Box<EndianReader>) = match self.compression_method {
|
||||||
|
CompressionMethod::None => {
|
||||||
|
let order = self.reader.byte_order;
|
||||||
|
(length as usize, Box::new(SmartReader::wrap(&mut self.reader, order)))
|
||||||
|
},
|
||||||
|
CompressionMethod::LZW => {
|
||||||
|
let (bytes, reader) = try!(LZWReader::new(&mut self.reader, length as usize, max_uncompressed_length));
|
||||||
|
(bytes, Box::new(reader))
|
||||||
|
},
|
||||||
|
CompressionMethod::PackBits => {
|
||||||
|
let order = self.reader.byte_order;
|
||||||
|
let (bytes, reader) = try!(PackBitsReader::new(&mut self.reader, order, length as usize));
|
||||||
|
(bytes, Box::new(reader))
|
||||||
|
},
|
||||||
|
method => return Err(TiffError::UnsupportedError(format!(
|
||||||
|
"Compression method {:?} is unsupported", method
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
Ok(match (color_type, buffer) {
|
||||||
|
(ColorType:: RGB(8), DecodingBuffer::U8(ref mut buffer)) |
|
||||||
|
(ColorType::RGBA(8), DecodingBuffer::U8(ref mut buffer)) => {
|
||||||
|
try!(reader.read(&mut buffer[..bytes]))
|
||||||
|
}
|
||||||
|
(ColorType::RGBA(16), DecodingBuffer::U16(ref mut buffer)) |
|
||||||
|
(ColorType:: RGB(16), DecodingBuffer::U16(ref mut buffer)) => {
|
||||||
|
for datum in buffer[..bytes/2].iter_mut() {
|
||||||
|
*datum = try!(reader.read_u16())
|
||||||
|
}
|
||||||
|
bytes/2
|
||||||
|
}
|
||||||
|
(ColorType::Gray(16), DecodingBuffer::U16(ref mut buffer)) => {
|
||||||
|
for datum in buffer[..bytes/2].iter_mut() {
|
||||||
|
*datum = try!(reader.read_u16());
|
||||||
|
if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
*datum = 0xffff - *datum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytes/2
|
||||||
|
}
|
||||||
|
(ColorType::Gray(n), DecodingBuffer::U8(ref mut buffer)) if n <= 8 => {
|
||||||
|
try!(reader.read_exact(&mut buffer[..bytes]));
|
||||||
|
if self.photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
for byte in buffer[..bytes].iter_mut() {
|
||||||
|
*byte = 0xff - *byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
(type_, _) => return Err(TiffError::UnsupportedError(format!(
|
||||||
|
"Color type {:?} is unsupported", type_
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the entire image and return it as a Vector
|
||||||
|
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
||||||
|
let bits_per_pixel: u8 = self.bits_per_sample.iter().cloned().sum();
|
||||||
|
let scanline_size_bits = self.width as usize * bits_per_pixel as usize;
|
||||||
|
let scanline_size = (scanline_size_bits + 7) / 8;
|
||||||
|
let rows_per_strip = self.get_tag_u32(ifd::Tag::RowsPerStrip)
|
||||||
|
.unwrap_or(self.height) as usize;
|
||||||
|
let buffer_size =
|
||||||
|
self.width as usize
|
||||||
|
* self.height as usize
|
||||||
|
* self.bits_per_sample.iter().count();
|
||||||
|
let mut result = match self.bits_per_sample.iter().cloned().max().unwrap_or(8) {
|
||||||
|
n if n <= 8 => DecodingResult::U8(Vec::with_capacity(buffer_size)),
|
||||||
|
n if n <= 16 => DecodingResult::U16(Vec::with_capacity(buffer_size)),
|
||||||
|
n => return Err(
|
||||||
|
TiffError::UnsupportedError(
|
||||||
|
format!("{} bits per channel not supported", n)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if let Ok(config) = self.get_tag_u32(ifd::Tag::PlanarConfiguration) {
|
||||||
|
match FromPrimitive::from_u32(config) {
|
||||||
|
Some(PlanarConfiguration::Chunky) => {},
|
||||||
|
config => return Err(TiffError::UnsupportedError(
|
||||||
|
format!("Unsupported planar configuration “{:?}”.", config)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Safe since the uninitialized values are never read.
|
||||||
|
match result {
|
||||||
|
DecodingResult::U8(ref mut buffer) =>
|
||||||
|
unsafe { buffer.set_len(buffer_size) },
|
||||||
|
DecodingResult::U16(ref mut buffer) =>
|
||||||
|
unsafe { buffer.set_len(buffer_size) },
|
||||||
|
}
|
||||||
|
let mut units_read = 0;
|
||||||
|
for (i, (&offset, &byte_count)) in try!(self.get_tag_u32_vec(ifd::Tag::StripOffsets))
|
||||||
|
.iter().zip(try!(self.get_tag_u32_vec(ifd::Tag::StripByteCounts)).iter()).enumerate() {
|
||||||
|
let uncompressed_strip_size = scanline_size
|
||||||
|
* (self.height as usize - i * rows_per_strip);
|
||||||
|
|
||||||
|
units_read += match result {
|
||||||
|
DecodingResult::U8(ref mut buffer) => {
|
||||||
|
try!(self.expand_strip(
|
||||||
|
DecodingBuffer::U8(&mut buffer[units_read..]),
|
||||||
|
offset, byte_count, uncompressed_strip_size
|
||||||
|
))
|
||||||
|
},
|
||||||
|
DecodingResult::U16(ref mut buffer) => {
|
||||||
|
try!(self.expand_strip(
|
||||||
|
DecodingBuffer::U16(&mut buffer[units_read..]),
|
||||||
|
offset, byte_count, uncompressed_strip_size
|
||||||
|
))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if units_read == buffer_size {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Shrink length such that the uninitialized memory is not exposed.
|
||||||
|
if units_read < buffer_size {
|
||||||
|
match result {
|
||||||
|
DecodingResult::U8(ref mut buffer) =>
|
||||||
|
unsafe { buffer.set_len(units_read) },
|
||||||
|
DecodingResult::U16(ref mut buffer) =>
|
||||||
|
unsafe { buffer.set_len(units_read) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(predictor) = self.get_tag_u32(ifd::Tag::Predictor) {
|
||||||
|
result = match FromPrimitive::from_u32(predictor) {
|
||||||
|
Some(Predictor::None) => result,
|
||||||
|
Some(Predictor::Horizontal) => {
|
||||||
|
try!(rev_hpredict(
|
||||||
|
result,
|
||||||
|
try!(self.dimensions()),
|
||||||
|
try!(self.colortype())
|
||||||
|
))
|
||||||
|
},
|
||||||
|
None => return Err(TiffError::FormatError(
|
||||||
|
format!("Unknown predictor “{}” encountered", predictor)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
|
use std::io::{self, Read, Seek};
|
||||||
|
use byteorder::{ReadBytesExt, BigEndian, LittleEndian};
|
||||||
|
use lzw;
|
||||||
|
|
||||||
|
/// Byte order of the TIFF file.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum ByteOrder {
|
||||||
|
/// little endian byte order
|
||||||
|
LittleEndian,
|
||||||
|
/// big endian byte order
|
||||||
|
BigEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Reader that is aware of the byte order.
|
||||||
|
pub trait EndianReader: Read {
|
||||||
|
/// Byte order that should be adhered to
|
||||||
|
fn byte_order(&self) -> ByteOrder;
|
||||||
|
|
||||||
|
/// Reads an u16
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_u16(&mut self) -> Result<u16, io::Error> {
|
||||||
|
match self.byte_order() {
|
||||||
|
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u16::<LittleEndian>(self),
|
||||||
|
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u16::<BigEndian>(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads an u32
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_u32(&mut self) -> Result<u32, io::Error> {
|
||||||
|
match self.byte_order() {
|
||||||
|
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u32::<LittleEndian>(self),
|
||||||
|
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u32::<BigEndian>(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reader that decompresses LZW streams
|
||||||
|
pub struct LZWReader {
|
||||||
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
byte_order: ByteOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LZWReader {
|
||||||
|
/// Wraps a reader
|
||||||
|
pub fn new<R>(reader: &mut SmartReader<R>, compressed_length: usize, max_uncompressed_length: usize) -> io::Result<(usize, LZWReader)> where R: Read + Seek {
|
||||||
|
let order = reader.byte_order;
|
||||||
|
let mut compressed = vec![0; compressed_length as usize];
|
||||||
|
try!(reader.read_exact(&mut compressed[..]));
|
||||||
|
let mut uncompressed = Vec::with_capacity(max_uncompressed_length);
|
||||||
|
let mut decoder = lzw::DecoderEarlyChange::new(lzw::MsbReader::new(), 8);
|
||||||
|
let mut bytes_read = 0;
|
||||||
|
while bytes_read < compressed_length && uncompressed.len() < max_uncompressed_length {
|
||||||
|
let (len, bytes) = try!(decoder.decode_bytes(&compressed[bytes_read..]));
|
||||||
|
bytes_read += len;
|
||||||
|
uncompressed.extend_from_slice(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = uncompressed.len();
|
||||||
|
Ok((bytes, LZWReader {
|
||||||
|
buffer: io::Cursor::new(uncompressed),
|
||||||
|
byte_order: order
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for LZWReader {
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndianReader for LZWReader {
|
||||||
|
#[inline(always)]
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reader that unpacks Apple's `PackBits` format
|
||||||
|
pub struct PackBitsReader {
|
||||||
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
byte_order: ByteOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackBitsReader {
|
||||||
|
/// Wraps a reader
|
||||||
|
pub fn new<R: Read + Seek>(mut reader: R, byte_order: ByteOrder, length: usize) -> io::Result<(usize, PackBitsReader)> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
let mut read: usize = 0;
|
||||||
|
while read < length {
|
||||||
|
read += try!(read_packbits_run(&mut reader, &mut buffer));
|
||||||
|
}
|
||||||
|
Ok((buffer.len(), PackBitsReader {
|
||||||
|
buffer: io::Cursor::new(buffer),
|
||||||
|
byte_order
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io::Result<usize> {
|
||||||
|
let mut header: [u8; 1] = [0];
|
||||||
|
|
||||||
|
let bytes = try!(reader.read(&mut header));
|
||||||
|
|
||||||
|
match bytes {
|
||||||
|
0 => Ok(0),
|
||||||
|
_ => match header[0] as i8 {
|
||||||
|
-128 => Ok(1),
|
||||||
|
h if h >= -127 && h <= -1 => {
|
||||||
|
let new_len = buffer.len() + (1 - h as isize) as usize;
|
||||||
|
try!(reader.read_exact(&mut header));
|
||||||
|
buffer.resize(new_len, header[0]);
|
||||||
|
Ok(2)
|
||||||
|
},
|
||||||
|
h => {
|
||||||
|
let num_vals = h as usize + 1;
|
||||||
|
let start = buffer.len();
|
||||||
|
buffer.resize(start + num_vals, 0);
|
||||||
|
try!(reader.read_exact(&mut buffer[start..]));
|
||||||
|
Ok(num_vals + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for PackBitsReader {
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndianReader for PackBitsReader {
|
||||||
|
#[inline(always)]
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reader that is aware of the byte order.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SmartReader<R> where R: Read + Seek {
|
||||||
|
reader: R,
|
||||||
|
pub byte_order: ByteOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> SmartReader<R> where R: Read + Seek {
|
||||||
|
/// Wraps a reader
|
||||||
|
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
|
||||||
|
SmartReader { reader, byte_order }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> EndianReader for SmartReader<R> where R: Read + Seek {
|
||||||
|
#[inline(always)]
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> Read for SmartReader<R> {
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.reader.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> Seek for SmartReader<R> {
|
||||||
|
#[inline]
|
||||||
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
|
self.reader.seek(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_packbits() {
|
||||||
|
let encoded = vec![0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA,
|
||||||
|
0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA];
|
||||||
|
let encoded_len = encoded.len();
|
||||||
|
|
||||||
|
let buff = io::Cursor::new(encoded);
|
||||||
|
let (_, mut decoder) = PackBitsReader::new(buff, ByteOrder::LittleEndian, encoded_len).unwrap();
|
||||||
|
|
||||||
|
let mut decoded = Vec::new();
|
||||||
|
decoder.read_to_end(&mut decoded).unwrap();
|
||||||
|
|
||||||
|
let expected = vec![0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA,
|
||||||
|
0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA,
|
||||||
|
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA];
|
||||||
|
assert_eq!(decoded, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
/// Tiff error kinds.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TiffError {
|
||||||
|
/// The Image is not formatted properly
|
||||||
|
FormatError(String),
|
||||||
|
|
||||||
|
/// The Decoder does not support this image format
|
||||||
|
UnsupportedError(String),
|
||||||
|
|
||||||
|
/// An I/O Error occurred while decoding the image
|
||||||
|
IoError(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TiffError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
match *self {
|
||||||
|
TiffError::FormatError(ref e) => write!(fmt, "Format error: {}", e),
|
||||||
|
TiffError::UnsupportedError(ref f) => write!(fmt, "The Decoder does not support the \
|
||||||
|
image format `{}`", f),
|
||||||
|
TiffError::IoError(ref e) => e.fmt(fmt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for TiffError {
|
||||||
|
fn description (&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
TiffError::FormatError(..) => "Format error",
|
||||||
|
TiffError::UnsupportedError(..) => "Unsupported error",
|
||||||
|
TiffError::IoError(..) => "IO error",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause (&self) -> Option<&Error> {
|
||||||
|
match *self {
|
||||||
|
TiffError::IoError(ref e) => Some(e),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for TiffError {
|
||||||
|
fn from(err: io::Error) -> TiffError {
|
||||||
|
TiffError::IoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of an image decoding/encoding process
|
||||||
|
pub type TiffResult<T> = Result<T, TiffError>;
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
//! Decoding and Encoding of TIFF Images
|
||||||
|
//!
|
||||||
|
//! TIFF (Tagged Image File Format) is a versatile image format that supports
|
||||||
|
//! lossless and lossy compression.
|
||||||
|
//!
|
||||||
|
//! # Related Links
|
||||||
|
//! * <http://partners.adobe.com/public/developer/tiff/index.html> - The TIFF specification
|
||||||
|
|
||||||
|
extern crate byteorder;
|
||||||
|
extern crate lzw;
|
||||||
|
#[macro_use] extern crate num_derive;
|
||||||
|
extern crate num_traits;
|
||||||
|
|
||||||
|
pub mod decoder;
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
pub use self::error::{TiffError, TiffResult};
|
||||||
|
|
||||||
|
/// An enumeration over supported color types and their bit depths
|
||||||
|
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
|
||||||
|
pub enum ColorType {
|
||||||
|
/// Pixel is grayscale
|
||||||
|
Gray(u8),
|
||||||
|
|
||||||
|
/// Pixel contains R, G and B channels
|
||||||
|
RGB(u8),
|
||||||
|
|
||||||
|
/// Pixel is an index into a color palette
|
||||||
|
Palette(u8),
|
||||||
|
|
||||||
|
/// Pixel is grayscale with an alpha channel
|
||||||
|
GrayA(u8),
|
||||||
|
|
||||||
|
/// Pixel is RGB with an alpha channel
|
||||||
|
RGBA(u8),
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
extern crate tiff;
|
||||||
|
|
||||||
|
use tiff::ColorType;
|
||||||
|
use tiff::decoder::{Decoder, DecodingResult};
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_u8()
|
||||||
|
{
|
||||||
|
let img_file = File::open("./tests/images/minisblack-1c-8b.tiff").expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(8));
|
||||||
|
let img_res = decoder.read_image();
|
||||||
|
assert!(img_res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rgb_u8()
|
||||||
|
{
|
||||||
|
let img_file = File::open("./tests/images/rgb-3c-8b.tiff").expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
|
let img_res = decoder.read_image();
|
||||||
|
assert!(img_res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_u16()
|
||||||
|
{
|
||||||
|
let img_file = File::open("./tests/images/minisblack-1c-16b.tiff").expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(16));
|
||||||
|
let img_res = decoder.read_image();
|
||||||
|
assert!(img_res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rgb_u16()
|
||||||
|
{
|
||||||
|
let img_file = File::open("./tests/images/rgb-3c-16b.tiff").expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(16));
|
||||||
|
let img_res = decoder.read_image();
|
||||||
|
assert!(img_res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: GrayA support
|
||||||
|
//#[test]
|
||||||
|
//fn test_gray_alpha_u8()
|
||||||
|
//{
|
||||||
|
//let img_file = File::open("./tests/images/minisblack-2c-8b-alpha.tiff").expect("Cannot find test image!");
|
||||||
|
//let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
//assert_eq!(decoder.colortype().unwrap(), ColorType::GrayA(8));
|
||||||
|
//let img_res = decoder.read_image();
|
||||||
|
//assert!(img_res.is_ok());
|
||||||
|
//}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
Copyright (c) 1988-1997 Sam Leffler
|
||||||
|
Copyright (c) 1991-1997 Silicon Graphics, Inc.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, distribute, and sell this software and
|
||||||
|
its documentation for any purpose is hereby granted without fee, provided
|
||||||
|
that (i) the above copyright notices and this permission notice appear in
|
||||||
|
all copies of the software and related documentation, and (ii) the names of
|
||||||
|
Sam Leffler and Silicon Graphics may not be used in any advertising or
|
||||||
|
publicity relating to the software without the specific, prior written
|
||||||
|
permission of Sam Leffler and Silicon Graphics.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||||
|
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
|
||||||
|
ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
|
||||||
|
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||||
|
WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
|
||||||
|
LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
||||||
|
OF THIS SOFTWARE.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
The test files in this directory mostly have dimension 157x151.
|
||||||
|
The naming convention is
|
||||||
|
|
||||||
|
photometric-channels-bits.tiff
|
||||||
|
|
||||||
|
minisblack-1c-16b.tiff
|
||||||
|
minisblack-1c-8b.tiff
|
||||||
|
miniswhite-1c-1b.tiff
|
||||||
|
palette-1c-1b.tiff
|
||||||
|
palette-1c-4b.tiff
|
||||||
|
palette-1c-8b.tiff
|
||||||
|
rgb-3c-16b.tiff
|
||||||
|
rgb-3c-8b.tiff
|
||||||
|
|
||||||
|
logluv-3c-16b.tiff: logluv compression/photometric interp
|
||||||
|
minisblack-2c-8b-alpha.tiff: grey+alpha
|
||||||
|
|
||||||
|
BMP files (anchient BMPv2 since v3 does not work):
|
||||||
|
|
||||||
|
palette-1c-8b.bmp
|
||||||
|
rgb-3c-8b.bmp
|
||||||
|
|
||||||
|
GIF files (anchient GIF '87 since modern GIF does not work):
|
||||||
|
palette-1c-8b.gif
|
||||||
|
|
||||||
|
PNM files:
|
||||||
|
minisblack-1c-8b.pgm
|
||||||
|
miniswhite-1c-1b.pbm
|
||||||
|
rgb-3c-8b.ppm
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user