Skip to content

Commit

Permalink
✨ Add metadata when encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
inflation committed May 4, 2024
1 parent 1fe7130 commit 15f4afb
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 161 deletions.
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 @@ along with jpegxl-rs. If not, see <https://www.gnu.org/licenses/>.
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 @@ impl<'prl, 'mm> JxlEncoder<'prl, 'mm> {
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),
};
self.check_enc_status(unsafe {
JxlEncoderAddBox(
self.enc,
Metadata::box_type(t),
data.as_ptr().cast(),
data.len(),
compress.into(),
)
})
}

/// Encode a JPEG XL image from existing raw JPEG data
Expand Down Expand Up @@ -521,7 +431,8 @@ impl<'prl, 'mm> JxlEncoder<'prl, 'mm> {

/// 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 @@ impl<'prl, 'mm> JxlEncoder<'prl, 'mm> {
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 @@ impl Drop for JxlEncoder<'_, '_> {
}
}

/// 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) })
}
}
Loading

0 comments on commit 15f4afb

Please sign in to comment.