Here is a minimal example of how I tag images using the latest changes, in both TIFFs and JPEGs.
https://gist.github.com/spoutn1k/4f27fecacc6c9284bb23a9347f0af29c
The main takeaways are:
- In the interest of sharing code, exporting exif data as a block is not crazy relevant. I create the exif blob by using the same code as the TIFF export on an encoder with no image, then call
finish manually and get the raw data to pass to the JPEG encoder.
let mut f = Vec::new();
let mut encoder = TiffEncoder::new(Cursor::new(&mut f))?;
let exif_data = encode_exif_ifd(data, &mut encoder)?;
let gps_data = encode_gps_ifd(data, &mut encoder)?;
let mut main = encoder.image_directory()?;
encode_exif(data, &mut main)?;
main.write_tag(Tag::ExifDirectory, exif_data.offset)?;
if let Some(gps_offset) = gps_data {
main.write_tag(Tag::GpsDirectory, gps_offset.offset)?;
}
main.finish()?;
let mut jpg_encoder = JpegEncoder::new_with_quality(output, 90);
jpg_encoder.set_exif_metadata(f)?;
- The directory offset generic is a limitation over writing generic code. Having to pass offsets around makes it so that the code is limited to standard or big tiffs, unless we write one big ugly function.
pub fn encode_exif_ifd<W: Write + Seek>(
data: &ExposureData,
encoder: &mut TiffEncoder<W>,
) -> Result<DirectoryOffset<TiffKindStandard>, Error> {
let mut dir = encoder.extra_directory()?;
if let Some(iso) = &data.iso {
let iso = iso.parse::<u16>().unwrap();
dir.write_tag(Tag::Unknown(0x8827), iso)?;
}
Ok(dir.finish_with_offsets()?)
}
:
- The example writes a limited amount of tags, but most tags are written using the
Tag::Unknown enum variant, and tag definitions have to rely on it (see GPStag). This, along with no type checking, leads to errors; not sure this is on the tiff library to fix though.
impl From<GpsTag> for Tag {
fn from(tag: GpsTag) -> Self {
Tag::Unknown(u16::from(tag))
}
}
- The main hair-puller is the directory management; we open directories from the main encoder, fill them, then write to the main encoder, then add references to these previous directories. Factor this by generics matching and we get some pretty gnarly code.
pub fn encode_tiff_with_exif<O: Write + Seek>(
input: DynamicImage,
output: O,
data: &ExposureData,
) -> Result<(), Error> {
let mut encoder = TiffEncoder::new(output)?
.with_compression(Compression::Lzw)
.with_predictor(Predictor::Horizontal);
let exif_data = encode_exif_ifd(data, &mut encoder)?;
let gps_data = encode_gps_ifd(data, &mut encoder)?;
match format(input) {
SupportedImage::RGB(photo) => {
let mut image = encoder.new_image::<colortype::RGB8>(photo.width(), photo.height())?;
encode_exif(data, image.encoder())?;
image
.encoder()
.write_tag(Tag::ExifDirectory, exif_data.offset)?;
if let Some(gps_offset) = gps_data {
image
.encoder()
.write_tag(Tag::GpsDirectory, gps_offset.offset)?;
}
image.write_data(&photo)?;
}
SupportedImage::Gray(photo) => {
let mut image = encoder.new_image::<colortype::Gray8>(photo.width(), photo.height())?;
encode_exif(data, image.encoder())?;
image
.encoder()
.write_tag(Tag::ExifDirectory, exif_data.offset)?;
if let Some(gps_offset) = gps_data {
image
.encoder()
.write_tag(Tag::GpsDirectory, gps_offset.offset)?;
}
image.write_data(&photo)?;
}
}
Ok(())
}
The new changes are amazing and allow me to do with rust something that usually would take a ton of deserialization boilerplate and external processes to do. There definitely are some ways this could be streamlined, and drawing the line between where this lib should stop and an helper crate would take over is needed. Hope that helps !
Here is a minimal example of how I tag images using the latest changes, in both TIFFs and JPEGs.
https://gist.github.com/spoutn1k/4f27fecacc6c9284bb23a9347f0af29c
The main takeaways are:
finishmanually and get the raw data to pass to the JPEG encoder.Tag::Unknownenum variant, and tag definitions have to rely on it (see GPStag). This, along with no type checking, leads to errors; not sure this is on the tiff library to fix though.The new changes are amazing and allow me to do with rust something that usually would take a ton of deserialization boilerplate and external processes to do. There definitely are some ways this could be streamlined, and drawing the line between where this lib should stop and an helper crate would take over is needed. Hope that helps !