Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcfcecb2df | ||
|
|
0da9770916 | ||
|
|
c30ea911a2 | ||
|
|
da07c0f4f5 | ||
|
|
289fab2fcd | ||
|
|
21252d9d89 | ||
|
|
ffaccf2b52 | ||
|
|
4dea7c4c81 | ||
|
|
aa42fcb1bb | ||
|
|
1f7b25ef55 | ||
|
|
6c65e66440 | ||
|
|
a733ff13d9 | ||
|
|
b1cd459330 | ||
|
|
4f1c88e35f | ||
|
|
7307e96782 | ||
|
|
a08604b015 | ||
|
|
9333d99c92 |
No files matched your search
+24
@@ -1,3 +1,27 @@
|
||||
# Version 0.9.0
|
||||
|
||||
New features:
|
||||
* Added support for photometric interpretation `YCbCr` and added related
|
||||
`ColorType`.
|
||||
|
||||
Fixes:
|
||||
* Decoding tiled images calculates padding correctly when image width or height
|
||||
is a multiple of tile size. It could previously corrupt the last tile per row
|
||||
by skipping over data.
|
||||
|
||||
# Version 0.8.1
|
||||
|
||||
Changes:
|
||||
* The jpeg decoder gained to ability to utilize the Photometric Interpretation
|
||||
directly instead of relying on a custom APP segment.
|
||||
|
||||
Fixes:
|
||||
* A spurious error within the PackBits decoder lead to the incorrect results
|
||||
(wrong bits or errors), based on the maximum size of reads from the
|
||||
underlying reader.
|
||||
* Removed a panic path in jpeg decoding, when a feature such as photometric
|
||||
interpretation is not supported. An error is returned instead.
|
||||
|
||||
# Version 0.8.0
|
||||
|
||||
Changes:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
edition = "2018"
|
||||
resolver = "2"
|
||||
|
||||
|
||||
+51
-22
@@ -1,8 +1,5 @@
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::{
|
||||
add_app14segment, ByteOrder, DeflateReader, JpegReader, JpegTagApp14Transform, LZWReader,
|
||||
PackBitsReader,
|
||||
};
|
||||
use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader};
|
||||
use super::tag_reader::TagReader;
|
||||
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
||||
use super::{stream::SmartReader, ChunkType};
|
||||
@@ -35,12 +32,11 @@ impl TileAttributes {
|
||||
(self.image_height + self.tile_length - 1) / self.tile_length
|
||||
}
|
||||
fn padding_right(&self) -> usize {
|
||||
self.tile_width - self.image_width % self.tile_width
|
||||
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
|
||||
}
|
||||
fn padding_down(&self) -> usize {
|
||||
self.tile_length - self.image_height % self.tile_length
|
||||
(self.tile_length - self.image_height % self.tile_length) % self.tile_length
|
||||
}
|
||||
|
||||
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
|
||||
let row = tile / self.tiles_across();
|
||||
let column = tile % self.tiles_across();
|
||||
@@ -121,7 +117,7 @@ impl Image {
|
||||
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
||||
&& ifd.contains_key(&Tag::JPEGTables)
|
||||
{
|
||||
let mut vec = tag_reader
|
||||
let vec = tag_reader
|
||||
.find_tag(Tag::JPEGTables)?
|
||||
.unwrap()
|
||||
.into_u8_vec()?;
|
||||
@@ -131,11 +127,6 @@ impl Image {
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: Can we avoid this somehow?
|
||||
if photometric_interpretation == PhotometricInterpretation::RGB {
|
||||
add_app14segment(&mut vec, JpegTagApp14Transform::App14TransformUnknown)
|
||||
}
|
||||
|
||||
Some(Arc::new(vec))
|
||||
} else {
|
||||
None
|
||||
@@ -314,6 +305,15 @@ impl Image {
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::YCbCr => match self.bits_per_sample[..] {
|
||||
[y, cb, cr] if [y, y] == [cb, cr] => Ok(ColorType::YCbCr(y)),
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
self.bits_per_sample.clone(),
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
||||
if self.bits_per_sample.len() == 1 =>
|
||||
{
|
||||
@@ -332,6 +332,7 @@ impl Image {
|
||||
|
||||
fn create_reader<'r, R: 'r + Read>(
|
||||
reader: R,
|
||||
photometric_interpretation: PhotometricInterpretation,
|
||||
compression_method: CompressionMethod,
|
||||
compressed_length: u64,
|
||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||
@@ -354,14 +355,36 @@ impl Image {
|
||||
|
||||
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
|
||||
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||
let data = match decoder.decode() {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
return Err(TiffError::FormatError(TiffFormatError::Format(
|
||||
e.to_string(),
|
||||
)))
|
||||
|
||||
match photometric_interpretation {
|
||||
PhotometricInterpretation::RGB => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::RGB)
|
||||
}
|
||||
};
|
||||
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))
|
||||
}
|
||||
@@ -450,6 +473,7 @@ impl Image {
|
||||
(ColorType::RGB(n), _)
|
||||
| (ColorType::RGBA(n), _)
|
||||
| (ColorType::CMYK(n), _)
|
||||
| (ColorType::YCbCr(n), _)
|
||||
| (ColorType::Gray(n), _)
|
||||
if usize::from(n) == buffer.byte_len() * 8 => {}
|
||||
(ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor {
|
||||
@@ -509,8 +533,13 @@ impl Image {
|
||||
let padding_right = chunk_dims.0 - data_dims.0;
|
||||
|
||||
let jpeg_tables = self.jpeg_tables.clone();
|
||||
let mut reader =
|
||||
Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?;
|
||||
let mut reader = Self::create_reader(
|
||||
reader,
|
||||
photometric_interpretation,
|
||||
compression_method,
|
||||
*compressed_bytes,
|
||||
jpeg_tables,
|
||||
)?;
|
||||
|
||||
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;
|
||||
|
||||
+7
-59
@@ -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 {
|
||||
// #[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
@@ -384,23 +332,23 @@ impl<R: Read> Read for PackBitsReader<R> {
|
||||
}
|
||||
|
||||
let length = buf.len().min(self.count);
|
||||
match self.state {
|
||||
PackBitsReaderState::Literal => {
|
||||
self.reader.read(&mut buf[..length])?;
|
||||
}
|
||||
let actual = match self.state {
|
||||
PackBitsReaderState::Literal => self.reader.read(&mut buf[..length])?,
|
||||
PackBitsReaderState::Repeat { value } => {
|
||||
for b in &mut buf[..length] {
|
||||
*b = value;
|
||||
}
|
||||
|
||||
length
|
||||
}
|
||||
PackBitsReaderState::Header => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
self.count -= length;
|
||||
self.count -= actual;
|
||||
if self.count == 0 {
|
||||
self.state = PackBitsReaderState::Header;
|
||||
}
|
||||
return Ok(length);
|
||||
return Ok(actual);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -235,3 +235,11 @@ impl ColorType for CMYK64Float {
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
}
|
||||
|
||||
pub struct YCbCr8;
|
||||
impl ColorType for YCbCr8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
use std::str;
|
||||
use std::string;
|
||||
use std::sync::Arc;
|
||||
|
||||
use jpeg::UnsupportedFeature;
|
||||
|
||||
use crate::decoder::{ifd::Value, ChunkType};
|
||||
use crate::tags::{
|
||||
@@ -69,6 +73,7 @@ pub enum TiffFormatError {
|
||||
RequiredTagEmpty(Tag),
|
||||
StripTileTagConflict,
|
||||
CycleInOffsets,
|
||||
JpegDecoder(JpegDecoderError),
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffFormatError {
|
||||
@@ -119,6 +124,7 @@ impl fmt::Display for TiffFormatError {
|
||||
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."),
|
||||
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),
|
||||
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
||||
UnsupportedDataType,
|
||||
UnsupportedInterpretation(PhotometricInterpretation),
|
||||
UnsupportedJpegFeature(UnsupportedFeature),
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffUnsupportedError {
|
||||
@@ -191,6 +199,16 @@ impl fmt::Display for TiffUnsupportedError {
|
||||
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||
}
|
||||
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
|
||||
pub type TiffResult<T> = Result<T, TiffError>;
|
||||
@@ -37,4 +37,7 @@ pub enum ColorType {
|
||||
|
||||
/// Pixel is CMYK
|
||||
CMYK(u8),
|
||||
|
||||
/// Pixel is YCbCr
|
||||
YCbCr(u8),
|
||||
}
|
||||
@@ -264,6 +264,11 @@ fn test_gray_f64_roundtrip() {
|
||||
test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ycbcr_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||
}
|
||||
|
||||
trait AssertDecode {
|
||||
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user