Skip to content

Commit

Permalink
gufo-common: Add dyn abstraction traits
Browse files Browse the repository at this point in the history
  • Loading branch information
sophie-h committed Nov 22, 2024
1 parent 9b54af7 commit 7041683
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 64 deletions.
21 changes: 16 additions & 5 deletions gufo-common/src/cicp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct Cicp {
impl Cicp {
pub const SRGB: Cicp = Cicp {
color_primaries: ColorPrimaries::Srgb,
transfer_characteristics: TransferCharacteristics::Gamma22,
transfer_characteristics: TransferCharacteristics::Gamma24,
matrix_coefficients: MatrixCoefficients::Identity,
video_full_range_flag: VideoRangeFlag::Full,
};
Expand Down Expand Up @@ -55,6 +55,17 @@ impl Cicp {
}
}

impl From<Cicp> for Vec<u8> {
fn from(value: Cicp) -> Self {
vec![
value.color_primaries.into(),
value.transfer_characteristics.into(),
value.matrix_coefficients.into(),
value.video_full_range_flag.into(),
]
}
}

utils::convertible_enum!(
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
Expand All @@ -70,18 +81,18 @@ utils::convertible_enum!(
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TransferCharacteristics {
/// Standard dynamic range
/// Gamma=2.2 curve
Gamma22 = 1,
Unspecified = 2,
/// Standard dynamic range 10 bit
/// Gamma=2.2 curve
Gamma22_ = 6,
/// Linear
Linear = 8,
/// Gamma=2.4 curve per IEC 61966-2-1 sRGB
Gamma24 = 13,
/// Standard dynamic range 10 bit
/// Gamma=2.2 curve 10 bit
Gamma22Bit10 = 14,
/// Standard dynamic range 12 bit
/// Gamma=2.2 curve 12 bit
Gamma22Bit12 = 15,
/// Perceptual quantization (PQ) system
Pq = 16,
Expand Down
22 changes: 22 additions & 0 deletions gufo-common/src/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::cicp::Cicp;

pub trait ImageFormat {
/// Usually checks if data start with correct magic bytes
fn is_filetype(data: &[u8]) -> bool;
}

pub trait ImageMetadata {
fn cicp(&self) -> Option<Cicp> {
None
}

fn exif(&self) -> Vec<Vec<u8>> {
Vec::new()
}

fn xmp(&self) -> Vec<Vec<u8>> {
Vec::new()
}
}

pub trait ImageComplete: ImageMetadata + ImageFormat {}
2 changes: 2 additions & 0 deletions gufo-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ pub mod error;
pub mod exif;
pub mod field;
pub mod geography;
pub mod image;
pub mod math;
pub mod orientation;
pub mod prelude;
pub mod read;
pub mod utils;
pub mod xmp;
1 change: 1 addition & 0 deletions gufo-common/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub use crate::image::{ImageComplete, ImageFormat, ImageMetadata};
38 changes: 27 additions & 11 deletions gufo-jpeg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::ops::Range;

use gufo_common::error::ErrorWithData;
use gufo_common::math::*;
use gufo_common::prelude::*;
pub use segments::*;

pub const EXIF_IDENTIFIER_STRING: &[u8] = b"Exif\0\0";
Expand All @@ -24,6 +25,22 @@ pub struct Jpeg {
data: Vec<u8>,
}

impl ImageFormat for Jpeg {
fn is_filetype(data: &[u8]) -> bool {
data.starts_with(MAGIC_BYTES)
}
}

impl ImageMetadata for Jpeg {
fn exif(&self) -> Vec<Vec<u8>> {
self.exif_data().map(|x| x.to_vec()).collect()
}

fn xmp(&self) -> Vec<Vec<u8>> {
self.exif_data().map(|x| x.to_vec()).collect()
}
}

impl Jpeg {
pub fn new(data: Vec<u8>) -> Result<Self, ErrorWithData<Error>> {
match Self::find_segments(&data) {
Expand All @@ -32,10 +49,6 @@ impl Jpeg {
}
}

pub fn is_filetype(data: &[u8]) -> bool {
data.starts_with(MAGIC_BYTES)
}

pub fn into_inner(self) -> Vec<u8> {
self.data
}
Expand Down Expand Up @@ -91,17 +104,18 @@ impl Jpeg {

pub fn components_specification_parameters(
&self,
components: usize,
component: usize,
) -> Result<ComponentSpecificationParameters, Error> {
let cs = self
.sos()?
.components_specifications
.get(4)
.get(component)
.ok_or(Error::MissingComponentSpecification)?
.cs;
self.sof()?
.parameters
.get(cs as usize)
.iter()
.find(|x| x.c == cs)
.ok_or(Error::MissingComponentSpecificationParameters)
.cloned()
}
Expand Down Expand Up @@ -138,23 +152,23 @@ impl Jpeg {
.map(|x| x.segment(&self))
}

pub fn exif(&self) -> impl Iterator<Item = Segment> {
pub fn exif_segments(&self) -> impl Iterator<Item = Segment> {
self.segments_marker(Marker::APP1)
.filter(|x| x.data().starts_with(EXIF_IDENTIFIER_STRING))
}

pub fn exif_data(&self) -> impl Iterator<Item = &[u8]> {
self.exif()
self.exif_segments()
.filter_map(|x| x.data().get(EXIF_IDENTIFIER_STRING.len()..))
}

pub fn xmp(&self) -> impl Iterator<Item = Segment> {
pub fn xmp_segments(&self) -> impl Iterator<Item = Segment> {
self.segments_marker(Marker::APP1)
.filter(|x| x.data().starts_with(XMP_IDENTIFIER_STRING))
}

pub fn xmp_data(&self) -> impl Iterator<Item = &[u8]> {
self.xmp()
self.xmp_segments()
.filter_map(|x| x.data().get(XMP_IDENTIFIER_STRING.len()..))
}

Expand Down Expand Up @@ -367,6 +381,8 @@ pub enum Error {
MissingComponentSpecification,
#[error("Missing component specification parameters")]
MissingComponentSpecificationParameters,
#[error("Missing quantization table")]
MissingDqt,
}

gufo_common::utils::convertible_enum!(
Expand Down
115 changes: 74 additions & 41 deletions gufo-png/src/png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,84 @@ use std::ops::Range;
use std::slice::SliceIndex;

use gufo_common::cicp::Cicp;
use gufo_common::prelude::*;

pub use super::*;

pub const MAGIC_BYTES: &[u8] = &[137, 80, 78, 71, 13, 10, 26, 10];
pub const DEFAULT_INFLATE_LIMIT: usize = 10_usize.pow(6) * 100;

#[derive(Debug, Clone)]
pub struct Png {
/// Raw data
pub(crate) data: Vec<u8>,
/// Chunks in the order in which they appear in the data
pub(crate) chunks: Vec<RawChunk>,
pub(crate) inflate_limit: usize,
}

impl ImageFormat for Png {
fn is_filetype(data: &[u8]) -> bool {
data.starts_with(MAGIC_BYTES)
}
}

impl ImageMetadata for Png {
fn cicp(&self) -> Option<Cicp> {
let cicp_raw = self
.chunks()
.into_iter()
.find(|x| x.chunk_type() == ChunkType::cICP)?;

Cicp::from_bytes(cicp_raw.chunk_data().get(0..4)?.try_into().ok()?).ok()
}

/// Returns raw exif data if available
///
/// Prefers the newer [`eXIf`](ChunkType::eXIf) chunk if available and uses
/// the legacy [`zTXt`](ChunkType::zTXt) chunk with [`LEGACY_EXIF_KEYWORD`]
/// as fallback.
fn exif(&self) -> Vec<Vec<u8>> {
let chunks = self.chunks();

let mut result = Vec::new();

if let Some(exif) = chunks.iter().find(|x| x.chunk_type() == ChunkType::eXIf) {
result.push(exif.chunk_data().to_vec());
}

let mut legacy_exif = chunks
.iter()
.filter_map(|x| x.legacy_exif(self.inflate_limit))
.collect();

result.append(&mut legacy_exif);

result
}

fn xmp(&self) -> Vec<Vec<u8>> {
let chunks = self.chunks();

let mut result = Vec::new();

if let Some(xmp) = chunks.iter().find_map(|x| x.xmp().ok().flatten()) {
result.push(xmp.as_bytes().to_vec());
}

let mut legacy_xmp = chunks
.iter()
.filter_map(|x| x.legacy_xmp(self.inflate_limit))
.collect();

result.append(&mut legacy_xmp);

result
}
}

impl ImageComplete for Png {}

/// Representation of a PNG image
///
/// ```
Expand All @@ -24,24 +89,25 @@ pub struct Png {
///
/// assert_eq!(png.chunks()[0].chunk_type(), gufo_png::ChunkType::IHDR);
/// assert_eq!(png.chunks().len(), 43);
/// assert_eq!(png.exif(10000).unwrap().len(), 7646);
///
/// use gufo_common::prelude::*;
/// assert_eq!(png.exif().first().unwrap().len(), 7646);
/// ```
impl Png {
/// Returns PNG image representation
///
/// * `data`: PNG image data starting with magic byte
pub fn new(data: Vec<u8>) -> Result<Self, ErrorWithData<Error>> {
match Self::find_chunks(&data) {
Ok(chunks) => Ok(Self { chunks, data }),
Ok(chunks) => Ok(Self {
chunks,
data,
inflate_limit: DEFAULT_INFLATE_LIMIT,
}),
Err(err) => Err(ErrorWithData::new(err, data)),
}
}

/// Checks if passed data have PNG magic bytes
pub fn is_filetype(data: &[u8]) -> bool {
data.starts_with(MAGIC_BYTES)
}

/// Convert into raw data
pub fn into_inner(self) -> Vec<u8> {
self.data
Expand All @@ -63,40 +129,6 @@ impl Png {
Ok(())
}

/// Returns raw exif data if available
///
/// Prefers the newer [`eXIf`](ChunkType::eXIf) chunk if available and uses
/// the legacy [`zTXt`](ChunkType::zTXt) chunk with [`LEGACY_EXIF_KEYWORD`]
/// as fallback.
pub fn exif(&self, inflate_limit: usize) -> Option<Vec<u8>> {
let chunks = self.chunks();

if let Some(exif) = chunks.iter().find(|x| x.chunk_type() == ChunkType::eXIf) {
Some(exif.chunk_data().to_vec())
} else {
chunks.iter().find_map(|x| x.legacy_exif(inflate_limit))
}
}

pub fn xmp(&self, inflate_limit: usize) -> Option<Vec<u8>> {
let chunks = self.chunks();

if let Some(xmp) = chunks.iter().find_map(|x| x.xmp().ok().flatten()) {
Some(xmp.as_bytes().to_vec())
} else {
chunks.iter().find_map(|x| x.legacy_xmp(inflate_limit))
}
}

pub fn cicp(&self) -> Option<Cicp> {
let cicp_raw = self
.chunks()
.into_iter()
.find(|x| x.chunk_type() == ChunkType::cICP)?;

Cicp::from_bytes(cicp_raw.chunk_data().get(0..4)?.try_into().ok()?).ok()
}

/// List all chunks in the data
fn find_chunks(data: &[u8]) -> Result<Vec<RawChunk>, Error> {
let mut cur = Cursor::new(data);
Expand Down Expand Up @@ -235,6 +267,7 @@ impl Png {

#[cfg(test)]
mod tests {
use super::*;
use gufo_common::cicp::*;

#[test]
Expand Down
20 changes: 20 additions & 0 deletions gufo/src/image.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use gufo_common::cicp::Cicp;
use gufo_common::error::ErrorWithData;
use gufo_common::prelude::*;

use crate::Error;

Expand Down Expand Up @@ -28,4 +30,22 @@ impl Image {

Err(ErrorWithData::new(Error::NoSupportedFiletypeFound, data))
}

pub fn into_inner(self) -> Vec<u8> {
match self {
Self::Jpeg(jpeg) => jpeg.into_inner(),
Self::Png(png) => png.into_inner(),
}
}

pub fn dyn_metadata(&self) -> Box<&dyn ImageMetadata> {
match self {
Self::Png(png) => Box::new(png as &dyn ImageMetadata),
Self::Jpeg(jpeg) => Box::new(jpeg as &dyn ImageMetadata),
}
}

pub fn cicp(&self) -> Option<Cicp> {
self.dyn_metadata().cicp()
}
}
Loading

0 comments on commit 7041683

Please sign in to comment.