From 6e1dffc9d0c7a18efe96dbfcb19013a9ef4d0017 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sun, 21 Feb 2021 14:18:59 +0100 Subject: [PATCH 1/7] Add interface for meta data read during decode --- src/io/metagram.rs | 156 +++++++++++++++++++++++++++++++++++++++++++++ src/io/mod.rs | 2 + 2 files changed, 158 insertions(+) create mode 100644 src/io/metagram.rs diff --git a/src/io/metagram.rs b/src/io/metagram.rs new file mode 100644 index 0000000000..0e004ab667 --- /dev/null +++ b/src/io/metagram.rs @@ -0,0 +1,156 @@ +use std::cell::RefCell; +use std::sync::{Arc, Mutex}; +use std::collections::HashMap; + +use crate::color; + +// oder (Engram), Metagram, +/// Collects some arbitrary meta data of an image. +#[derive(Clone, Default)] +pub struct Metagram { + width: u32, + height: u32, + color: Option, + color_profile_iccp: Vec, + exif: Vec, + comments: Vec, +} + +/// A buffer for the extra meta data produced by an image decoder. +pub struct Recorder { + inner: RecorderInner, +} + +enum RecorderInner { + Owned(Box>), + Shared(Arc>), +} + +/// An owned handle to a `Metagram`, that allows concurrent modification. +#[allow(missing_copy_implementations)] +pub struct SharedRecorder { + inner: Arc>, +} + +impl Metagram { + pub fn dimensions(&mut self, width: u32, height: u32) { + self.width = width; + self.height = height; + } + + pub fn color(&mut self, color: color::ExtendedColorType) { + self.color = Some(color); + } + + pub fn add_comment(&mut self, comment: String) { + self.comments.push(comment) + } + + pub fn set_exif(&mut self, data: Vec) { + self.exif = data; + } +} + +impl Recorder { + pub fn new() -> Self { + Recorder::default() + } + + pub fn with(meta: Metagram) -> Self { + Recorder { + inner: RecorderInner::Owned(Box::new(RefCell::new(meta))), + } + } + + pub fn dimensions(&self, width: u32, height: u32) { + self.inner.with_mut(|meta| meta.dimensions(width, height)) + } + + pub fn color(&self, color: color::ExtendedColorType) { + self.inner.with_mut(|meta| meta.color(color)) + } + + /// Add an additional comment. + pub fn add_comment(&self, comment: String) { + self.inner.with_mut(|meta| meta.add_comment(comment)) + } + + /// Overwrite all EXIF data. + pub fn exif(&self, data: Vec) { + self.inner.with_mut(|meta| meta.set_exif(data)) + } + + /// Split the recorder such that it can be sent to a different thread. + pub fn share(&mut self) -> SharedRecorder { + let inner; + match &self.inner { + RecorderInner::Shared(arc) => { + inner = Arc::clone(&arc); + }, + RecorderInner::Owned(boxed) => { + let meta = boxed.borrow().clone(); + let arc = Arc::new(Mutex::new(meta)); + inner = Arc::clone(&arc); + self.inner = RecorderInner::Shared(arc); + } + }; + + SharedRecorder { + inner, + } + } + + /// Get a clone of the configured meta data. + pub fn to_result(&self) -> Metagram { + self.inner.to_result() + } +} + +impl RecorderInner { + fn with_mut(&self, function: impl FnOnce(&mut Metagram)) { + match self { + RecorderInner::Owned(boxed) => { + function(&mut boxed.borrow_mut()) + } + RecorderInner::Shared(arc) => { + function(&mut arc.lock().unwrap()) + } + } + } + + fn to_result(&self) -> Metagram { + match self { + RecorderInner::Owned(boxed) => boxed.borrow().clone(), + RecorderInner::Shared(arc) => arc.lock().unwrap().clone(), + } + } +} + +impl SharedRecorder { + pub fn set_exif(&mut self, data: Vec) { + self.with_mut(|meta| meta.set_exif(data)) + } + + fn with_mut(&self, function: impl FnOnce(&mut Metagram)) { + // Regarding lock recovery: None of the inner methods should usually panic. The only + // exception would be from allocation error while inserting a new exif tag or something. + function(&mut self.inner.lock().unwrap_or_else(|err| err.into_inner())) + } +} + +impl Default for Recorder { + fn default() -> Self { + Recorder { + inner: RecorderInner::Owned(Default::default()), + } + } +} + +/// Convert a shared recorder into a non-thread safe variant. +/// The two will _still_ record to the same meta data collection but this new instances could be +/// used as a method argument to another `ImageDecoder` impl. +impl From for Recorder { + fn from(shared: SharedRecorder) -> Recorder { + Recorder { inner: RecorderInner::Shared(shared.inner) } + } +} diff --git a/src/io/mod.rs b/src/io/mod.rs index e7964c7259..e87e0aedcf 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,5 +1,7 @@ //! Input and output of images. +mod metagram; mod reader; pub(crate) mod free_functions; pub use self::reader::Reader; +pub use self::metagram::{Metagram, Recorder, SharedRecorder}; From 5a927d93eb20dd53abe61e0ace3267a2ce1099ff Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sun, 28 Feb 2021 15:33:42 +0100 Subject: [PATCH 2/7] Add meta data recording to ImageDecoder --- src/image.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/image.rs b/src/image.rs index f1d30add42..33a42ced38 100644 --- a/src/image.rs +++ b/src/image.rs @@ -10,6 +10,7 @@ use std::usize; use crate::ImageBuffer; use crate::color::{ColorType, ExtendedColorType}; use crate::error::{ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, ParameterError, ParameterErrorKind}; +use crate::io::Recorder; use crate::math::Rect; use crate::traits::Pixel; @@ -560,6 +561,17 @@ pub trait ImageDecoder<'a>: Sized { self.total_bytes() } + /// Record auxiliary information. + /// For decoders that may encounter additional extra data, i.e. EXIF data that might be + /// discovered while reading the image, a `SharedRecorder` should be retrieved and data + /// recorded with it instead. All relevant information should be added before returning from + /// the `read_image` call. + fn metagram(&mut self, recorder: &mut Recorder) { + let (width, height) = self.dimensions(); + recorder.dimensions(width, height); + recorder.color(self.original_color_type()); + } + /// Returns all the bytes in the image. /// /// This function takes a slice of bytes and writes the pixel data of the image into it. From 5eb4b4e7cec9f7e16cc124d08a905f54485c753c Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Fri, 12 Mar 2021 00:47:46 +0100 Subject: [PATCH 3/7] Work on meta data API consistency The fields of Metagram are now public, since it is not intended that they do keep invariants relative to one another. The documentation also clarifies the relation to the actual processing applied by the decoding. The SharedRecorder now has the same basic set of methods for adding meta data as the original Recorder. Added documentation and some examples. --- src/image.rs | 1 - src/io/metagram.rs | 151 +++++++++++++++++++++++++++++++-------------- 2 files changed, 106 insertions(+), 46 deletions(-) diff --git a/src/image.rs b/src/image.rs index 33a42ced38..71d360bf4d 100644 --- a/src/image.rs +++ b/src/image.rs @@ -569,7 +569,6 @@ pub trait ImageDecoder<'a>: Sized { fn metagram(&mut self, recorder: &mut Recorder) { let (width, height) = self.dimensions(); recorder.dimensions(width, height); - recorder.color(self.original_color_type()); } /// Returns all the bytes in the image. diff --git a/src/io/metagram.rs b/src/io/metagram.rs index 0e004ab667..17e4cda793 100644 --- a/src/io/metagram.rs +++ b/src/io/metagram.rs @@ -1,22 +1,62 @@ use std::cell::RefCell; use std::sync::{Arc, Mutex}; -use std::collections::HashMap; - -use crate::color; // oder (Engram), Metagram, /// Collects some arbitrary meta data of an image. +/// +/// Note that information collected here will, per default, appear opaque to the `image` library +/// itself. For example, the width and height of an image might be recorded as one set of values +/// that's completely different from the reported dimensions in the decoder. During decoding and +/// writing of images the library may ignore the color profile and exif data. You should always +/// recheck with the specific decoder if you require color accuracy beyond presuming sRGB. (This +/// may be improved upon in the future, if you have a concrete draft please open an issue). #[derive(Clone, Default)] pub struct Metagram { - width: u32, - height: u32, - color: Option, - color_profile_iccp: Vec, - exif: Vec, - comments: Vec, + /// The original width in pixels. + pub width: u32, + /// The original height in pixels. + pub height: u32, + /// An ICC profile characterizing color interpretation of the image input. + pub color_profile: Option>, + /// Encoded EXIF data associated with the image. + pub exif: Option>, + _non_exhaustive: (), } /// A buffer for the extra meta data produced by an image decoder. +/// +/// There are two main ways of creation: Any consumer of the `ImageDecoder` interface can create +/// their own recorder to retrieve meta data from the decoder. A decoder wrapping another format +/// (i.e. jpeg-within-tiff) might create a recorder from a `SharedRecorder` to add additional data +/// to the outer recorder instead. +/// +/// # Use +/// +/// ```rust +/// # struct FakeDecoder; +/// # use image::ImageDecoder; +/// # impl ImageDecoder<'_> for FakeDecoder { +/// # type Reader = std::io::Empty; +/// # fn dimensions(&self) -> (u32, u32) { (0, 0) } +/// # fn color_type(&self) -> image::ColorType { todo!() } +/// # fn into_reader(self) -> image::ImageResult { Ok(std::io::empty()) } +/// # } +/// use image::io::{Metagram, Recorder}; +/// +/// let mut some_decoder = // .. +/// # FakeDecoder; +/// let mut recorder = Recorder::new(); +/// some_decoder.metagram(&mut recorder); +/// +/// if recorder.is_shared() { +/// // The decoder kept a shared clone, The complete metagram may not yet be available. +/// // It will likely add more to the metagram while decoding. +/// let mut _buffer = vec![0; some_decoder.total_bytes() as usize]; +/// some_decoder.read_image(&mut _buffer); +/// } +/// +/// let meta: Metagram = recorder.to_result(); +/// ``` pub struct Recorder { inner: RecorderInner, } @@ -32,52 +72,25 @@ pub struct SharedRecorder { inner: Arc>, } -impl Metagram { - pub fn dimensions(&mut self, width: u32, height: u32) { - self.width = width; - self.height = height; - } - - pub fn color(&mut self, color: color::ExtendedColorType) { - self.color = Some(color); - } - - pub fn add_comment(&mut self, comment: String) { - self.comments.push(comment) - } - - pub fn set_exif(&mut self, data: Vec) { - self.exif = data; - } -} - impl Recorder { + /// Create a recorder recording into a new, empty meta data. pub fn new() -> Self { Recorder::default() } + /// Create a record that already contains some data. pub fn with(meta: Metagram) -> Self { Recorder { inner: RecorderInner::Owned(Box::new(RefCell::new(meta))), } } - pub fn dimensions(&self, width: u32, height: u32) { - self.inner.with_mut(|meta| meta.dimensions(width, height)) - } - - pub fn color(&self, color: color::ExtendedColorType) { - self.inner.with_mut(|meta| meta.color(color)) - } - - /// Add an additional comment. - pub fn add_comment(&self, comment: String) { - self.inner.with_mut(|meta| meta.add_comment(comment)) - } - - /// Overwrite all EXIF data. - pub fn exif(&self, data: Vec) { - self.inner.with_mut(|meta| meta.set_exif(data)) + /// Check if this recorder was shared. + pub fn is_shared(&self) -> bool { + match self.inner { + RecorderInner::Owned(_) => false, + RecorderInner::Shared(_) => true, + } } /// Split the recorder such that it can be sent to a different thread. @@ -126,8 +139,39 @@ impl RecorderInner { } } +/// Setters for recording meta data. +/// Any change here should also be made for `SharedRecorder` unless it is specifically not possible +/// to perform on a shared, and locked struct. +impl Recorder { + /// Add original dimensions. + pub fn dimensions(&self, width: u32, height: u32) { + self.inner.with_mut(|meta| meta.set_dimensions((width, height))); + } + + /// Add a color profile. + pub fn color(&self, color: Vec) { + self.inner.with_mut(|meta| meta.set_color(color)) + } + + /// Overwrite all EXIF data. + pub fn exif(&self, data: Vec) { + self.inner.with_mut(|meta| meta.set_exif(data)) + } +} + impl SharedRecorder { - pub fn set_exif(&mut self, data: Vec) { + /// Add original dimensions. + pub fn dimensions(&self, width: u32, height: u32) { + self.with_mut(|meta| meta.set_dimensions((width, height))); + } + + /// Add a color profile. + pub fn color(&self, color: Vec) { + self.with_mut(|meta| meta.set_color(color)) + } + + /// Overwrite all EXIF data. + pub fn exif(&self, data: Vec) { self.with_mut(|meta| meta.set_exif(data)) } @@ -138,6 +182,23 @@ impl SharedRecorder { } } +/// Private implementation of metagram, existing for the purpose of make assignment available as +/// methods. +impl Metagram { + fn set_dimensions(&mut self, (width, height): (u32, u32)) { + self.width = width; + self.height = height; + } + + fn set_color(&mut self, color: Vec) { + self.color_profile = Some(color); + } + + fn set_exif(&mut self, exif: Vec) { + self.exif = Some(exif); + } +} + impl Default for Recorder { fn default() -> Self { Recorder { From 1f4d8d489635a91c2de8d429aec5879653dfba4e Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Thu, 1 Apr 2021 21:03:53 +0200 Subject: [PATCH 4/7] Introduce a model for specified color spaces --- src/color.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 11 ++++-- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/color.rs b/src/color.rs index 0166c082aa..feb425b0e3 100644 --- a/src/color.rs +++ b/src/color.rs @@ -34,6 +34,100 @@ pub enum ColorType { __NonExhaustive(crate::utils::NonExhaustiveMarker), } +/// Color information of an image's texels. +#[derive(Clone, Debug, PartialEq)] +#[non_exhaustive] +pub enum Color { + /// The color space is given by an encoded ICC profile. + /// + /// This is a superset of other options but the consumer must itself decode and extract the + /// values. They should indicate an error similar to a completely unsupported color space in + /// case this fails. + Icc { + /// The binary ICC data. + profile: Vec, + }, + /// There is, explicitly, no known color model associated with these values. + /// + /// The image might contain indices without any associated color map, or it might represent + /// quantities not related to color, or non-standard colorimetric values. Note that this is + /// different from no information being available. + Opaque, + /// A common model based on the CIE 1931 XYZ observer. + Xyz { + /// The standardized RGB primary colors. + primary: Primaries, + /// The transfer function (electro-optical, opto-electrical). + transfer: Transfer, + /// The whitepoint of the color space. + /// In general, we can not transform from one to another without loss of accuracy. + whitepoint: Whitepoint, + /// The absolute luminance of the values in the color space. + luminance: Luminance, + }, +} + +/// Transfer functions from encoded chromatic samples to physical quantity. +/// +/// Ignoring viewing environmental effects, this describes a pair of functions that are each others +/// inverse: An electro-optical transfer (EOTF) and opto-electronic transfer function (OETF) that +/// describes how scene lighting is encoded as an electric signal. These are applied to each +/// stimulus value. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Transfer { + /// Specified in ITU Rec.709. + Bt709, + Bt470M, + /// Specified in ITU Rec.601. + Bt601, + Smpte240, + /// Also known as the identity function. + Linear, + /// The common sRGB standard which is close to standard 'gamma correction'. + Srgb, + /// ITU Rec.2020 with 10 bit quantization. + Bt2020_10bit, + /// ITU Rec.2020 with 12 bit quantization. + Bt2020_12bit, + Smpte2084, + /// Specified in ITU Rec.2100. + /// The same as Smpte2084. + Bt2100Pq, + /// ITU Rec.2100 Hybrid Log-Gamma. + Bt2100Hlg, +} + +/// The reference brightness of the color specification. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Luminance { + /// 100cd/m². + Sdr, + /// 10_000cd/m². + /// Known as high-dynamic range. + Hdr, +} + +/// The relative stimuli of the three corners of a triangular gamut. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Primaries { + Bt601_525, + Bt601_625, + Bt709, + Smpte240, + Bt2020, + Bt2100, +} + +/// The whitepoint/standard illuminant. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Whitepoint { + D65, +} + impl ColorType { /// Returns the number of bytes contained in a pixel of `ColorType` ```c``` pub fn bytes_per_pixel(self) -> u8 { @@ -81,6 +175,15 @@ impl ColorType { } } +impl Color { + pub const SRGB: Color = Color::Xyz { + luminance: Luminance::Sdr, + primary: Primaries::Bt709, + transfer: Transfer::Srgb, + whitepoint: Whitepoint::D65, + }; +} + /// An enumeration of color types encountered in image formats. /// /// This is not exhaustive over all existing image formats but should be granular enough to allow diff --git a/src/lib.rs b/src/lib.rs index 0f395e872e..3ae6b14291 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,9 +101,9 @@ extern crate quickcheck; use std::io::Write; -pub use crate::color::{ColorType, ExtendedColorType}; +pub use crate::color_::{ColorType, ExtendedColorType}; -pub use crate::color::{Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; +pub use crate::color_::{Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; pub use crate::error::{ImageError, ImageResult}; @@ -354,11 +354,16 @@ pub mod webp { pub use crate::codecs::webp::{vp8, WebPDecoder}; } +pub mod color { + pub(crate) use crate::color_::*; + pub use crate::color_::{Color, Luminance, Transfer, Primaries, Whitepoint}; +} mod animation; #[path = "buffer.rs"] mod buffer_; -mod color; +#[path = "color.rs"] +mod color_; mod dynimage; mod image; mod traits; From a46aa6d812ca8cd1ad9401cb807b64f8cc27ba32 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sat, 3 Apr 2021 23:46:44 +0200 Subject: [PATCH 5/7] Replace simple ICC with color enum We can choose two ways here: Add a new field with he 'decoded' color space, or the approach implemented currently which is to have an enum which presents an alternative. --- src/io/metagram.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/io/metagram.rs b/src/io/metagram.rs index 17e4cda793..def2e212ea 100644 --- a/src/io/metagram.rs +++ b/src/io/metagram.rs @@ -1,6 +1,8 @@ use std::cell::RefCell; use std::sync::{Arc, Mutex}; +use crate::color::Color; + // oder (Engram), Metagram, /// Collects some arbitrary meta data of an image. /// @@ -16,8 +18,9 @@ pub struct Metagram { pub width: u32, /// The original height in pixels. pub height: u32, - /// An ICC profile characterizing color interpretation of the image input. - pub color_profile: Option>, + /// The available color information. + /// For example, an ICC profile characterizing color interpretation of the image input. + pub color_profile: Option, /// Encoded EXIF data associated with the image. pub exif: Option>, _non_exhaustive: (), From c4090eb7ed0a578893f1c0d70f463354b52f2025 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Mon, 5 Apr 2021 15:45:23 +0200 Subject: [PATCH 6/7] Replace non-exhaustive attribute --- src/color.rs | 25 ++++++++++++++++++------- src/io/metagram.rs | 6 +++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/color.rs b/src/color.rs index feb425b0e3..5e5419c68b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -3,6 +3,7 @@ use std::ops::{Index, IndexMut}; use num_traits::{NumCast, ToPrimitive, Zero}; use crate::traits::{Pixel, Primitive}; +use crate::utils::NonExhaustiveMarker; /// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] @@ -31,12 +32,11 @@ pub enum ColorType { Bgra8, #[doc(hidden)] - __NonExhaustive(crate::utils::NonExhaustiveMarker), + __NonExhaustive(NonExhaustiveMarker), } /// Color information of an image's texels. #[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] pub enum Color { /// The color space is given by an encoded ICC profile. /// @@ -65,6 +65,9 @@ pub enum Color { /// The absolute luminance of the values in the color space. luminance: Luminance, }, + + #[doc(hidden)] + __NonExhaustive(NonExhaustiveMarker), } /// Transfer functions from encoded chromatic samples to physical quantity. @@ -74,7 +77,6 @@ pub enum Color { /// describes how scene lighting is encoded as an electric signal. These are applied to each /// stimulus value. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[non_exhaustive] pub enum Transfer { /// Specified in ITU Rec.709. Bt709, @@ -96,22 +98,26 @@ pub enum Transfer { Bt2100Pq, /// ITU Rec.2100 Hybrid Log-Gamma. Bt2100Hlg, + + #[doc(hidden)] + __NonExhaustive(NonExhaustiveMarker), } /// The reference brightness of the color specification. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[non_exhaustive] pub enum Luminance { /// 100cd/m². Sdr, /// 10_000cd/m². /// Known as high-dynamic range. Hdr, + + #[doc(hidden)] + __NonExhaustive(NonExhaustiveMarker), } /// The relative stimuli of the three corners of a triangular gamut. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[non_exhaustive] pub enum Primaries { Bt601_525, Bt601_625, @@ -119,13 +125,18 @@ pub enum Primaries { Smpte240, Bt2020, Bt2100, + + #[doc(hidden)] + __NonExhaustive(NonExhaustiveMarker), } /// The whitepoint/standard illuminant. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[non_exhaustive] pub enum Whitepoint { D65, + + #[doc(hidden)] + __NonExhaustive(NonExhaustiveMarker), } impl ColorType { @@ -247,7 +258,7 @@ pub enum ExtendedColorType { Unknown(u8), #[doc(hidden)] - __NonExhaustive(crate::utils::NonExhaustiveMarker), + __NonExhaustive(NonExhaustiveMarker), } impl ExtendedColorType { diff --git a/src/io/metagram.rs b/src/io/metagram.rs index def2e212ea..0b58962a9f 100644 --- a/src/io/metagram.rs +++ b/src/io/metagram.rs @@ -152,7 +152,7 @@ impl Recorder { } /// Add a color profile. - pub fn color(&self, color: Vec) { + pub fn color(&self, color: Color) { self.inner.with_mut(|meta| meta.set_color(color)) } @@ -169,7 +169,7 @@ impl SharedRecorder { } /// Add a color profile. - pub fn color(&self, color: Vec) { + pub fn color(&self, color: Color) { self.with_mut(|meta| meta.set_color(color)) } @@ -193,7 +193,7 @@ impl Metagram { self.height = height; } - fn set_color(&mut self, color: Vec) { + fn set_color(&mut self, color: Color) { self.color_profile = Some(color); } From f57a4c45fbd96ea68c80e0d5b7ece23b529c9383 Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sat, 19 Jun 2021 02:00:22 +0200 Subject: [PATCH 7/7] Address code review Renamed main struct to MetadataContainer, integrated configuration into the recorder with documentation. --- src/io/{metagram.rs => metadata.rs} | 129 +++++++++++++++++++++++----- src/io/mod.rs | 4 +- 2 files changed, 108 insertions(+), 25 deletions(-) rename src/io/{metagram.rs => metadata.rs} (57%) diff --git a/src/io/metagram.rs b/src/io/metadata.rs similarity index 57% rename from src/io/metagram.rs rename to src/io/metadata.rs index 0b58962a9f..a0b16c9ebb 100644 --- a/src/io/metagram.rs +++ b/src/io/metadata.rs @@ -3,8 +3,7 @@ use std::sync::{Arc, Mutex}; use crate::color::Color; -// oder (Engram), Metagram, -/// Collects some arbitrary meta data of an image. +/// Collects some arbitrary metadata of an image. /// /// Note that information collected here will, per default, appear opaque to the `image` library /// itself. For example, the width and height of an image might be recorded as one set of values @@ -13,7 +12,7 @@ use crate::color::Color; /// recheck with the specific decoder if you require color accuracy beyond presuming sRGB. (This /// may be improved upon in the future, if you have a concrete draft please open an issue). #[derive(Clone, Default)] -pub struct Metagram { +pub struct MetadataContainer { /// The original width in pixels. pub width: u32, /// The original height in pixels. @@ -26,10 +25,37 @@ pub struct Metagram { _non_exhaustive: (), } -/// A buffer for the extra meta data produced by an image decoder. +/// Hints whether one specific metadatum is requested. +#[derive(Clone, Copy)] +pub enum DatumRequested { + /// The decoder should skip the datum when encountered. + Ignore, + /// The decoder should decode, validate, and add the datum. + Record, +} + +/// Configure the caller needs of metadata. +/// +/// Not every piece of metadata can be added for free. For example, reading EXIF profiles is +/// commonly associated with decoding of additional chunks, validation, and additional resource +/// usage. All of these can be opt-out by allowing such a customization. Note that, nevertheless, +/// the absence of metadata is not definitive proof of its absence in the original file. It might +/// be more recent than the decoder and not yet have support. +#[derive(Clone, Default)] +// We may want to add a non-copy field? +#[allow(missing_copy_implementations)] +pub struct RecorderConfig { + /// Whether the decoder should try to provide color profile data. + pub color_profile: DatumRequested, + /// Whether the decoder should try to provide EXIF data. + pub exif: DatumRequested, + _non_exhaustive: (), +} + +/// A buffer for the extra metadata produced by an image decoder. /// /// There are two main ways of creation: Any consumer of the `ImageDecoder` interface can create -/// their own recorder to retrieve meta data from the decoder. A decoder wrapping another format +/// their own recorder to retrieve metadata from the decoder. A decoder wrapping another format /// (i.e. jpeg-within-tiff) might create a recorder from a `SharedRecorder` to add additional data /// to the outer recorder instead. /// @@ -44,7 +70,7 @@ pub struct Metagram { /// # fn color_type(&self) -> image::ColorType { todo!() } /// # fn into_reader(self) -> image::ImageResult { Ok(std::io::empty()) } /// # } -/// use image::io::{Metagram, Recorder}; +/// use image::io::{MetadataContainer, Recorder}; /// /// let mut some_decoder = // .. /// # FakeDecoder; @@ -58,33 +84,50 @@ pub struct Metagram { /// some_decoder.read_image(&mut _buffer); /// } /// -/// let meta: Metagram = recorder.to_result(); +/// let meta: MetadataContainer = recorder.to_result(); /// ``` pub struct Recorder { inner: RecorderInner, + config: RecorderConfig, } enum RecorderInner { - Owned(Box>), - Shared(Arc>), + Owned(Box>), + Shared(Arc>), } -/// An owned handle to a `Metagram`, that allows concurrent modification. +/// An owned handle to a `MetadataContainer`, that allows concurrent modification. #[allow(missing_copy_implementations)] pub struct SharedRecorder { - inner: Arc>, + inner: Arc>, + config: RecorderConfig, } impl Recorder { - /// Create a recorder recording into a new, empty meta data. + /// Create a recorder recording into a new, empty metadata. pub fn new() -> Self { Recorder::default() } + /// Get the current configuration. + pub fn config(&self) -> &RecorderConfig { + &self.config + } + + /// Get a mutable reference to the current configuration. + /// + /// Note that the configuration is cloned when converting between `Recorder` and + /// `SharedRecorder` but it is not _shared_ between instances. That is, changes only apply to + /// this instance and not to clones. + pub fn config_mut(&mut self) -> &mut RecorderConfig { + &mut self.config + } + /// Create a record that already contains some data. - pub fn with(meta: Metagram) -> Self { + pub fn with(meta: MetadataContainer) -> Self { Recorder { inner: RecorderInner::Owned(Box::new(RefCell::new(meta))), + config: RecorderConfig::default(), } } @@ -113,28 +156,29 @@ impl Recorder { SharedRecorder { inner, + config: self.config.clone(), } } - /// Get a clone of the configured meta data. - pub fn to_result(&self) -> Metagram { + /// Get a clone of the configured metadata. + pub fn to_result(&self) -> MetadataContainer { self.inner.to_result() } } impl RecorderInner { - fn with_mut(&self, function: impl FnOnce(&mut Metagram)) { + fn with_mut(&self, function: impl FnOnce(&mut MetadataContainer)) { match self { RecorderInner::Owned(boxed) => { function(&mut boxed.borrow_mut()) } RecorderInner::Shared(arc) => { - function(&mut arc.lock().unwrap()) + function(&mut arc.lock().unwrap_or_else(|err| err.into_inner())) } } } - fn to_result(&self) -> Metagram { + fn to_result(&self) -> MetadataContainer { match self { RecorderInner::Owned(boxed) => boxed.borrow().clone(), RecorderInner::Shared(arc) => arc.lock().unwrap().clone(), @@ -142,7 +186,8 @@ impl RecorderInner { } } -/// Setters for recording meta data. +/// Setters for recording metadata. +/// /// Any change here should also be made for `SharedRecorder` unless it is specifically not possible /// to perform on a shared, and locked struct. impl Recorder { @@ -160,9 +205,32 @@ impl Recorder { pub fn exif(&self, data: Vec) { self.inner.with_mut(|meta| meta.set_exif(data)) } + + /// Mutate the metadata container. + /// + /// This will make the changes visible to all other recorders sharing the container target. It + /// is not specified what happens when `function` panics but there will be no undefined + /// behavior. + pub fn with_mut(&self, function: impl FnOnce(&mut MetadataContainer)) { + self.inner.with_mut(function) + } } impl SharedRecorder { + /// Get the current configuration. + pub fn config(&self) -> &RecorderConfig { + &self.config + } + + /// Get a mutable reference to the current configuration. + /// + /// Note that the configuration is cloned when converting between `Recorder` and + /// `SharedRecorder` but it is not _shared_ between instances. That is, changes only apply to + /// this instance and not to clones. + pub fn config_mut(&mut self) -> &mut RecorderConfig { + &mut self.config + } + /// Add original dimensions. pub fn dimensions(&self, width: u32, height: u32) { self.with_mut(|meta| meta.set_dimensions((width, height))); @@ -178,7 +246,12 @@ impl SharedRecorder { self.with_mut(|meta| meta.set_exif(data)) } - fn with_mut(&self, function: impl FnOnce(&mut Metagram)) { + /// Mutate the shared metadata container. + /// + /// This will make the changes visible to all other recorders sharing the container target. It + /// is not specified what happens when `function` panics but there will be no undefined + /// behavior. + pub fn with_mut(&self, function: impl FnOnce(&mut MetadataContainer)) { // Regarding lock recovery: None of the inner methods should usually panic. The only // exception would be from allocation error while inserting a new exif tag or something. function(&mut self.inner.lock().unwrap_or_else(|err| err.into_inner())) @@ -187,7 +260,7 @@ impl SharedRecorder { /// Private implementation of metagram, existing for the purpose of make assignment available as /// methods. -impl Metagram { +impl MetadataContainer { fn set_dimensions(&mut self, (width, height): (u32, u32)) { self.width = width; self.height = height; @@ -206,15 +279,25 @@ impl Default for Recorder { fn default() -> Self { Recorder { inner: RecorderInner::Owned(Default::default()), + config: RecorderConfig::default(), } } } /// Convert a shared recorder into a non-thread safe variant. -/// The two will _still_ record to the same meta data collection but this new instances could be +/// The two will _still_ record to the same metadata collection but this new instances could be /// used as a method argument to another `ImageDecoder` impl. impl From for Recorder { fn from(shared: SharedRecorder) -> Recorder { - Recorder { inner: RecorderInner::Shared(shared.inner) } + Recorder { + inner: RecorderInner::Shared(shared.inner), + config: shared.config, + } + } +} + +impl Default for DatumRequested { + fn default() -> Self { + DatumRequested::Record } } diff --git a/src/io/mod.rs b/src/io/mod.rs index e87e0aedcf..bfb3120e0a 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,7 +1,7 @@ //! Input and output of images. -mod metagram; +mod metadata; mod reader; pub(crate) mod free_functions; pub use self::reader::Reader; -pub use self::metagram::{Metagram, Recorder, SharedRecorder}; +pub use self::metadata::{DatumRequested, MetadataContainer, Recorder, RecorderConfig, SharedRecorder};