7 files changed
+840
No files matched your search
@@ -19,3 +19,6 @@ byteorder = "1.2"
|
||||
lzw = "0.10"
|
||||
num-derive = "0.2"
|
||||
num-traits = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0"
|
||||
@@ -26,6 +26,12 @@ macro_rules! tags {
|
||||
Tag::Unknown(n)
|
||||
}
|
||||
}
|
||||
pub fn to_u16(&self) -> u16 {
|
||||
match self {
|
||||
$( Tag::$tag => $val, )*
|
||||
Tag::Unknown(n) => *n,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
use crate::decoder::PhotometricInterpretation;
|
||||
|
||||
/// Trait for different colortypes that can be encoded.
|
||||
pub trait ColorType {
|
||||
/// The type of each sample of this colortype
|
||||
type Inner: super::TiffValue;
|
||||
/// The value of the tiff tag `PhotometricInterpretation`
|
||||
const TIFF_VALUE: PhotometricInterpretation;
|
||||
/// The value of the tiff tag `BitsPerSample`
|
||||
const BITS_PER_SAMPLE: &'static [u16];
|
||||
}
|
||||
|
||||
pub struct Gray8;
|
||||
impl ColorType for Gray8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||
}
|
||||
|
||||
pub struct Gray16;
|
||||
impl ColorType for Gray16 {
|
||||
type Inner = u16;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||
}
|
||||
|
||||
pub struct RGB8;
|
||||
impl ColorType for RGB8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
}
|
||||
|
||||
pub struct RGB16;
|
||||
impl ColorType for RGB16 {
|
||||
type Inner = u16;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
|
||||
}
|
||||
|
||||
pub struct RGBA8;
|
||||
impl ColorType for RGBA8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
}
|
||||
|
||||
pub struct RGBA16;
|
||||
impl ColorType for RGBA16 {
|
||||
type Inner = u16;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||
}
|
||||
|
||||
pub struct CMYK8;
|
||||
impl ColorType for CMYK8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
use std::io::{Write, Seek};
|
||||
use std::collections::BTreeMap;
|
||||
use byteorder::{NativeEndian};
|
||||
|
||||
use crate::decoder::ifd::{self, Tag};
|
||||
use crate::error::{TiffResult, TiffFormatError, TiffError};
|
||||
|
||||
mod writer;
|
||||
pub mod colortype;
|
||||
|
||||
use self::writer::*;
|
||||
use self::colortype::*;
|
||||
|
||||
/// Type to represent tiff values of type `RATIONAL`
|
||||
#[derive(Clone)]
|
||||
pub struct Rational {
|
||||
pub n: u32,
|
||||
pub d: u32,
|
||||
}
|
||||
|
||||
/// Type to represent resolution units
|
||||
pub enum ResolutionUnit {
|
||||
None = 1,
|
||||
Inch = 2,
|
||||
Centimeter = 3,
|
||||
}
|
||||
|
||||
/// Trait for types that can be encoded in a tiff file
|
||||
pub trait TiffValue {
|
||||
const BYTE_LEN: u32;
|
||||
const FIELD_TYPE: ifd::Type;
|
||||
fn count(&self) -> u32;
|
||||
fn bytes(&self) -> u32 {
|
||||
self.count() * Self::BYTE_LEN
|
||||
}
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()>;
|
||||
}
|
||||
|
||||
impl TiffValue for [u8] {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::BYTE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_bytes(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u16] {
|
||||
const BYTE_LEN: u32 = 2;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::SHORT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
// We write using nativeedian so this sould be safe
|
||||
let slice = unsafe {
|
||||
std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len()*2)
|
||||
};
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u32] {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::LONG;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
// We write using nativeedian so this sould be safe
|
||||
let slice = unsafe {
|
||||
std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len()*4)
|
||||
};
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [Rational] {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
for x in self {
|
||||
x.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u8 {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::BYTE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u16 {
|
||||
const BYTE_LEN: u32 = 2;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::SHORT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u16(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u32 {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::LONG;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for Rational {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u32(self.n)?;
|
||||
writer.write_u32(self.d)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for str {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: ifd::Type = ifd::Type::ASCII;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32 + 1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||
writer.write_bytes(self.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
Ok(())
|
||||
}
|
||||
else {
|
||||
Err(TiffError::FormatError(TiffFormatError::InvalidTag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||
const BYTE_LEN: u32 = T::BYTE_LEN;
|
||||
const FIELD_TYPE: ifd::Type = T::FIELD_TYPE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
(*self).count()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
(*self).write(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tiff encoder.
|
||||
///
|
||||
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
|
||||
/// to encode tiff ifd directories with images.
|
||||
///
|
||||
/// See `DirectoryEncoder` and `ImageEncoder`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # extern crate tempfile;
|
||||
/// # let mut file = tempfile::tempfile().unwrap();
|
||||
/// # let image_data = vec![0; 100*100*3];
|
||||
/// use tiff::encoder::*;
|
||||
///
|
||||
/// let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
///
|
||||
/// tiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap();
|
||||
/// ```
|
||||
pub struct TiffEncoder<W> {
|
||||
writer: TiffWriter<W>,
|
||||
}
|
||||
|
||||
impl<W: Write + Seek> TiffEncoder<W> {
|
||||
pub fn new(writer: W) -> TiffResult<TiffEncoder<W>> {
|
||||
let mut encoder = TiffEncoder {
|
||||
writer: TiffWriter::new(writer),
|
||||
};
|
||||
|
||||
NativeEndian::write_header(&mut encoder.writer)?;
|
||||
|
||||
Ok(encoder)
|
||||
}
|
||||
|
||||
/// Create a `DirectoryEncoder` to encode an ifd directory.
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W>> {
|
||||
DirectoryEncoder::new(&mut self.writer)
|
||||
}
|
||||
|
||||
/// Create an 'ImageEncoder' to encode an image one slice at a time.
|
||||
pub fn new_image<C: ColorType>(&mut self, width: u32, height: u32) -> TiffResult<ImageEncoder<W, C>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
ImageEncoder::new(encoder, width, height)
|
||||
}
|
||||
|
||||
/// Convenience function to write an entire image from memory.
|
||||
pub fn write_image<C: ColorType>(&mut self, width: u32, height: u32, data: &[C::Inner]) -> TiffResult<()> where [C::Inner]: TiffValue {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let mut image: ImageEncoder<W, C> = ImageEncoder::new(encoder, width, height).unwrap();
|
||||
|
||||
let mut idx = 0;
|
||||
while image.next_strip_sample_count() > 0 {
|
||||
let sample_count = image.next_strip_sample_count() as usize;
|
||||
image.write_strip(&data[idx..idx+sample_count]).unwrap();
|
||||
idx += sample_count;
|
||||
}
|
||||
image.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Low level interface to encode ifd directories.
|
||||
///
|
||||
/// You should call `finish` on this when you are finished with it.
|
||||
/// Encoding can silently fail while this is dropping.
|
||||
pub struct DirectoryEncoder<'a, W: Write + Seek> {
|
||||
writer: &'a mut TiffWriter<W>,
|
||||
dropped: bool,
|
||||
// We use BTreeMap to make sure tags are written in correct order
|
||||
ifd_pointer_pos: u64,
|
||||
ifd: BTreeMap<u16, (u16, u32, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> {
|
||||
fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<DirectoryEncoder<'a, W>> {
|
||||
writer.pad_word_boundary()?;
|
||||
let ifd_pointer_pos = writer.offset();
|
||||
writer.write_u32(0)?;
|
||||
Ok(DirectoryEncoder {
|
||||
writer,
|
||||
dropped: false,
|
||||
ifd_pointer_pos,
|
||||
ifd: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a single ifd tag.
|
||||
pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) {
|
||||
let len = <T>::BYTE_LEN * value.count();
|
||||
let mut bytes = Vec::with_capacity(len as usize);
|
||||
{
|
||||
let mut writer = TiffWriter::new(&mut bytes);
|
||||
value.write(&mut writer).unwrap();
|
||||
}
|
||||
|
||||
self.ifd.insert(tag.to_u16(), (<T>::FIELD_TYPE as u16, value.count(), bytes));
|
||||
}
|
||||
|
||||
fn write_directory(&mut self) -> TiffResult<u64> {
|
||||
// Start by writing out all values
|
||||
for (_, _, ref mut bytes) in self.ifd.values_mut() {
|
||||
if bytes.len() > 4 {
|
||||
let offset = self.writer.offset();
|
||||
self.writer.write_bytes(bytes)?;
|
||||
*bytes = vec![0, 0, 0, 0];
|
||||
let mut writer = TiffWriter::new(bytes as &mut [u8]);
|
||||
writer.write_u32(offset as u32)?;
|
||||
}
|
||||
else {
|
||||
while bytes.len() < 4 {
|
||||
bytes.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let offset = self.writer.offset();
|
||||
|
||||
self.writer.write_u16(self.ifd.len() as u16)?;
|
||||
for (tag, (field_type, count, offset)) in self.ifd.iter() {
|
||||
self.writer.write_u16(*tag)?;
|
||||
self.writer.write_u16(*field_type)?;
|
||||
self.writer.write_u32(*count)?;
|
||||
self.writer.write_bytes(offset)?;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Write some data to the tiff file, the offset of the data is returned.
|
||||
///
|
||||
/// This could be used to write tiff strips.
|
||||
pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
|
||||
let offset = self.writer.offset();
|
||||
value.write(&mut self.writer)?;
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
let ifd_pointer = self.write_directory()?;
|
||||
let curr_pos = self.writer.offset();
|
||||
|
||||
self.writer.goto_offset(self.ifd_pointer_pos)?;
|
||||
self.writer.write_u32(ifd_pointer as u32)?;
|
||||
self.writer.goto_offset(curr_pos)?;
|
||||
self.writer.write_u32(0)?;
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write out the ifd directory.
|
||||
pub fn finish(mut self) -> TiffResult<()> {
|
||||
self.finish_internal()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let _ = self.finish_internal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Type to encode images strip by strip.
|
||||
///
|
||||
/// You should call `finish` on this when you are finished with it.
|
||||
/// Encoding can silently fail while this is dropping.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # extern crate tempfile;
|
||||
/// # let mut file = tempfile::tempfile().unwrap();
|
||||
/// # let image_data = vec![0; 100*100*3];
|
||||
/// use tiff::encoder::*;
|
||||
///
|
||||
/// let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
/// let mut image = tiff.new_image::<colortype::RGB8>(100, 100).unwrap();
|
||||
///
|
||||
/// let mut idx = 0;
|
||||
/// while image.next_strip_sample_count() > 0 {
|
||||
/// let sample_count = image.next_strip_sample_count() as usize;
|
||||
/// image.write_strip(&image_data[idx..idx+sample_count]).unwrap();
|
||||
/// idx += sample_count;
|
||||
/// }
|
||||
/// image.finish().unwrap();
|
||||
/// ```
|
||||
pub struct ImageEncoder<'a, W: Write + Seek, C: ColorType> {
|
||||
encoder: DirectoryEncoder<'a, W>,
|
||||
strip_idx: u64,
|
||||
strip_count: u64,
|
||||
row_samples: u64,
|
||||
height: u32,
|
||||
rows_per_strip: u64,
|
||||
strip_offsets: Vec<u32>,
|
||||
strip_byte_count: Vec<u32>,
|
||||
dropped: bool,
|
||||
_phantom: std::marker::PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
fn new(mut encoder: DirectoryEncoder<'a, W>, width: u32, height: u32) -> TiffResult<ImageEncoder<'a, W, T>> {
|
||||
let row_samples = u64::from(width) * <T>::BITS_PER_SAMPLE.len() as u64;
|
||||
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
||||
|
||||
// As per tiff spec each strip should be about 8k long
|
||||
let rows_per_strip = (8000 + row_bytes - 1) / row_bytes;
|
||||
|
||||
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
|
||||
|
||||
encoder.write_tag(Tag::ImageWidth, width);
|
||||
encoder.write_tag(Tag::ImageLength, height);
|
||||
encoder.write_tag(Tag::Compression, 1u16);
|
||||
|
||||
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE);
|
||||
encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE as u16);
|
||||
|
||||
encoder.write_tag(Tag::RowsPerStrip, rows_per_strip as u32);
|
||||
|
||||
encoder.write_tag(Tag::SamplesPerPixel, <T>::BITS_PER_SAMPLE.len() as u16);
|
||||
encoder.write_tag(Tag::XResolution, Rational {n: 1, d: 1});
|
||||
encoder.write_tag(Tag::YResolution, Rational {n: 1, d: 1});
|
||||
encoder.write_tag(Tag::ResolutionUnit, 1u16);
|
||||
|
||||
Ok(ImageEncoder {
|
||||
encoder,
|
||||
strip_count,
|
||||
strip_idx: 0,
|
||||
row_samples,
|
||||
rows_per_strip,
|
||||
height,
|
||||
strip_offsets: Vec::new(),
|
||||
strip_byte_count: Vec::new(),
|
||||
dropped: false,
|
||||
_phantom: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Number of samples the next strip should have.
|
||||
pub fn next_strip_sample_count(&self) -> u64 {
|
||||
if self.strip_idx >= self.strip_count {
|
||||
return 0
|
||||
}
|
||||
|
||||
let start_row = std::cmp::min(u64::from(self.height), self.strip_idx * self.rows_per_strip);
|
||||
let end_row = std::cmp::min(u64::from(self.height), (self.strip_idx+1)*self.rows_per_strip);
|
||||
|
||||
(end_row - start_row) * self.row_samples
|
||||
}
|
||||
|
||||
/// Write a single strip.
|
||||
pub fn write_strip(&mut self, value: &[T::Inner]) -> TiffResult<()> where [T::Inner]: TiffValue {
|
||||
// TODO: Compression
|
||||
let samples = self.next_strip_sample_count();
|
||||
assert_eq!(value.len() as u64, samples);
|
||||
|
||||
let offset = self.encoder.write_data(value)?;
|
||||
self.strip_offsets.push(offset as u32);
|
||||
self.strip_byte_count.push(value.bytes() as u32);
|
||||
|
||||
self.strip_idx += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set image resolution
|
||||
pub fn resolution(&mut self, unit: ResolutionUnit, value: Rational) {
|
||||
self.encoder.write_tag(Tag::ResolutionUnit, unit as u16);
|
||||
self.encoder.write_tag(Tag::XResolution, value.clone());
|
||||
self.encoder.write_tag(Tag::YResolution, value);
|
||||
}
|
||||
|
||||
/// Set image resolution unit
|
||||
pub fn resolution_unit(&mut self, unit: ResolutionUnit) {
|
||||
self.encoder.write_tag(Tag::ResolutionUnit, unit as u16);
|
||||
}
|
||||
|
||||
/// Set image x-resolution
|
||||
pub fn x_resolution(&mut self, value: Rational) {
|
||||
self.encoder.write_tag(Tag::XResolution, value);
|
||||
}
|
||||
|
||||
/// Set image y-resolution
|
||||
pub fn y_resolution(&mut self, value: Rational) {
|
||||
self.encoder.write_tag(Tag::YResolution, value);
|
||||
}
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
self.encoder.write_tag(Tag::StripOffsets, &*self.strip_offsets);
|
||||
self.encoder.write_tag(Tag::StripByteCounts, &*self.strip_byte_count);
|
||||
self.dropped = true;
|
||||
|
||||
self.encoder.finish_internal()
|
||||
}
|
||||
|
||||
/// Get a reference of the underlying `DirectoryEncoder`
|
||||
pub fn encoder(&mut self) -> &mut DirectoryEncoder<'a, W> {
|
||||
&mut self.encoder
|
||||
}
|
||||
|
||||
/// Write out image and ifd directory.
|
||||
pub fn finish(mut self) -> TiffResult<()> {
|
||||
self.finish_internal()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Write + Seek, C: ColorType> Drop for ImageEncoder<'a, W, C> {
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let _ = self.finish_internal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
use std::io::{self, Write, Seek, SeekFrom};
|
||||
use byteorder::{ByteOrder, WriteBytesExt, BigEndian, LittleEndian, NativeEndian};
|
||||
use crate::error::TiffResult;
|
||||
|
||||
pub trait TiffByteOrder: ByteOrder {
|
||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>;
|
||||
}
|
||||
|
||||
impl TiffByteOrder for LittleEndian {
|
||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.writer.write_u16::<LittleEndian>(0x4949)?;
|
||||
writer.writer.write_u16::<LittleEndian>(42)?;
|
||||
writer.offset += 4;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffByteOrder for BigEndian {
|
||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.writer.write_u16::<BigEndian>(0x4d4d)?;
|
||||
writer.writer.write_u16::<BigEndian>(42)?;
|
||||
writer.offset += 4;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TiffWriter<W> {
|
||||
writer: W,
|
||||
offset: u64,
|
||||
}
|
||||
|
||||
impl<W: Write> TiffWriter<W> {
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self {
|
||||
writer,
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> u64 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
|
||||
self.writer.write_all(bytes)?;
|
||||
self.offset += bytes.len() as u64;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
|
||||
self.writer.write_u8(n)?;
|
||||
self.offset += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
|
||||
self.writer.write_u16::<NativeEndian>(n)?;
|
||||
self.offset += 2;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
|
||||
self.writer.write_u32::<NativeEndian>(n)?;
|
||||
self.offset += 4;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
|
||||
self.writer.write_u64::<NativeEndian>(n)?;
|
||||
self.offset += 8;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn pad_word_boundary(&mut self) -> Result<(), io::Error> {
|
||||
if self.offset % 4 != 0 {
|
||||
let padding = [0, 0, 0];
|
||||
let padd_len = 4 - (self.offset % 4);
|
||||
self.writer.write_all(&padding[..padd_len as usize])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Seek> TiffWriter<W> {
|
||||
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
|
||||
self.offset = offset;
|
||||
self.writer.seek(SeekFrom::Start(offset as u64))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ extern crate lzw;
|
||||
extern crate num_traits;
|
||||
|
||||
pub mod decoder;
|
||||
pub mod encoder;
|
||||
mod error;
|
||||
|
||||
pub use self::error::{TiffError, TiffFormatError, TiffUnsupportedError, TiffResult};
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
extern crate tiff;
|
||||
extern crate tempfile;
|
||||
|
||||
use tiff::ColorType;
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::encoder::{colortype, TiffEncoder};
|
||||
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let mut image_data = Vec::new();
|
||||
for x in 0..100 {
|
||||
for y in 0..100u8 {
|
||||
let val = x + y;
|
||||
image_data.push(val);
|
||||
image_data.push(val);
|
||||
image_data.push(val);
|
||||
}
|
||||
}
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
|
||||
tiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap();
|
||||
}
|
||||
{
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||
assert_eq!(image_data, img_res);
|
||||
}
|
||||
else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_gray_u8_roundtrip()
|
||||
{
|
||||
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 image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::U8(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
|
||||
let (width, height) = decoder.dimensions().unwrap();
|
||||
tiff.write_image::<colortype::Gray8>(width, height, &image_data).unwrap();
|
||||
}
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||
assert_eq!(image_data, img_res);
|
||||
}
|
||||
else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u8_roundtrip()
|
||||
{
|
||||
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 image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::U8(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
|
||||
let (width, height) = decoder.dimensions().unwrap();
|
||||
tiff.write_image::<colortype::RGB8>(width, height, &image_data).unwrap();
|
||||
}
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||
assert_eq!(image_data, img_res);
|
||||
}
|
||||
else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u16_roundtrip()
|
||||
{
|
||||
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 image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::U16(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
|
||||
let (width, height) = decoder.dimensions().unwrap();
|
||||
tiff.write_image::<colortype::Gray16>(width, height, &image_data).unwrap();
|
||||
}
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
if let DecodingResult::U16(img_res) = decoder.read_image().unwrap() {
|
||||
assert_eq!(image_data, img_res);
|
||||
}
|
||||
else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u16_roundtrip()
|
||||
{
|
||||
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());
|
||||
|
||||
let image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::U16(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
|
||||
let (width, height) = decoder.dimensions().unwrap();
|
||||
tiff.write_image::<colortype::RGB16>(width, height, &image_data).unwrap();
|
||||
}
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
if let DecodingResult::U16(img_res) = decoder.read_image().unwrap() {
|
||||
assert_eq!(image_data, img_res);
|
||||
}
|
||||
else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user