Skip to content

Add new wrappers for image-rs - #446

Open
amyspark wants to merge 2 commits into
mozilla:masterfrom
amyspark:add-image-rs-wrappers
Open

Add new wrappers for image-rs#446
amyspark wants to merge 2 commits into
mozilla:masterfrom
amyspark:add-image-rs-wrappers

Conversation

@amyspark

Copy link
Copy Markdown

Hi all,

This PR adds support for a few BMFF boxes that are required to speed up the AVIF decoder's setup in image-rs, avoiding having to decode the full image to query e.g. dimensions, bit depth, etc.

While at it, I've also added support for detecting and exposing the Exif and XMP metadata boxes.

All feedback is appreciated.

Fixes#441
Fixes#444

cc @Shnatsel

Comment threadmp4parse/src/lib.rs Outdated
Comment threadmp4parse/src/lib.rs Outdated
@amyspark
amysparkforce-pushed the add-image-rs-wrappers branch from 86a54c0 to fde99abCompareApril 10, 2026 14:22
@amyspark
amyspark requested a review from linkmauveApril 13, 2026 15:58

@kinetiknzkinetiknz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I haven't reviewed this thoroughly yet, but I've included some initial feedback.

The iloc read loop is potentially very expensive after these changes. It should remain the way it was, but extended to read the required items.

The read_infe/read_string changes don't work correctly for multiple reasons, so I don't believe this code has been tested.

We'll need automated tests added to cover the changes in the core parser.

Comment threadmp4parse/src/lib.rs Outdated
Comment threadmp4parse/src/lib.rs Outdated
Comment threadmp4parse/src/lib.rs Outdated
Comment threadmp4parse/src/lib.rs
.map(|item| self.item_as_slice(item))
}

pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The duplication in these additions with the *_ptr() variants needs to be addressed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I am not sure how to address it, since using the existing _ptr APIs requires unsafe blocks:

error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block
--> src\codecs\avif\decoder.rs:99:21
|
99 | height: (*dimensions).image_height,
| ^^^^^^^^^^^^^ dereference of raw pointer
|
= note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior

As per #444 (comment) you've been relying on cbindgen to use this crate, so didn't want to modify them.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fnprimary_property<'a,T>(&'aself,box_type:BoxType,extract:implFn(&'aItemProperty) -> Option<&'aT>,) -> Result<Option<&'aT>>{let primary = self.primary_item.as_ref().ok_or(Error::from(Status::PitmMissing))?;matchself.item_properties.get(primary.id, box_type)? {Some(prop) => Ok(Some(extract(prop).expect("property key mismatch"))),None => Ok(None),}}

The safe and _ptr variants can then be rewritten as:

pubfnimage_mirror(&self) -> Result<&ImageMirror>{self.primary_property(BoxType::ImageMirror, |p| match p {ItemProperty::Mirroring(v) => Some(v),
_ => None,})?
.ok_or_else(|| Status::ImirMissing.into())}pubfnimage_mirror_ptr(&self) -> Result<*constImageMirror>{matchself.image_mirror(){Ok(v) => Ok(v),Err(Error::InvalidData(Status::PitmMissing | Status::ImirMissing)) => Ok(std::ptr::null()),Err(e) => Err(e),}}

for example. The spatial_extents change needs to preserve the fail_with_status_if check.

Comment threadmp4parse/src/lib.rs Outdated
return Ok(None);
}

let item_name = src.read_string(strictness)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Use the pattern in read_hdlr to read nul-terminated strings. Use correct error types per that pattern, not the generic InvalidUtf8 that read_string returned.

With skip_box_remain moved, this is now broken when parsing item_type == uri boxes. Also broken if the optional content_encoding field is not present, since these changes assume it is.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Use the pattern in read_hdlr to read nul-terminated strings.

There are four different instances of null-terminated UTF-8 strings, I would like them to not copy-paste code for each one. The next iteration turns it into a standalone function with two new error code enums, let me know if that's OK.

Also took care of the uri boxes.

if the optional content_encoding field is not present

In the AVIF files I have here with XMP metadata, the field is present but empty. I couldn't find in 14496:12-2020 how to detect missing-but-optional strings, could you confirm whether this is done by expecting the box to end upon read?

Comment threadmp4parse/src/lib.rs Outdated
@amyspark
amysparkforce-pushed the add-image-rs-wrappers branch from fde99ab to 191fd55CompareApril 14, 2026 14:00
@amyspark
amysparkforce-pushed the add-image-rs-wrappers branch from 191fd55 to 4575afdCompareApril 14, 2026 16:11
@amyspark

Copy link
Copy Markdown
Author

Hi, is there anything I can do to help this forward?

@kinetiknz

Copy link
Copy Markdown
Collaborator

Hi, is there anything I can do to help this forward?

I think it'd be a good idea to separate this into two PRs. It's fairly easy to review and merge the "Add safe wrappers for AVIF metadata boxes" change, the only issue with that is the need to reduce the duplication between the safe and *_ptr() variants. That can be done with a safe helper for

"Read and expose Exif and XMP metadata in the AvifContext" needs tests for the infe parser changes (it's clear this code still has not been tested - the application/rdf+xml comparison fails because of untrimmed NULs, for example), the string reading should use read_into_try_vec to bound the read length (and the result can then be split on NULs into the appropriate fields). The uri_type reading path is taken for every non-mime type, causing parser failures in Normal/Strict mode on valid files.

Comment threadmp4parse/src/lib.rs
.map(|item| self.item_as_slice(item))
}

pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

fnprimary_property<'a,T>(&'aself,box_type:BoxType,extract:implFn(&'aItemProperty) -> Option<&'aT>,) -> Result<Option<&'aT>>{let primary = self.primary_item.as_ref().ok_or(Error::from(Status::PitmMissing))?;matchself.item_properties.get(primary.id, box_type)? {Some(prop) => Ok(Some(extract(prop).expect("property key mismatch"))),None => Ok(None),}}

The safe and _ptr variants can then be rewritten as:

pubfnimage_mirror(&self) -> Result<&ImageMirror>{self.primary_property(BoxType::ImageMirror, |p| match p {ItemProperty::Mirroring(v) => Some(v),
_ => None,})?
.ok_or_else(|| Status::ImirMissing.into())}pubfnimage_mirror_ptr(&self) -> Result<*constImageMirror>{matchself.image_mirror(){Ok(v) => Ok(v),Err(Error::InvalidData(Status::PitmMissing | Status::ImirMissing)) => Ok(std::ptr::null()),Err(e) => Err(e),}}

for example. The spatial_extents change needs to preserve the fail_with_status_if check.

Comment threadmp4parse/src/lib.rs
}
}

pub fn colour_information(&self) -> Result<&ColourInformation> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Don't add this as-is. item_properties.get() errors with Status::IprpConflict when multiple colr boxes are associated with the primary item but both an nclx and an ICC colr on one item is legal per HEIF (and common in real AVIFs that want to specify both display primaries and a profile). Add safe variants of the existing nclx_colour_information_ptr and use icc_colour_information directly.

Comment threadmp4parse/src/lib.rs
{
Some(ItemProperty::Mirroring(imir)) => Ok(imir),
Some(other_property) => panic!("property key mismatch: {:?}", other_property),
None => Err(Error::from(Status::ItemTypeMissing)),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Error type should be Status::ImirMissing.

Comment threadmp4parse/src/lib.rs
{
Some(ItemProperty::PixelAspectRatio(pasp)) => Ok(pasp),
Some(other_property) => panic!("property key mismatch: {:?}", other_property),
None => Err(Error::from(Status::ItemTypeMissing)),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Error type should be Status::PaspMissing.

Comment threadmp4parse/src/lib.rs
struct PropertyIndex(u16);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
struct ItemId(u32);
pub struct ItemId(u32);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why was this and ItemInfoEntry made pub?

Comment threadmp4parse/src/lib.rs
item_type: u32,
pub struct ItemInfoEntry {
pub item_id: ItemId,
pub item_type: u32,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This should be FourCC.

Comment threadmp4parse/src/lib.rs
};

assert!(item.is_none());
let mut item: Option<AvifItem> = None;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Revert these changes entirely. This can be done more efficiently with:

Before the iloc_items loop:

let exif_item_id = item_infos.iter().find(|i| i.item_type.to_be_bytes() == *b"Exif").map(|i| i.item_id);let xmp_item_id = item_infos.iter().find(|i| i.item_type.to_be_bytes() == *b"mime"
&& i.content_type.as_ref().is_some_and(|ct| ct.as_slice() == b"application/rdf+xml")).map(|i| i.item_id);

Inside the loop:

let item = ifSome(item_id) == primary_item_id {&mut primary_item
}elseifSome(item_id) == alpha_item_id {&mut alpha_item
}elseifSome(item_id) == exif_item_id {&mut exif_metadata
}elseifSome(item_id) == xmp_item_id {&mut xmp_metadata
}else{continue;};

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AVIF metadata is improperly marked as private Allow reading Exif metadata from AVIF images

3 participants

@amyspark@kinetiknz@linkmauve