From 44779721d4bd82d4e39f86bfc391923ed0b456a4 Mon Sep 17 00:00:00 2001 From: Inflation <2375962+inflation@users.noreply.github.com> Date: Sat, 4 May 2024 17:52:11 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20metadata=20when=20encoding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jpegxl-rs/Cargo.toml | 4 +- jpegxl-rs/src/encode.rs | 189 ++++++------------------------- jpegxl-rs/src/encode/frame.rs | 97 ++++++++++++++++ jpegxl-rs/src/encode/metadata.rs | 24 ++++ jpegxl-rs/src/encode/options.rs | 69 +++++++++++ jpegxl-rs/src/memory.rs | 4 +- jpegxl-sys/Cargo.toml | 2 +- jpegxl-sys/src/encode.rs | 2 +- jpegxl-sys/src/types.rs | 3 +- 9 files changed, 233 insertions(+), 161 deletions(-) create mode 100644 jpegxl-rs/src/encode/frame.rs create mode 100644 jpegxl-rs/src/encode/metadata.rs create mode 100644 jpegxl-rs/src/encode/options.rs diff --git a/jpegxl-rs/Cargo.toml b/jpegxl-rs/Cargo.toml index 0402a8a..ff8f2be 100644 --- a/jpegxl-rs/Cargo.toml +++ b/jpegxl-rs/Cargo.toml @@ -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 @@ -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] diff --git a/jpegxl-rs/src/encode.rs b/jpegxl-rs/src/encode.rs index dd10b8f..b1d86c4 100644 --- a/jpegxl-rs/src/encode.rs +++ b/jpegxl-rs/src/encode.rs @@ -20,134 +20,22 @@ along with jpegxl-rs. If not, see . 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 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, - endianness: Option, - align: Option, -} +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 { @@ -490,7 +378,29 @@ impl<'prl, 'mm> JxlEncoder<'prl, 'mm> { height: u32, ) -> Result, 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 @@ -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 @@ -536,7 +447,8 @@ impl<'prl, 'mm> JxlEncoder<'prl, 'mm> { self.start_encoding::() } - /// 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 @@ -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) -where - 'prl: 'enc, - 'mm: 'enc; - -impl MultiFrames<'_, '_, '_, U> { - /// Add a frame to the encoder - /// # Errors - /// Return [`EncodeError`] if the internal encoder fails to add a frame - pub fn add_frame(self, frame: &EncoderFrame) -> Result { - 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.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, EncodeError> { - self.0.start_encoding() - } -} - /// Return a [`JxlEncoderBuilder`] with default settings #[must_use] pub fn encoder_builder<'prl, 'mm>() -> JxlEncoderBuilder<'prl, 'mm> { diff --git a/jpegxl-rs/src/encode/frame.rs b/jpegxl-rs/src/encode/frame.rs new file mode 100644 index 0000000..9f3b912 --- /dev/null +++ b/jpegxl-rs/src/encode/frame.rs @@ -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, + endianness: Option, + align: Option, +} + +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, +) +where + 'prl: 'enc, + 'mm: 'enc; + +impl MultiFrames<'_, '_, '_, U> { + /// Add a frame to the encoder + /// # Errors + /// Return [`EncodeError`] if the internal encoder fails to add a frame + pub fn add_frame(self, frame: &EncoderFrame) -> Result { + 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.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, EncodeError> { + self.0.start_encoding() + } +} diff --git a/jpegxl-rs/src/encode/metadata.rs b/jpegxl-rs/src/encode/metadata.rs new file mode 100644 index 0000000..69670ef --- /dev/null +++ b/jpegxl-rs/src/encode/metadata.rs @@ -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) }) + } +} diff --git a/jpegxl-rs/src/encode/options.rs b/jpegxl-rs/src/encode/options.rs new file mode 100644 index 0000000..5bac696 --- /dev/null +++ b/jpegxl-rs/src/encode/options.rs @@ -0,0 +1,69 @@ +use std::mem::MaybeUninit; + +use jpegxl_sys::{color_encoding::JxlColorEncoding, encode as api}; + +/// 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, + /// 9 + Tortoise, + /// Slowest, 10 + Glacier, +} + +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 for JxlColorEncoding { + fn from(val: ColorEncoding) -> Self { + use ColorEncoding::{LinearSrgb, LinearSrgbLuma, Srgb, SrgbLuma}; + + let mut color_encoding = MaybeUninit::uninit(); + + unsafe { + match val { + Srgb => api::JxlColorEncodingSetToSRGB(color_encoding.as_mut_ptr(), false), + LinearSrgb => { + api::JxlColorEncodingSetToLinearSRGB(color_encoding.as_mut_ptr(), false); + } + SrgbLuma => api::JxlColorEncodingSetToSRGB(color_encoding.as_mut_ptr(), true), + LinearSrgbLuma => { + api::JxlColorEncodingSetToLinearSRGB(color_encoding.as_mut_ptr(), true); + } + } + color_encoding.assume_init() + } + } +} diff --git a/jpegxl-rs/src/memory.rs b/jpegxl-rs/src/memory.rs index c281f86..1b76ada 100644 --- a/jpegxl-rs/src/memory.rs +++ b/jpegxl-rs/src/memory.rs @@ -39,7 +39,7 @@ pub trait MemoryManager { #[must_use] fn manager(&self) -> JxlMemoryManager { JxlMemoryManager { - opaque: (self as *const Self).cast_mut().cast(), + opaque: std::ptr::from_ref::(self).cast_mut().cast(), alloc: self.alloc(), free: self.free(), } @@ -118,7 +118,7 @@ pub(crate) mod tests { new = s + size; } else { let addr = mm.arena.get_unchecked_mut(footer); - break (addr as *mut u8).cast(); + break std::ptr::from_mut::(addr).cast(); } } } diff --git a/jpegxl-sys/Cargo.toml b/jpegxl-sys/Cargo.toml index 65c2145..4f4e366 100644 --- a/jpegxl-sys/Cargo.toml +++ b/jpegxl-sys/Cargo.toml @@ -9,7 +9,7 @@ links = "jxl" name = "jpegxl-sys" 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 diff --git a/jpegxl-sys/src/encode.rs b/jpegxl-sys/src/encode.rs index c6529f2..5d71d39 100644 --- a/jpegxl-sys/src/encode.rs +++ b/jpegxl-sys/src/encode.rs @@ -242,7 +242,7 @@ extern "C" { pub fn JxlEncoderAddBox( enc: *mut JxlEncoder, - box_type: &mut JxlBoxType, + box_type: JxlBoxType, contents: *const u8, size: usize, compress_box: JxlBool, diff --git a/jpegxl-sys/src/types.rs b/jpegxl-sys/src/types.rs index 1f0c08a..32dbfdd 100644 --- a/jpegxl-sys/src/types.rs +++ b/jpegxl-sys/src/types.rs @@ -76,4 +76,5 @@ pub struct JxlBitDepth { exponent_bits_per_sample: u32, } -pub type JxlBoxType = [c_char; 4]; +#[repr(transparent)] +pub struct JxlBoxType(pub [c_char; 4]);