Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metadata #1448

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be more clear as ColorSpace?

/// 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<u8>,
},
/// 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,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Bt2020_10bit use a different transfer function than Bt2020_12bit?

Copy link
Member Author

@HeroicKatora HeroicKatora Apr 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not mathematically, but the quantization is different. Anyways having both of them is straightup copied from the transfer_characteristics field of AV1.

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 {
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -560,6 +561,16 @@ 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);
}

/// Returns all the bytes in the image.
///
/// This function takes a slice of bytes and writes the pixel data of the image into it.
Expand Down
220 changes: 220 additions & 0 deletions src/io/metagram.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
use std::cell::RefCell;
use std::sync::{Arc, Mutex};

use crate::color::Color;

// oder (Engram), Metagram,
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
/// Collects some arbitrary meta data of an image.
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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 {
HeroicKatora marked this conversation as resolved.
Show resolved Hide resolved
/// The original width in pixels.
pub width: u32,
/// The original height in pixels.
pub height: u32,
/// The available color information.
/// For example, an ICC profile characterizing color interpretation of the image input.
pub color_profile: Option<Color>,
/// Encoded EXIF data associated with the image.
pub exif: Option<Vec<u8>>,
_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<std::io::Empty> { 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,
}

enum RecorderInner {
Owned(Box<RefCell<Metagram>>),
Shared(Arc<Mutex<Metagram>>),
}

/// An owned handle to a `Metagram`, that allows concurrent modification.
#[allow(missing_copy_implementations)]
pub struct SharedRecorder {
inner: Arc<Mutex<Metagram>>,
}

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))),
}
}

/// 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.
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(),
}
}
}

/// 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<u8>) {
self.inner.with_mut(|meta| meta.set_color(color))
}

/// Overwrite all EXIF data.
pub fn exif(&self, data: Vec<u8>) {
self.inner.with_mut(|meta| meta.set_exif(data))
}
}

impl SharedRecorder {
/// 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<u8>) {
self.with_mut(|meta| meta.set_color(color))
}

/// Overwrite all EXIF data.
pub fn exif(&self, data: Vec<u8>) {
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()))
}
}

/// 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<u8>) {
self.color_profile = Some(color);
}

fn set_exif(&mut self, exif: Vec<u8>) {
self.exif = Some(exif);
}
}

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<SharedRecorder> for Recorder {
fn from(shared: SharedRecorder) -> Recorder {
Recorder { inner: RecorderInner::Shared(shared.inner) }
}
}
2 changes: 2 additions & 0 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
@@ -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};
Loading