Merge pull request #170 from AlanRace/jpeg-unwrap
Removed `unwrap` and set colour transform with Jpeg compressed tiffs
This commit is contained in:
3 files changed
+94
-71
No files matched your search
+39
-19
@@ -1,8 +1,5 @@
|
|||||||
use super::ifd::{Directory, Value};
|
use super::ifd::{Directory, Value};
|
||||||
use super::stream::{
|
use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader};
|
||||||
add_app14segment, ByteOrder, DeflateReader, JpegReader, JpegTagApp14Transform, LZWReader,
|
|
||||||
PackBitsReader,
|
|
||||||
};
|
|
||||||
use super::tag_reader::TagReader;
|
use super::tag_reader::TagReader;
|
||||||
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
||||||
use super::{stream::SmartReader, ChunkType};
|
use super::{stream::SmartReader, ChunkType};
|
||||||
@@ -121,7 +118,7 @@ impl Image {
|
|||||||
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
||||||
&& ifd.contains_key(&Tag::JPEGTables)
|
&& ifd.contains_key(&Tag::JPEGTables)
|
||||||
{
|
{
|
||||||
let mut vec = tag_reader
|
let vec = tag_reader
|
||||||
.find_tag(Tag::JPEGTables)?
|
.find_tag(Tag::JPEGTables)?
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_u8_vec()?;
|
.into_u8_vec()?;
|
||||||
@@ -131,11 +128,6 @@ impl Image {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Can we avoid this somehow?
|
|
||||||
if photometric_interpretation == PhotometricInterpretation::RGB {
|
|
||||||
add_app14segment(&mut vec, JpegTagApp14Transform::App14TransformUnknown)
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Arc::new(vec))
|
Some(Arc::new(vec))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -332,6 +324,7 @@ impl Image {
|
|||||||
|
|
||||||
fn create_reader<'r, R: 'r + Read>(
|
fn create_reader<'r, R: 'r + Read>(
|
||||||
reader: R,
|
reader: R,
|
||||||
|
photometric_interpretation: PhotometricInterpretation,
|
||||||
compression_method: CompressionMethod,
|
compression_method: CompressionMethod,
|
||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
@@ -354,14 +347,36 @@ impl Image {
|
|||||||
|
|
||||||
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
|
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
|
||||||
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||||
let data = match decoder.decode() {
|
|
||||||
Ok(data) => data,
|
match photometric_interpretation {
|
||||||
Err(e) => {
|
PhotometricInterpretation::RGB => {
|
||||||
return Err(TiffError::FormatError(TiffFormatError::Format(
|
decoder.set_color_transform(jpeg::ColorTransform::RGB)
|
||||||
e.to_string(),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
};
|
PhotometricInterpretation::WhiteIsZero => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::BlackIsZero => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::TransparencyMask => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::CMYK => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::CMYK)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::YCbCr => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
|
||||||
|
}
|
||||||
|
photometric_interpretation => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedInterpretation(
|
||||||
|
photometric_interpretation,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = decoder.decode()?;
|
||||||
|
|
||||||
Box::new(Cursor::new(data))
|
Box::new(Cursor::new(data))
|
||||||
}
|
}
|
||||||
@@ -509,8 +524,13 @@ impl Image {
|
|||||||
let padding_right = chunk_dims.0 - data_dims.0;
|
let padding_right = chunk_dims.0 - data_dims.0;
|
||||||
|
|
||||||
let jpeg_tables = self.jpeg_tables.clone();
|
let jpeg_tables = self.jpeg_tables.clone();
|
||||||
let mut reader =
|
let mut reader = Self::create_reader(
|
||||||
Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?;
|
reader,
|
||||||
|
photometric_interpretation,
|
||||||
|
compression_method,
|
||||||
|
*compressed_bytes,
|
||||||
|
jpeg_tables,
|
||||||
|
)?;
|
||||||
|
|
||||||
if output_width == data_dims.0 as usize && padding_right == 0 {
|
if output_width == data_dims.0 as usize && padding_right == 0 {
|
||||||
let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples;
|
let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples;
|
||||||
|
|||||||
@@ -250,58 +250,6 @@ impl JpegReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
pub(crate) enum JpegTagApp14Transform {
|
|
||||||
// App14TransformYCCK = 2,
|
|
||||||
// App14TransformYCbCr = 1,
|
|
||||||
App14TransformUnknown = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add_app14segment(jpeg_tables: &mut Vec<u8>, transform: JpegTagApp14Transform) {
|
|
||||||
// Add JPEG Tag APP14 Adobe segment to jpeg_tables.
|
|
||||||
// This segment stores image encoding information for DCT filters.
|
|
||||||
// When `transform` value is 0 which is defined as Unknown, jpeg-decoder interpret the
|
|
||||||
// color-space of image as RGB(3 channels) or CMYK(4).
|
|
||||||
let mut app14_offset = None;
|
|
||||||
let mut dht_offset = None;
|
|
||||||
for (offset, window) in jpeg_tables.windows(2).enumerate() {
|
|
||||||
if window == [0xff, 0xee] {
|
|
||||||
app14_offset = Some(offset);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if window == [0xff, 0xc4] {
|
|
||||||
dht_offset = Some(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match (app14_offset, dht_offset) {
|
|
||||||
(Some(i), _) => {
|
|
||||||
jpeg_tables[i + 16] = transform as u8;
|
|
||||||
}
|
|
||||||
(None, Some(sos_offset)) => {
|
|
||||||
let app14segment: [u8; 16] = [
|
|
||||||
0xff,
|
|
||||||
0xee,
|
|
||||||
0x00,
|
|
||||||
0x0e,
|
|
||||||
0x41,
|
|
||||||
0x64,
|
|
||||||
0x6f,
|
|
||||||
0x62,
|
|
||||||
0x65,
|
|
||||||
0x00,
|
|
||||||
0x64,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
transform as u8,
|
|
||||||
];
|
|
||||||
jpeg_tables.splice(sos_offset..sos_offset, app14segment.iter().copied());
|
|
||||||
}
|
|
||||||
(None, None) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for JpegReader {
|
impl Read for JpegReader {
|
||||||
// #[inline]
|
// #[inline]
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::string;
|
use std::string;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use jpeg::UnsupportedFeature;
|
||||||
|
|
||||||
use crate::decoder::{ifd::Value, ChunkType};
|
use crate::decoder::{ifd::Value, ChunkType};
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
@@ -69,6 +73,7 @@ pub enum TiffFormatError {
|
|||||||
RequiredTagEmpty(Tag),
|
RequiredTagEmpty(Tag),
|
||||||
StripTileTagConflict,
|
StripTileTagConflict,
|
||||||
CycleInOffsets,
|
CycleInOffsets,
|
||||||
|
JpegDecoder(JpegDecoderError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TiffFormatError {
|
impl fmt::Display for TiffFormatError {
|
||||||
@@ -119,6 +124,7 @@ impl fmt::Display for TiffFormatError {
|
|||||||
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||||
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
||||||
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
||||||
|
JpegDecoder(ref error) => write!(fmt, "{}", error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +152,8 @@ pub enum TiffUnsupportedError {
|
|||||||
UnsupportedBitsPerChannel(u8),
|
UnsupportedBitsPerChannel(u8),
|
||||||
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
||||||
UnsupportedDataType,
|
UnsupportedDataType,
|
||||||
|
UnsupportedInterpretation(PhotometricInterpretation),
|
||||||
|
UnsupportedJpegFeature(UnsupportedFeature),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TiffUnsupportedError {
|
impl fmt::Display for TiffUnsupportedError {
|
||||||
@@ -191,6 +199,16 @@ impl fmt::Display for TiffUnsupportedError {
|
|||||||
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||||
}
|
}
|
||||||
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
||||||
|
UnsupportedInterpretation(interpretation) => {
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"Unsupported photometric interpretation \"{:?}\".",
|
||||||
|
interpretation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
UnsupportedJpegFeature(ref unsupported_feature) => {
|
||||||
|
write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,5 +328,42 @@ impl From<LzwError> for TiffError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JpegDecoderError {
|
||||||
|
inner: Arc<jpeg::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JpegDecoderError {
|
||||||
|
fn new(error: jpeg::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for JpegDecoderError {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
Arc::ptr_eq(&self.inner, &other.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for JpegDecoderError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JpegDecoderError> for TiffError {
|
||||||
|
fn from(error: JpegDecoderError) -> Self {
|
||||||
|
TiffError::FormatError(TiffFormatError::JpegDecoder(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jpeg::Error> for TiffError {
|
||||||
|
fn from(error: jpeg::Error) -> Self {
|
||||||
|
JpegDecoderError::new(error).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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>;
|
||||||
Reference in new issue
Block a user