Merge branch 'master' into 16bit-performance

This commit is contained in:
Sven Nilsen authored and GitHub committed 2018-09-19 17:54:56 +02:00
commit 0228fc3204
5 files changed
+147 -72

No files matched your search

+21 -17
View File
@@ -4,9 +4,9 @@ use std::io::{self, Read, Seek};
use std::collections::{HashMap}; use std::collections::{HashMap};
use super::stream::{ByteOrder, SmartReader, EndianReader}; use super::stream::{ByteOrder, SmartReader, EndianReader};
use ::{TiffError, TiffResult}; use ::{TiffError, TiffFormatError, TiffUnsupportedError, TiffResult};
use self::Value::{Unsigned, List, Rational}; use self::Value::{Unsigned, List, Rational, Ascii};
macro_rules! tags { macro_rules! tags {
{$( {$(
@@ -33,7 +33,7 @@ macro_rules! tags {
// Note: These tags appear in the order they are mentioned in the TIFF reference // Note: These tags appear in the order they are mentioned in the TIFF reference
tags!{ tags!{
// Baseline tags: // Baseline tags:
Artist 315; // TODO add support Artist 315;
// grayscale images PhotometricInterpretation 1 or 3 // grayscale images PhotometricInterpretation 1 or 3
BitsPerSample 258; BitsPerSample 258;
CellLength 265; // TODO add support CellLength 265; // TODO add support
@@ -41,22 +41,22 @@ tags!{
// palette-color images (PhotometricInterpretation 3) // palette-color images (PhotometricInterpretation 3)
ColorMap 320; // TODO add support ColorMap 320; // TODO add support
Compression 259; // TODO add support for 2 and 32773 Compression 259; // TODO add support for 2 and 32773
Copyright 33_432; // TODO add support Copyright 33_432;
DateTime 306; // TODO add support DateTime 306;
ExtraSamples 338; // TODO add support ExtraSamples 338; // TODO add support
FillOrder 266; // TODO add support FillOrder 266; // TODO add support
FreeByteCounts 289; // TODO add support FreeByteCounts 289; // TODO add support
FreeOffsets 288; // TODO add support FreeOffsets 288; // TODO add support
GrayResponseCurve 291; // TODO add support GrayResponseCurve 291; // TODO add support
GrayResponseUnit 290; // TODO add support GrayResponseUnit 290; // TODO add support
HostComputer 316; // TODO add support HostComputer 316;
ImageDescription 270; // TODO add support ImageDescription 270;
ImageLength 257; ImageLength 257;
ImageWidth 256; ImageWidth 256;
Make 271; // TODO add support Make 271;
MaxSampleValue 281; // TODO add support MaxSampleValue 281; // TODO add support
MinSampleValue 280; // TODO add support MinSampleValue 280; // TODO add support
Model 272; // TODO add support Model 272;
NewSubfileType 254; // TODO add support NewSubfileType 254; // TODO add support
Orientation 274; // TODO add support Orientation 274; // TODO add support
PhotometricInterpretation 262; PhotometricInterpretation 262;
@@ -86,21 +86,20 @@ pub enum Type {
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Value { pub enum Value {
//Signed(i32), //Signed(i32),
Unsigned(u32), Unsigned(u32),
List(Vec<Value>), List(Vec<Value>),
Rational(u32, u32) Rational(u32, u32),
Ascii(String)
} }
impl Value { impl Value {
pub fn into_u32(self) -> TiffResult<u32> { pub fn into_u32(self) -> TiffResult<u32> {
match self { match self {
Unsigned(val) => Ok(val), Unsigned(val) => Ok(val),
val => Err(TiffError::FormatError(format!( val => Err(TiffError::FormatError(TiffFormatError::UnsignedIntegerExpected(val))),
"Expected unsigned integer, {:?} found.", val
)))
} }
} }
pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> { pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> {
@@ -113,8 +112,8 @@ impl Value {
Ok(new_vec) Ok(new_vec)
}, },
Unsigned(val) => Ok(vec![val]), Unsigned(val) => Ok(vec![val]),
Rational(numerator, denominator) => Ok(vec![numerator, denominator]) Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
//_ => Err(::image::FormatError("Tag data malformed.".to_string())) Ascii(val) => Ok(val.chars().map(|x| x as u32).collect())
} }
} }
} }
@@ -196,7 +195,12 @@ impl Entry {
} }
Ok(List(v)) Ok(List(v))
}, },
_ => Err(TiffError::UnsupportedError("Unsupported data type.".to_string())) (Type::ASCII, n) => {
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
let string = try!(decoder.read_string(n as usize));
Ok(Ascii(string))
}
_ => Err(TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedDataType))
} }
} }
} }
+35 -51
View File
@@ -2,8 +2,9 @@ use std::io::{self, Read, Seek};
use std::mem; use std::mem;
use num_traits::{FromPrimitive, Num}; use num_traits::{FromPrimitive, Num};
use std::collections::HashMap; use std::collections::HashMap;
use std::string::FromUtf8Error;
use ::{ColorType, TiffError, TiffResult}; use ::{ColorType, TiffError, TiffFormatError, TiffUnsupportedError, TiffResult};
use self::ifd::Directory; use self::ifd::Directory;
@@ -35,8 +36,8 @@ enum DecodingBuffer<'a> {
U16(&'a mut [u16]) U16(&'a mut [u16])
} }
#[derive(Clone, Copy, Debug, PartialEq, FromPrimitive)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, FromPrimitive)]
enum PhotometricInterpretation { pub enum PhotometricInterpretation {
WhiteIsZero = 0, WhiteIsZero = 0,
BlackIsZero = 1, BlackIsZero = 1,
RGB = 2, RGB = 2,
@@ -47,8 +48,8 @@ enum PhotometricInterpretation {
CIELab = 8, CIELab = 8,
} }
#[derive(Clone, Copy, Debug, FromPrimitive)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, FromPrimitive)]
enum CompressionMethod { pub enum CompressionMethod {
None = 1, None = 1,
Huffman = 2, Huffman = 2,
Fax3 = 3, Fax3 = 3,
@@ -58,13 +59,13 @@ enum CompressionMethod {
PackBits = 0x8005 PackBits = 0x8005
} }
#[derive(Clone, Copy, Debug, FromPrimitive)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, FromPrimitive)]
enum PlanarConfiguration { pub enum PlanarConfiguration {
Chunky = 1, Chunky = 1,
Planar = 2 Planar = 2
} }
#[derive(Clone, Copy, Debug, FromPrimitive)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, FromPrimitive)]
enum Predictor { enum Predictor {
None = 1, None = 1,
Horizontal = 2 Horizontal = 2
@@ -125,9 +126,7 @@ fn rev_hpredict(image: DecodingResult, size: (u32, u32), color_type: ColorType)
ColorType::Gray(8) | ColorType::Gray(16) => 1, ColorType::Gray(8) | ColorType::Gray(16) => 1,
ColorType::RGB(8) | ColorType::RGB(16) => 3, ColorType::RGB(8) | ColorType::RGB(16) => 3,
ColorType::RGBA(8) | ColorType::RGBA(16) | ColorType::CMYK(8) => 4, ColorType::RGBA(8) | ColorType::RGBA(16) | ColorType::CMYK(8) => 4,
_ => return Err(TiffError::UnsupportedError(format!( _ => return Err(TiffError::UnsupportedError(TiffUnsupportedError::HorizontalPredictor(color_type)))
"Horizontal predictor for {:?} is unsupported.", color_type
)))
}; };
Ok(match image { Ok(match image {
DecodingResult::U8(buf) => { DecodingResult::U8(buf) => {
@@ -171,9 +170,8 @@ impl<R: Read + Seek> Decoder<R> {
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
if self.bits_per_sample.len() == 1 => Ok(ColorType::Gray(self.bits_per_sample[0])), if self.bits_per_sample.len() == 1 => Ok(ColorType::Gray(self.bits_per_sample[0])),
_ => Err(TiffError::UnsupportedError(format!( // TODO: this is bad we should not fail at this point
"{:?} with {:?} bits per sample is unsupported", self.photometric_interpretation, self.bits_per_sample _ => Err(TiffError::UnsupportedError(TiffUnsupportedError::InterpretationWithBits(self.photometric_interpretation, self.bits_per_sample.clone())))
))) // TODO: this is bad we should not fail at this point}
} }
} }
@@ -187,12 +185,10 @@ impl<R: Read + Seek> Decoder<R> {
b"MM" => { b"MM" => {
self.byte_order = ByteOrder::BigEndian; self.byte_order = ByteOrder::BigEndian;
self.reader.byte_order = ByteOrder::BigEndian; }, self.reader.byte_order = ByteOrder::BigEndian; },
_ => return Err(TiffError::FormatError( _ => return Err(TiffError::FormatError(TiffFormatError::TiffSignatureNotFound))
"TIFF signature not found.".to_string()
))
} }
if try!(self.read_short()) != 42 { if try!(self.read_short()) != 42 {
return Err(TiffError::FormatError("TIFF signature invalid.".to_string())) return Err(TiffError::FormatError(TiffFormatError::TiffSignatureInvalid))
} }
self.next_ifd = match try!(self.read_long()) { self.next_ifd = match try!(self.read_long()) {
0 => None, 0 => None,
@@ -218,18 +214,14 @@ impl<R: Read + Seek> Decoder<R> {
try!(self.get_tag_u32(ifd::Tag::PhotometricInterpretation)) try!(self.get_tag_u32(ifd::Tag::PhotometricInterpretation))
) { ) {
Some(val) => val, Some(val) => val,
None => return Err(TiffError::UnsupportedError( None => return Err(TiffError::UnsupportedError(TiffUnsupportedError::UnknownInterpretation))
"The image is using an unknown photometric interpretation.".to_string()
))
}; };
if let Some(val) = try!(self.find_tag_u32(ifd::Tag::Compression)) { if let Some(val) = try!(self.find_tag_u32(ifd::Tag::Compression)) {
match FromPrimitive::from_u32(val) { match FromPrimitive::from_u32(val) {
Some(method) => { Some(method) => {
self.compression_method = method self.compression_method = method
}, },
None => return Err(TiffError::UnsupportedError( None => return Err(TiffError::UnsupportedError(TiffUnsupportedError::UnknownCompressionMethod))
"Unknown compression method.".to_string()
))
} }
} }
if let Some(val) = try!(self.find_tag_u32(ifd::Tag::SamplesPerPixel)) { if let Some(val) = try!(self.find_tag_u32(ifd::Tag::SamplesPerPixel)) {
@@ -247,9 +239,7 @@ impl<R: Read + Seek> Decoder<R> {
} }
} }
_ => return Err(TiffError::UnsupportedError( _ => return Err(TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedSampleDepth(self.samples)))
format!("{} samples per pixel is supported.", self.samples)
))
} }
Ok(self) Ok(self)
} }
@@ -279,6 +269,16 @@ impl<R: Read + Seek> Decoder<R> {
self.reader.read_u32() self.reader.read_u32()
} }
/// Reads a string
#[inline]
pub fn read_string(&mut self, length: usize) -> Result<String, FromUtf8Error> {
let mut out = String::with_capacity(length);
self.reader.read_to_string(&mut out);
// Strings may be null-terminated, so we trim anything downstream of the null byte
let trimmed = out.bytes().take_while(|&n| n != 0).collect::<Vec<u8>>();
String::from_utf8(trimmed)
}
/// Reads a TIFF IFA offset/value field /// Reads a TIFF IFA offset/value field
#[inline] #[inline]
pub fn read_offset(&mut self) -> Result<[u8; 4], io::Error> { pub fn read_offset(&mut self) -> Result<[u8; 4], io::Error> {
@@ -323,9 +323,7 @@ impl<R: Read + Seek> Decoder<R> {
fn read_ifd(&mut self) -> TiffResult<Directory> { fn read_ifd(&mut self) -> TiffResult<Directory> {
let mut dir: Directory = HashMap::new(); let mut dir: Directory = HashMap::new();
match self.next_ifd { match self.next_ifd {
None => return Err(TiffError::FormatError( None => return Err(TiffError::FormatError(TiffFormatError::ImageFileDirectoryNotFound)),
"Image file directory not found.".to_string())
),
Some(offset) => try!(self.goto_offset(offset)) Some(offset) => try!(self.goto_offset(offset))
} }
for _ in 0..try!(self.read_short()) { for _ in 0..try!(self.read_short()) {
@@ -379,9 +377,7 @@ impl<R: Read + Seek> Decoder<R> {
pub fn get_tag(&mut self, tag: ifd::Tag) -> TiffResult<ifd::Value> { pub fn get_tag(&mut self, tag: ifd::Tag) -> TiffResult<ifd::Value> {
match try!(self.find_tag(tag)) { match try!(self.find_tag(tag)) {
Some(val) => Ok(val), Some(val) => Ok(val),
None => Err(TiffError::FormatError(format!( None => Err(TiffError::FormatError(TiffFormatError::RequiredTagNotFound(tag))),
"Required tag `{:?}` not found.", tag
)))
} }
} }
@@ -414,13 +410,11 @@ impl<R: Read + Seek> Decoder<R> {
let (bytes, reader) = try!(PackBitsReader::new(&mut self.reader, order, length as usize)); let (bytes, reader) = try!(PackBitsReader::new(&mut self.reader, order, length as usize));
(bytes, Box::new(reader)) (bytes, Box::new(reader))
}, },
method => return Err(TiffError::UnsupportedError(format!( method => return Err(TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedCompressionMethod(method)))
"Compression method {:?} is unsupported", method
)))
}; };
Ok(match (color_type, buffer) { Ok(match (color_type, buffer) {
(ColorType:: RGB(8), DecodingBuffer::U8(ref mut buffer)) | (ColorType:: RGB(8), DecodingBuffer::U8(ref mut buffer)) |
(ColorType::RGBA(8), DecodingBuffer::U8(ref mut buffer)) | (ColorType::RGBA(8), DecodingBuffer::U8(ref mut buffer)) |
(ColorType::CMYK(8), DecodingBuffer::U8(ref mut buffer)) => { (ColorType::CMYK(8), DecodingBuffer::U8(ref mut buffer)) => {
try!(reader.read_exact(&mut buffer[..bytes])); try!(reader.read_exact(&mut buffer[..bytes]));
bytes bytes
@@ -448,9 +442,7 @@ impl<R: Read + Seek> Decoder<R> {
} }
bytes bytes
} }
(type_, _) => return Err(TiffError::UnsupportedError(format!( (type_, _) => return Err(TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedColorType(type_))),
"Color type {:?} is unsupported", type_
)))
}) })
} }
@@ -468,18 +460,12 @@ impl<R: Read + Seek> Decoder<R> {
let mut result = match self.bits_per_sample.iter().cloned().max().unwrap_or(8) { let mut result = match self.bits_per_sample.iter().cloned().max().unwrap_or(8) {
n if n <= 8 => DecodingResult::U8(vec![0; buffer_size]), n if n <= 8 => DecodingResult::U8(vec![0; buffer_size]),
n if n <= 16 => DecodingResult::U16(vec![0; buffer_size]), n if n <= 16 => DecodingResult::U16(vec![0; buffer_size]),
n => return Err( n => return Err(TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedBitsPerChannel(n))),
TiffError::UnsupportedError(
format!("{} bits per channel not supported", n)
)
)
}; };
if let Ok(config) = self.get_tag_u32(ifd::Tag::PlanarConfiguration) { if let Ok(config) = self.get_tag_u32(ifd::Tag::PlanarConfiguration) {
match FromPrimitive::from_u32(config) { match FromPrimitive::from_u32(config) {
Some(PlanarConfiguration::Chunky) => {}, Some(PlanarConfiguration::Chunky) => {},
config => return Err(TiffError::UnsupportedError( config => return Err(TiffError::UnsupportedError(TiffUnsupportedError::UnsupportedPlanarConfig(config)))
format!("Unsupported planar configuration “{:?}”.", config)
))
} }
} }
let mut units_read = 0; let mut units_read = 0;
@@ -519,9 +505,7 @@ impl<R: Read + Seek> Decoder<R> {
try!(self.colortype()) try!(self.colortype())
)) ))
}, },
None => return Err(TiffError::FormatError( None => return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor(predictor))),
format!("Unknown predictor “{}” encountered", predictor)
))
} }
} }
Ok(result) Ok(result)
+71 -2
View File
@@ -1,20 +1,83 @@
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::io; use std::io;
use std::string;
use decoder::ifd::{Tag, Value};
use decoder::{PhotometricInterpretation, CompressionMethod, PlanarConfiguration};
use ColorType;
/// Tiff error kinds. /// Tiff error kinds.
#[derive(Debug)] #[derive(Debug)]
pub enum TiffError { pub enum TiffError {
/// The Image is not formatted properly /// The Image is not formatted properly
FormatError(String), FormatError(TiffFormatError),
/// The Decoder does not support this image format /// The Decoder does not support this image format
UnsupportedError(String), UnsupportedError(TiffUnsupportedError),
/// An I/O Error occurred while decoding the image /// An I/O Error occurred while decoding the image
IoError(io::Error), IoError(io::Error),
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TiffFormatError {
TiffSignatureNotFound,
TiffSignatureInvalid,
ImageFileDirectoryNotFound,
RequiredTagNotFound(Tag),
UnknownPredictor(u32),
UnsignedIntegerExpected(Value),
}
impl fmt::Display for TiffFormatError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::TiffFormatError::*;
match *self {
TiffSignatureNotFound => write!(fmt, "TIFF signature not found."),
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
UnknownPredictor(ref predictor) => write!(fmt, "Unknown predictor “{}” encountered", predictor),
UnsignedIntegerExpected(ref val) => write!(fmt, "Expected unsigned integer, {:?} found.", val),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TiffUnsupportedError {
HorizontalPredictor(ColorType),
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
UnknownInterpretation,
UnknownCompressionMethod,
UnsupportedCompressionMethod(CompressionMethod),
UnsupportedSampleDepth(u8),
UnsupportedColorType(ColorType),
UnsupportedBitsPerChannel(u8),
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
UnsupportedDataType,
}
impl fmt::Display for TiffUnsupportedError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::TiffUnsupportedError::*;
match *self {
HorizontalPredictor(color_type) => write!(fmt, "Horizontal predictor for {:?} is unsupported.", color_type),
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => {
write!(fmt, "{:?} with {:?} bits per sample is unsupported", photometric_interpretation, bits_per_sample)
},
UnknownInterpretation => write!(fmt, "The image is using an unknown photometric interpretation."),
UnknownCompressionMethod => write!(fmt, "Unknown compression method."),
UnsupportedCompressionMethod(method) => write!(fmt, "Compression method {:?} is unsupported", method),
UnsupportedSampleDepth(samples) => write!(fmt, "{} samples per pixel is supported.", samples),
UnsupportedColorType(color_type) => write!(fmt, "Color type {:?} is unsupported", color_type),
UnsupportedBitsPerChannel(bits) => write!(fmt, "{} bits per channel not supported", bits),
UnsupportedPlanarConfig(config) => write!(fmt, "Unsupported planar configuration “{:?}”.", config),
UnsupportedDataType => write!(fmt, "Unsupported data type."),
}
}
}
impl fmt::Display for TiffError { impl fmt::Display for TiffError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self { match *self {
@@ -49,5 +112,11 @@ impl From<io::Error> for TiffError {
} }
} }
impl From<string::FromUtf8Error> for TiffError {
fn from(err: string::FromUtf8Error) -> TiffError {
TiffError::FormatError(String::from("Image contains invalid tag."))
}
}
/// Result of an image decoding/encoding process /// Result of an image decoding/encoding process
pub type TiffResult<T> = Result<T, TiffError>; pub type TiffResult<T> = Result<T, TiffError>;
+1 -1
View File
@@ -14,7 +14,7 @@ extern crate num_traits;
pub mod decoder; pub mod decoder;
mod error; mod error;
pub use self::error::{TiffError, TiffResult}; pub use self::error::{TiffError, TiffFormatError, TiffUnsupportedError, TiffResult};
/// An enumeration over supported color types and their bit depths /// An enumeration over supported color types and their bit depths
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
+19 -1
View File
@@ -1,7 +1,7 @@
extern crate tiff; extern crate tiff;
use tiff::ColorType; use tiff::ColorType;
use tiff::decoder::{Decoder, DecodingResult}; use tiff::decoder::{Decoder, DecodingResult, ifd::Tag, ifd::Value};
use std::fs::File; use std::fs::File;
@@ -45,6 +45,24 @@ fn test_rgb_u16()
assert!(img_res.is_ok()); assert!(img_res.is_ok());
} }
#[test]
fn test_string_tags()
{
// these files have null-terminated strings for their Software tag. One has extra bytes after
// the null byte, so we check both to ensure that we're truncating properly
let filenames = vec!["minisblack-1c-16b.tiff", "rgb-3c-16b.tiff"];
for filename in filenames.iter() {
let path = format!("./tests/images/{}", filename);
let img_file = File::open(path).expect("can't open file");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let software= decoder.get_tag(Tag::Software).unwrap();
match software {
Value::Ascii(s) => assert_eq!(s, String::from("GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/")),
_ => assert!(false)
};
}
}
// TODO: GrayA support // TODO: GrayA support
//#[test] //#[test]
//fn test_gray_alpha_u8() //fn test_gray_alpha_u8()