-
Notifications
You must be signed in to change notification settings - Fork 67
Add new wrappers for image-rs #446
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -227,6 +227,8 @@ pub enum Status { | |
| IlocOffsetOverflow, | ||
| ImageItemType, | ||
| InfeFlagsNonzero, | ||
| InfeStringNoNul, | ||
| InfeStringNotUtf8, | ||
| InvalidUtf8, | ||
| IpcoIndexOverflow, | ||
| IpmaBadIndex, | ||
|
|
@@ -611,6 +613,14 @@ impl From<Status> for &str { | |
| "'infe' flags field shall be 0 \ | ||
| per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2" | ||
| } | ||
| Status::InfeStringNoNul => { | ||
| "'infe' strings shall be null-terminated \ | ||
| per ISOBMFF (ISO 14496-12:2020) § 8.11.6.3" | ||
| } | ||
| Status::InfeStringNotUtf8 => { | ||
| "'infe' strings field shall be valid utf8 \ | ||
| per ISOBMFF (ISO 14496-12:2020) § 8.11.6.3" | ||
| } | ||
| Status::InvalidUtf8 => { | ||
| "invalid utf8" | ||
| } | ||
|
|
@@ -1594,6 +1604,10 @@ pub struct AvifContext { | |
| pub sequence: Option<MediaContext>, | ||
| /// A collection of unsupported features encountered during the parse | ||
| pub unsupported_features: UnsupportedFeatures, | ||
| /// AVIF box containing the Exif metadata, if any | ||
| exif_metadata: Option<AvifItem>, | ||
| /// AVIF box containing the XMP metadata, if any | ||
| xmp_metadata: Option<AvifItem>, | ||
| } | ||
|
|
||
| impl AvifContext { | ||
|
|
@@ -1640,6 +1654,49 @@ impl AvifContext { | |
| } | ||
| } | ||
|
|
||
| pub fn av1_config(&self) -> Result<&AV1ConfigBox> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
| .item_properties | ||
| .get(primary_item.id, BoxType::AV1CodecConfigurationBox)? | ||
| { | ||
| Some(ItemProperty::AV1Config(av1c)) => Ok(av1c), | ||
| Some(other_property) => panic!("property key mismatch: {:?}", other_property), | ||
| None => Err(Error::from(Status::Av1cMissing)), | ||
| } | ||
| } else { | ||
| Err(Error::from(Status::PitmMissing)) | ||
| } | ||
| } | ||
|
|
||
|
|
||
| pub fn exif_metadata(&self) -> Option<&[u8]> { | ||
| self.exif_metadata | ||
| .as_ref() | ||
| .map(|item| self.item_as_slice(item)) | ||
| } | ||
|
|
||
| pub fn xmp_metadata(&self) -> Option<&[u8]> { | ||
| self.xmp_metadata | ||
| .as_ref() | ||
| .map(|item| self.item_as_slice(item)) | ||
| } | ||
|
|
||
| pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
| .item_properties | ||
| .get(primary_item.id, BoxType::ImageSpatialExtentsProperty)? | ||
| { | ||
| Some(ItemProperty::ImageSpatialExtents(ispe)) => Ok(ispe), | ||
| Some(other_property) => panic!("property key mismatch: {:?}", other_property), | ||
| None => Err(Error::from(Status::IspeMissing)), | ||
| } | ||
| } else { | ||
| Err(Error::from(Status::PitmMissing)) | ||
| } | ||
| } | ||
|
|
||
| pub fn spatial_extents_ptr(&self) -> Result<*const ImageSpatialExtentsProperty> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
|
|
@@ -1661,6 +1718,21 @@ impl AvifContext { | |
| } | ||
| } | ||
|
|
||
| pub fn colour_information(&self) -> Result<&ColourInformation> { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
| .item_properties | ||
| .get(primary_item.id, BoxType::ColourInformationBox)? | ||
| { | ||
| Some(ItemProperty::Colour(v)) => Ok(v), | ||
| Some(other_property) => panic!("property key mismatch: {:?}", other_property), | ||
| None => Err(Error::from(Status::ItemTypeMissing)), | ||
| } | ||
| } else { | ||
| Err(Error::from(Status::PitmMissing)) | ||
| } | ||
| } | ||
|
|
||
| /// Returns None if there is no primary item or it has no associated NCLX colour boxes. | ||
| pub fn nclx_colour_information_ptr(&self) -> Option<Result<*const NclxColourInformation>> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
|
|
@@ -1722,6 +1794,21 @@ impl AvifContext { | |
| } | ||
| } | ||
|
|
||
| pub fn image_mirror(&self) -> Result<&ImageMirror> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
| .item_properties | ||
| .get(primary_item.id, BoxType::ImageMirror)? | ||
| { | ||
| Some(ItemProperty::Mirroring(imir)) => Ok(imir), | ||
| Some(other_property) => panic!("property key mismatch: {:?}", other_property), | ||
| None => Err(Error::from(Status::ItemTypeMissing)), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error type should be Status::ImirMissing. |
||
| } | ||
| } else { | ||
| Err(Error::from(Status::PitmMissing)) | ||
| } | ||
| } | ||
|
|
||
| pub fn image_mirror_ptr(&self) -> Result<*const ImageMirror> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
|
|
@@ -1737,6 +1824,21 @@ impl AvifContext { | |
| } | ||
| } | ||
|
|
||
| pub fn pixel_aspect_ratio(&self) -> Result<&PixelAspectRatio> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
| .item_properties | ||
| .get(primary_item.id, BoxType::PixelAspectRatioBox)? | ||
| { | ||
| Some(ItemProperty::PixelAspectRatio(pasp)) => Ok(pasp), | ||
| Some(other_property) => panic!("property key mismatch: {:?}", other_property), | ||
| None => Err(Error::from(Status::ItemTypeMissing)), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error type should be Status::PaspMissing. |
||
| } | ||
| } else { | ||
| Err(Error::from(Status::PitmMissing)) | ||
| } | ||
| } | ||
|
|
||
| pub fn pixel_aspect_ratio_ptr(&self) -> Result<*const PixelAspectRatio> { | ||
| if let Some(primary_item) = &self.primary_item { | ||
| match self | ||
|
|
@@ -1894,7 +1996,7 @@ impl DataBox { | |
| #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] | ||
| struct PropertyIndex(u16); | ||
| #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] | ||
| struct ItemId(u32); | ||
| pub struct ItemId(u32); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was this and ItemInfoEntry made |
||
|
|
||
| impl ItemId { | ||
| fn read(src: &mut impl ReadBytesExt, version: u8) -> Result<ItemId> { | ||
|
|
@@ -1910,9 +2012,13 @@ impl ItemId { | |
| /// See ISOBMFF (ISO 14496-12:2020) § 8.11.6 | ||
| /// Only versions {2, 3} are supported | ||
| #[derive(Debug)] | ||
| struct ItemInfoEntry { | ||
| item_id: ItemId, | ||
| item_type: u32, | ||
| pub struct ItemInfoEntry { | ||
| pub item_id: ItemId, | ||
| pub item_type: u32, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
| pub item_name: TryVec<u8>, | ||
| pub content_type: Option<TryVec<u8>>, | ||
| pub content_encoding: Option<TryVec<u8>>, | ||
| pub uri_type: Option<TryVec<u8>>, | ||
| } | ||
|
|
||
| /// See ISOBMFF (ISO 14496-12:2020) § 8.11.12 | ||
|
|
@@ -2501,24 +2607,17 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif | |
| debug!("alpha_item_id: {alpha_item_id:?}"); | ||
| let mut primary_item = None; | ||
| let mut alpha_item = None; | ||
| let mut items: TryHashMap<ItemId, AvifItem> = Default::default(); | ||
|
|
||
| // store data or record location of relevant items | ||
| for (item_id, loc) in iloc_items { | ||
| let item = if Some(item_id) == primary_item_id { | ||
| &mut primary_item | ||
| } else if Some(item_id) == alpha_item_id { | ||
| &mut alpha_item | ||
| } else { | ||
| continue; | ||
| }; | ||
|
|
||
| assert!(item.is_none()); | ||
| let mut item: Option<AvifItem> = None; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 = if Some(item_id) == primary_item_id {
&mut primary_item
} else if Some(item_id) == alpha_item_id {
&mut alpha_item
} else if Some(item_id) == exif_item_id {
&mut exif_metadata
} else if Some(item_id) == xmp_item_id {
&mut xmp_metadata
} else {
continue;
}; |
||
|
|
||
| // If our item is spread over multiple extents, we'll need to copy it | ||
| // into a contiguous buffer. Otherwise, we can just store the extent | ||
| // and return a pointer into the mdat/idat later to avoid the copy. | ||
| if loc.extents.len() > 1 { | ||
| *item = Some(AvifItem::with_inline_data(item_id)) | ||
| item = Some(AvifItem::with_inline_data(item_id)) | ||
| } | ||
|
|
||
| trace!( | ||
|
|
@@ -2531,10 +2630,10 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif | |
| // true if the extent is successfully added to the AvifItem | ||
| let mut find_and_add_to_item = |extent: &Extent, dat: &DataBox| -> Result<bool> { | ||
| if let Some(extent_slice) = dat.get(extent) { | ||
| match item { | ||
| match &mut item { | ||
| None => { | ||
| trace!("Using IsobmffItem::Location"); | ||
| *item = Some(AvifItem { | ||
| item = Some(AvifItem { | ||
| id: item_id, | ||
| image_data: dat.location(extent), | ||
| }); | ||
|
|
@@ -2594,6 +2693,14 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif | |
| } | ||
|
|
||
| assert!(item.is_some()); | ||
|
|
||
| if Some(item_id) == primary_item_id { | ||
| primary_item = item; | ||
| } else if Some(item_id) == alpha_item_id { | ||
| alpha_item = item; | ||
| } else { | ||
| items.insert(item_id, item.unwrap())?; | ||
| }; | ||
| } | ||
|
|
||
| if (primary_item_id.is_some() && primary_item.is_none()) | ||
|
|
@@ -2697,6 +2804,21 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif | |
| check_image_item(&mut primary_item)?; | ||
| check_image_item(&mut alpha_item)?; | ||
|
|
||
| let mut exif_metadata = None; | ||
| let mut xmp_metadata = None; | ||
|
|
||
| item_infos.into_iter().for_each(|item| { | ||
| if &item.item_type.to_be_bytes() == b"Exif" { | ||
| exif_metadata = items.remove(&item.item_id); | ||
| } else if &item.item_type.to_be_bytes() == b"mime" | ||
| && item | ||
| .content_type | ||
| .is_some_and(|v| v.as_slice() == b"application/rdf+xml") | ||
| { | ||
| xmp_metadata = items.remove(&item.item_id); | ||
| } | ||
| }); | ||
|
|
||
| Ok(AvifContext { | ||
| strictness, | ||
| media_storage, | ||
|
|
@@ -2707,6 +2829,8 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif | |
| item_properties, | ||
| major_brand, | ||
| sequence: image_sequence, | ||
| exif_metadata, | ||
| xmp_metadata, | ||
| unsupported_features, | ||
| }) | ||
| } | ||
|
|
@@ -2881,6 +3005,39 @@ impl std::fmt::Display for U32BE { | |
| } | ||
| } | ||
|
|
||
| fn read_infe_string<T: Read>( | ||
| src: &mut BMFFBox<T>, | ||
| strictness: ParseStrictness, | ||
| ) -> Result<TryVec<u8>> { | ||
| let mut s = TryVec::new(); | ||
| loop { | ||
| match src.read_u8() { | ||
| Ok(v) => { | ||
| s.push(v)?; | ||
| if v == 0 { | ||
| break; | ||
| } | ||
| }, | ||
| Err(_) => break, | ||
| } | ||
| } | ||
| match std::str::from_utf8(&s) { | ||
| Ok(s) => { | ||
| if !s.bytes().any(|b| b == b'\0') { | ||
| fail_with_status_if( | ||
| strictness != ParseStrictness::Permissive, | ||
| Status::InfeStringNoNul, | ||
| )?; | ||
| } | ||
| } | ||
| Err(_) => fail_with_status_if( | ||
| strictness != ParseStrictness::Permissive, | ||
| Status::InfeStringNotUtf8, | ||
| )?, | ||
| } | ||
| Ok(s) | ||
| } | ||
|
|
||
| /// Parse an Item Info Entry | ||
| /// See ISOBMFF (ISO 14496-12:2020) § 8.11.6.2 | ||
| fn read_infe<T: Read>( | ||
|
|
@@ -2912,15 +3069,32 @@ fn read_infe<T: Read>( | |
| let item_type = be_u32(src)?; | ||
| debug!("infe {:?} item_type: {}", item_id, U32BE(item_type)); | ||
|
|
||
| // There are some additional fields here, but they're not of interest to us | ||
| skip_box_remain(src)?; | ||
|
|
||
| if item_protection_index != 0 { | ||
| unsupported_features.insert(Feature::Ipro); | ||
| Ok(None) | ||
| } else { | ||
| Ok(Some(ItemInfoEntry { item_id, item_type })) | ||
| skip_box_remain(src)?; | ||
| return Ok(None); | ||
| } | ||
|
|
||
| let item_name = read_infe_string(src, strictness)?; | ||
|
|
||
| let (content_type, content_encoding, uri_type) = if &item_type.to_be_bytes() == b"mime" { | ||
| ( | ||
| Some(read_infe_string(src, strictness)?), | ||
| Some(read_infe_string(src, strictness)?), | ||
| None | ||
| ) | ||
| } else { | ||
| (None, None, Some(read_infe_string(src, strictness)?)) | ||
| }; | ||
|
|
||
| Ok(Some(ItemInfoEntry { | ||
| item_id, | ||
| item_type, | ||
| item_name, | ||
| content_type, | ||
| content_encoding, | ||
| uri_type | ||
| })) | ||
| } | ||
|
|
||
| /// Parse an Item Reference Box | ||
|
|
@@ -3645,8 +3819,8 @@ fn read_ipco<T: Read>( | |
| #[repr(C)] | ||
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
| pub struct ImageSpatialExtentsProperty { | ||
| image_width: u32, | ||
| image_height: u32, | ||
| pub image_width: u32, | ||
| pub image_height: u32, | ||
| } | ||
|
|
||
| /// Parse image spatial extents property | ||
|
|
@@ -3669,8 +3843,8 @@ fn read_ispe<T: Read>(src: &mut BMFFBox<T>) -> Result<ImageSpatialExtentsPropert | |
| #[repr(C)] | ||
| #[derive(Debug)] | ||
| pub struct PixelAspectRatio { | ||
| h_spacing: u32, | ||
| v_spacing: u32, | ||
| pub h_spacing: u32, | ||
| pub v_spacing: u32, | ||
| } | ||
|
|
||
| /// Parse pixel aspect ratio property | ||
|
|
@@ -3722,16 +3896,16 @@ fn read_pixi<T: Read>(src: &mut BMFFBox<T>) -> Result<PixelInformation> { | |
| #[repr(C)] | ||
| #[derive(Debug)] | ||
| pub struct NclxColourInformation { | ||
| colour_primaries: u8, | ||
| transfer_characteristics: u8, | ||
| matrix_coefficients: u8, | ||
| full_range_flag: bool, | ||
| pub colour_primaries: u8, | ||
| pub transfer_characteristics: u8, | ||
| pub matrix_coefficients: u8, | ||
| pub full_range_flag: bool, | ||
| } | ||
|
|
||
| /// The raw bytes of the ICC profile | ||
| #[repr(C)] | ||
| pub struct IccColourInformation { | ||
| bytes: TryVec<u8>, | ||
| pub bytes: TryVec<u8>, | ||
| } | ||
|
|
||
| impl fmt::Debug for IccColourInformation { | ||
|
|
||
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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
_ptrAPIs requiresunsafeblocks:As per #444 (comment) you've been relying on cbindgen to use this crate, so didn't want to modify them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The safe and _ptr variants can then be rewritten as:
for example. The
spatial_extentschange needs to preserve thefail_with_status_ifcheck.