Sketch a TagFilter option for copying metadata

This commit is contained in:
A. Molzer committed 2026-01-13 23:46:39 +01:00
1 parent 1bd6293857
commit 348bbda906
2 files changed
+219 -1

No files matched your search

+48 -1
View File
@@ -9,7 +9,7 @@ use std::{
}; };
use crate::{ use crate::{
decoder::ifd::Entry, decoder::{ifd::Entry, IfdDecoder},
error::{TiffResult, UsageError}, error::{TiffResult, UsageError},
tags::{ tags::{
ByteOrder, CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation, ByteOrder, CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation,
@@ -22,6 +22,7 @@ pub mod colortype;
pub mod compression; pub mod compression;
mod tiff_value; mod tiff_value;
mod writer; mod writer;
mod tag_filter;
use self::colortype::*; use self::colortype::*;
use self::compression::Compression as Comp; use self::compression::Compression as Comp;
@@ -40,6 +41,8 @@ pub type Predictor = crate::tags::Predictor;
#[cfg(feature = "deflate")] #[cfg(feature = "deflate")]
pub type DeflateLevel = compression::DeflateLevel; pub type DeflateLevel = compression::DeflateLevel;
pub use tag_filter::TagFilter;
#[derive(Clone, Copy, PartialEq, Default)] #[derive(Clone, Copy, PartialEq, Default)]
pub enum Compression { pub enum Compression {
#[default] #[default]
@@ -434,6 +437,22 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
self.directory.extend(entries); self.directory.extend(entries);
} }
/// Copy metadata from the IFD that a decoder refers to.
pub fn write_metadata_from(
&mut self,
mut decoder: IfdDecoder<'_>,
options: EncodeMetadataOptions<'_>,
) -> TiffResult<EncodeMetadataReport> {
let mut report = EncodeMetadataReport {
};
for tag in decoder.tag_iter() {
let (tag, entry) = tag?;
}
Ok(report)
}
/// Define the parent directory. /// Define the parent directory.
/// ///
/// Each directory has an offset based link to its successor, forming a linked list in the /// Each directory has an offset based link to its successor, forming a linked list in the
@@ -582,6 +601,34 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
} }
} }
#[non_exhaustive]
#[derive(Clone, Copy)]
pub struct EncodeMetadataOptions<'lt> {
/// Several metadata kinds contain additional tags in further, linked directories that are only
/// referenced with an `Ifd`/`Ifd8` tag. These directories may have their own semantics, e.g. a
/// different set of tags or even new value types.
///
/// Since we know that the whole directory contains only metadata it would be possible to copy
/// all tags contained in it, however tags that can not be semantically validated might contain
/// data that is only valid within the original image or must be transformed during the copy.
/// For instance, JPEG thumbnails are embedded with separate offset and length fields (`0x0201`
/// and `0x202`). (This has caused the author mental harm, please do not consult me about it).
///
/// Setting this flag will skip such unknown tags instead of attempting a potentially incorrect
/// copy. However the set of copied metadata is then dependent on the `tiff` library.
pub tag_filter: &'lt TagFilter,
/// Some tags are not allowed to be recorded or can be semantically validated. The library can
/// skip these tags while copying metadata. For instance, `YCbCrCoefficients` _may_ be
/// recorded for images in `YCbCr` photometric interpretation but _must not_ be recorded for
/// images in any other interpretation (colorspace conversion should have been done during the
/// transformation).
pub skip_invalid_tags: bool,
}
#[non_exhaustive]
pub struct EncodeMetadataReport {
}
/// Type to encode images strip by strip. /// Type to encode images strip by strip.
/// ///
/// You should call `finish` on this when you are finished with it. /// You should call `finish` on this when you are finished with it.
+171
View File
@@ -0,0 +1,171 @@
/// A filter of tags copied in
/// [`DirectoryEncoder::write_metadata_from`][`super::DirectoryEncoder::write_metadata_from`].
pub struct TagFilter {
unknown: bool,
}
impl TagFilter {
/// Return a filter that allows all tags from metadata subdirectories.
pub fn allow_all_in_subdirectories() -> &'static TagFilter {
static INSTANCE: TagFilter = TagFilter { unknown: true };
&INSTANCE
}
/// Return a filter that has an explicit allow list of tags, for each of a known list of
/// metadata subdirectories.
pub fn allow_known() -> &'static TagFilter {
static INSTANCE: TagFilter = TagFilter { unknown: false };
&INSTANCE
}
/// As per EXIF V3.0 tags, Primary Image (0th IFD).
pub(crate) fn filter_primary_exif(&self, tag: u16) -> Level {
match tag {
// width, height, etc
0x0100..=0x0103 => Level::SampleLayout,
// photometric interpretation
0x0106 => Level::SampleLayout,
0x010e..=0x0110 => Level::Basic,
// strip offsets
0x0111 => Level::SampleLayout,
0x0112 => Level::Basic,
// strip byte counts, samples
0x0115..=0x0117 => Level::SampleLayout,
0x011a..=0x011b => Level::Basic,
// planar configuration
0x011c => Level::Photometric(Photometric::Planar),
0x0128 => Level::Basic,
// transfer function
0x012d => Level::Photometric(Photometric::All),
0x0131..=0x0132 => Level::Basic,
0x013b => Level::Basic,
// Whitepoint, Chromaticities
0x013e..=0x013f => Level::Photometric(Photometric::All),
// The table lists these as Not allowed for anything but we may have a choice here for
// older models of images.
0x0201..=0x0202 => Level::Special(Special::JpegThumbnail),
0x0211..=0x0213 => Level::Photometric(Photometric::YCbCr),
0x0214 => Level::Photometric(Photometric::All),
0x8298 => Level::Basic,
0x8769 => Level::Ifd(SubifdKind::ExifPrivate),
_ => Level::Unknown,
}
}
pub(crate) fn filter_primary_gps(&self, tag: u16) -> Level {
match tag {
0x8825 => Level::Ifd(SubifdKind::Gps),
_ => Level::Unknown,
}
}
pub(crate) fn filter_icc(&self, tag: u16) -> Level {
match tag {
0x8ff3 => Level::Basic,
_ => Level::Unknown,
}
}
pub(crate) fn filter_exif_private(&self, tag: u16) -> Level {
match tag {
0x829a => Level::Basic,
0x829d => Level::Basic,
0x8822 => Level::Basic,
0x8824 => Level::Basic,
0x8827 => Level::Basic,
0x8828 => Level::Basic,
0x8830..=0x8835 => Level::Basic,
0x9000 => Level::Basic,
0x9003..=0x9004 => Level::Basic,
0x9010..=0x9012 => Level::Basic,
// Not allowed for uncompressed data!
0x9101..=0x9102 => Level::Special(Special::CompressedExclusive),
0x9201..=0x9209 => Level::Basic,
0x920a => Level::Basic,
0x9214 => Level::Basic,
0x927c => Level::Basic,
0x9286 => Level::Basic,
0x9290..=0x9292 => Level::Basic,
0x9400..=0x9405 => Level::Basic,
0xa000 => Level::Basic,
0xa001 => Level::Photometric(Photometric::All),
0xa002..=0xa003 => Level::Special(Special::CompressedExclusive),
0xa004 => Level::Basic,
0xa005 => Level::Ifd(SubifdKind::Interoperability),
0xa20b..=0xa20c => Level::Basic,
0xa20e..=0xa210 => Level::Basic,
0xa214..=0xa215 => Level::Basic,
0xa217 => Level::Basic,
0xa300..=0xa302 => Level::Basic,
0xa401..=0xa40c => Level::Basic,
0xa420 => Level::Basic,
0xa430..=0xa43c => Level::Basic,
0xa460..=0xa462 => Level::Basic,
0xa500 => Level::Photometric(Photometric::All),
_ => Level::Unknown,
}
}
fn filter_gps_private(&self, tag: u16) -> Level {
match tag {
0x0000..=0x001f => Level::Basic,
_ => Level::Unknown,
}
}
fn filter_interoperability(&self, tag: u16) -> Level {
match tag {
0x0001 => Level::Special(Special::CompressedExclusive),
_ => Level::Unknown,
}
}
pub(crate) fn filter_secondary_exif(&self, tag: u16) -> Level {
// No difference for now. The only tabular difference I could spot is the treatment of
// sample positioning which is mandatory in the primary IFD of YCbCr but optional for all
// following IFDs..
self.filter_primary_exif(tag)
}
pub(crate) fn filter_secondary_gps(&self, tag: u16) -> Level {
self.filter_primary_gps(tag)
}
}
/// The level of support for a value.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Level {
Unknown,
/// Value describes the image data layout, which we will not want to write into the encoder's
/// directory where the image data is written later.
SampleLayout,
/// Value is an IFD and must be copied by making a subdirectory.
Ifd(SubifdKind),
Photometric(Photometric),
Basic,
/// We recognize the tag but it can not be copied by itself through normal means.
Special(Special),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Photometric {
All,
/// Do not copy for non-planar data.
Planar,
/// Do not copy for non-YCbCr data.
YCbCr,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SubifdKind {
ExifPrivate,
Gps,
Interoperability,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Special {
JpegThumbnail,
/// May only occur in compressed (JPEG APP segments) data.
CompressedExclusive,
}