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

✨ Add metadata when encoding #49

Merged
merged 1 commit into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions jpegxl-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license = "GPL-3.0-or-later"
name = "jpegxl-rs"
readme = "README.md"
repository = "https://github.com/inflation/jpegxl-rs"
version = "0.10.2+libjxl-0.10.2"
version = "0.10.3+libjxl-0.10.2"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -34,7 +34,7 @@ half = "2.4.0"
byteorder = "1.5.0"

[dependencies.jpegxl-sys]
version = "0.10.2"
version = "0.10.3"
path = "../jpegxl-sys"

[dev-dependencies]
Expand Down
189 changes: 35 additions & 154 deletions jpegxl-rs/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,134 +20,22 @@
use std::{marker::PhantomData, mem::MaybeUninit, ops::Deref, ptr::null};

#[allow(clippy::wildcard_imports)]
use jpegxl_sys::{
color_encoding::JxlColorEncoding,
encode::*,
types::{JxlEndianness, JxlPixelFormat},
};
use jpegxl_sys::encode::*;

use crate::{
common::PixelType, errors::EncodeError, memory::MemoryManager, parallel::JxlParallelRunner,
};

// MARK: Utility types

/// Encoding speed
#[derive(Debug, Clone, Copy)]
pub enum EncoderSpeed {
/// Fastest, 1
Lightning = 1,
/// 2
Thunder = 2,
/// 3
Falcon = 3,
/// 4
Cheetah,
/// 5
Hare,
/// 6
Wombat,
/// 7, default
Squirrel,
/// 8
Kitten,
/// Slowest, 9
Tortoise,
}

impl std::default::Default for EncoderSpeed {
fn default() -> Self {
Self::Squirrel
}
}

/// Encoding color profile
#[derive(Debug, Clone, Copy)]
pub enum ColorEncoding {
/// SRGB, default for uint pixel types
Srgb,
/// Linear SRGB, default for float pixel types
LinearSrgb,
/// SRGB, images with only luma channel
SrgbLuma,
/// Linear SRGB with only luma channel
LinearSrgbLuma,
}

impl From<ColorEncoding> for JxlColorEncoding {
fn from(val: ColorEncoding) -> Self {
use ColorEncoding::{LinearSrgb, LinearSrgbLuma, Srgb, SrgbLuma};

let mut color_encoding = MaybeUninit::uninit();

unsafe {
match val {
Srgb => JxlColorEncodingSetToSRGB(color_encoding.as_mut_ptr(), false),
LinearSrgb => JxlColorEncodingSetToLinearSRGB(color_encoding.as_mut_ptr(), false),
SrgbLuma => JxlColorEncodingSetToSRGB(color_encoding.as_mut_ptr(), true),
LinearSrgbLuma => {
JxlColorEncodingSetToLinearSRGB(color_encoding.as_mut_ptr(), true);
}
}
color_encoding.assume_init()
}
}
}

/// A frame for the encoder, consisting of the pixels and its options
pub struct EncoderFrame<'data, T: PixelType> {
data: &'data [T],
num_channels: Option<u32>,
endianness: Option<JxlEndianness>,
align: Option<usize>,
}
mod options;
pub use options::*;

impl<'data, T: PixelType> EncoderFrame<'data, T> {
/// Create a default frame from the data.
///
/// Use RGB(3) channels, native endianness and no alignment.
pub fn new(data: &'data [T]) -> Self {
Self {
data,
num_channels: None,
endianness: None,
align: None,
}
}

/// Set the number of channels of the source.
///
/// _Note_: If you want to use alpha channel, add here
#[must_use]
pub fn num_channels(mut self, value: u32) -> Self {
self.num_channels = Some(value);
self
}

/// Set the endianness of the source.
#[must_use]
pub fn endianness(mut self, value: JxlEndianness) -> Self {
self.endianness = Some(value);
self
}
mod metadata;
pub use metadata::*;

/// Set the align of the source.
/// Align scanlines to a multiple of align bytes, or 0 to require no alignment at all
#[must_use]
pub fn align(mut self, value: usize) -> Self {
self.align = Some(value);
self
}
mod frame;
pub use frame::*;

fn pixel_format(&self) -> JxlPixelFormat {
JxlPixelFormat {
num_channels: self.num_channels.unwrap_or(3),
data_type: T::pixel_type(),
endianness: self.endianness.unwrap_or(JxlEndianness::Native),
align: self.align.unwrap_or(0),
}
}
}
// MARK: Utility types

/// Encoder result
pub struct EncoderResult<U: PixelType> {
Expand Down Expand Up @@ -490,7 +378,29 @@
height: u32,
) -> Result<MultiFrames<'enc, 'prl, 'mm, U>, EncodeError> {
self.setup_encoder(width, height, U::bits_per_sample(), self.has_alpha)?;
Ok(MultiFrames::<'enc, 'prl, '_, U>(self, PhantomData))
Ok(MultiFrames::<'enc, 'prl, 'mm, U>(self, PhantomData))
}

/// Add a metadata box to the encoder
///
/// # Errors
/// Return [`EncodeError`] if it fails to add metadata
pub fn add_metadata(&mut self, metadata: &Metadata, compress: bool) -> Result<(), EncodeError> {
let (&t, &data) = match metadata {
Metadata::Exif(data) => (b"Exif", data),
Metadata::Xmp(data) => (b"xml ", data),
Metadata::Jumb(data) => (b"jumb", data),
Metadata::Custom(t, data) => (t, data),

Check warning on line 393 in jpegxl-rs/src/encode.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/encode.rs#L388-L393

Added lines #L388 - L393 were not covered by tests
};
self.check_enc_status(unsafe {
JxlEncoderAddBox(
self.enc,
Metadata::box_type(t),
data.as_ptr().cast(),
data.len(),
compress.into(),
)
})

Check warning on line 403 in jpegxl-rs/src/encode.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/encode.rs#L395-L403

Added lines #L395 - L403 were not covered by tests
}

/// Encode a JPEG XL image from existing raw JPEG data
Expand Down Expand Up @@ -521,7 +431,8 @@

/// Encode a JPEG XL image from pixels
///
/// Note: Use RGB(3) channels, native endianness and no alignment. Ignore alpha channel settings
/// Note: Use RGB(3) channels, native endianness and no alignment.
/// Ignore alpha channel settings
///
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to encode
Expand All @@ -536,7 +447,8 @@
self.start_encoding::<U>()
}

/// Encode a JPEG XL image from a frame. See [`EncoderFrame`] for custom options of the original pixels.
/// Encode a JPEG XL image from a frame.
/// See [`EncoderFrame`] for custom options of the original pixels.
///
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to encode
Expand All @@ -558,37 +470,6 @@
}
}

/// A wrapper type for encoding multiple frames
pub struct MultiFrames<'enc, 'prl, 'mm, U>(&'enc mut JxlEncoder<'prl, 'mm>, PhantomData<U>)
where
'prl: 'enc,
'mm: 'enc;

impl<U: PixelType> MultiFrames<'_, '_, '_, U> {
/// Add a frame to the encoder
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to add a frame
pub fn add_frame<T: PixelType>(self, frame: &EncoderFrame<T>) -> Result<Self, EncodeError> {
self.0.add_frame(frame)?;
Ok(self)
}

/// Add a JPEG raw frame to the encoder
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to add a jpeg frame
pub fn add_jpeg_frame(self, data: &[u8]) -> Result<Self, EncodeError> {
self.0.add_jpeg_frame(data)?;
Ok(self)
}

/// Encode a JPEG XL image from the frames
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to encode
pub fn encode(self) -> Result<EncoderResult<U>, EncodeError> {
self.0.start_encoding()
}
}

/// Return a [`JxlEncoderBuilder`] with default settings
#[must_use]
pub fn encoder_builder<'prl, 'mm>() -> JxlEncoderBuilder<'prl, 'mm> {
Expand Down
97 changes: 97 additions & 0 deletions jpegxl-rs/src/encode/frame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use std::marker::PhantomData;

use jpegxl_sys::types::{JxlEndianness, JxlPixelFormat};

use crate::{common::PixelType, EncodeError};

use super::{EncoderResult, JxlEncoder};

/// A frame for the encoder, consisting of the pixels and its options
#[allow(clippy::module_name_repetitions)]
pub struct EncoderFrame<'data, T: PixelType> {
pub(crate) data: &'data [T],
num_channels: Option<u32>,
endianness: Option<JxlEndianness>,
align: Option<usize>,
}

impl<'data, T: PixelType> EncoderFrame<'data, T> {
/// Create a default frame from the data.
///
/// Use RGB(3) channels, native endianness and no alignment.
pub fn new(data: &'data [T]) -> Self {
Self {
data,
num_channels: None,
endianness: None,
align: None,
}
}

/// Set the number of channels of the source.
///
/// _Note_: If you want to use alpha channel, add here
#[must_use]
pub fn num_channels(mut self, value: u32) -> Self {
self.num_channels = Some(value);
self
}

/// Set the endianness of the source.
#[must_use]
pub fn endianness(mut self, value: JxlEndianness) -> Self {
self.endianness = Some(value);
self
}

/// Set the align of the source.
/// Align scanlines to a multiple of align bytes, or 0 to require no alignment at all
#[must_use]
pub fn align(mut self, value: usize) -> Self {
self.align = Some(value);
self
}

pub(crate) fn pixel_format(&self) -> JxlPixelFormat {
JxlPixelFormat {
num_channels: self.num_channels.unwrap_or(3),
data_type: T::pixel_type(),
endianness: self.endianness.unwrap_or(JxlEndianness::Native),
align: self.align.unwrap_or(0),
}
}
}

/// A wrapper type for encoding multiple frames
pub struct MultiFrames<'enc, 'prl, 'mm, U>(
pub(crate) &'enc mut JxlEncoder<'prl, 'mm>,
pub(crate) PhantomData<U>,
)
where
'prl: 'enc,
'mm: 'enc;

impl<U: PixelType> MultiFrames<'_, '_, '_, U> {
/// Add a frame to the encoder
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to add a frame
pub fn add_frame<T: PixelType>(self, frame: &EncoderFrame<T>) -> Result<Self, EncodeError> {
self.0.add_frame(frame)?;
Ok(self)
}

/// Add a JPEG raw frame to the encoder
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to add a jpeg frame
pub fn add_jpeg_frame(self, data: &[u8]) -> Result<Self, EncodeError> {
self.0.add_jpeg_frame(data)?;
Ok(self)
}

/// Encode a JPEG XL image from the frames
/// # Errors
/// Return [`EncodeError`] if the internal encoder fails to encode
pub fn encode(self) -> Result<EncoderResult<U>, EncodeError> {
self.0.start_encoding()
}
}
24 changes: 24 additions & 0 deletions jpegxl-rs/src/encode/metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use jpegxl_sys::types::JxlBoxType;

/// Metadata box
pub enum Metadata<'d> {
/// EXIF
/// The contents of this box must be prepended by a 4-byte tiff header offset,
/// which may be 4 zero bytes in case the tiff header follows immediately.
Exif(&'d [u8]),
/// XMP/IPTC metadata
Xmp(&'d [u8]),
/// JUMBF superbox
Jumb(&'d [u8]),
/// Custom Metadata.
/// Type should not start with `jxl`, `JXL`, or conflict with other box type,
/// and should be registered with MP4RA (mp4ra.org).
Custom([u8; 4], &'d [u8]),
}

impl Metadata<'_> {
#[must_use]
pub(crate) fn box_type(t: [u8; 4]) -> JxlBoxType {
JxlBoxType(unsafe { std::mem::transmute(t) })
}

Check warning on line 23 in jpegxl-rs/src/encode/metadata.rs

View check run for this annotation

Codecov / codecov/patch

jpegxl-rs/src/encode/metadata.rs#L21-L23

Added lines #L21 - L23 were not covered by tests
}
Loading
Loading