libheif_rs/
image_handle.rs

1use std::ffi::CString;
2use std::mem::MaybeUninit;
3use std::os::raw::c_char;
4use std::ptr;
5
6use four_cc::FourCC;
7use libheif_sys as lh;
8
9use crate::utils::cstr_to_str;
10use crate::{
11    ColorProfileNCLX, ColorProfileRaw, ColorProfileType, ColorSpace, HeifError, HeifErrorCode,
12    HeifErrorSubCode, ImageMetadata, Result,
13};
14
15cfg_if::cfg_if! {
16    if #[cfg(feature = "v1_18")] {
17        use crate::regions::RegionItem;
18        use crate::HeifContext;
19    }
20}
21
22/// Encoded image.
23pub struct ImageHandle {
24    pub(crate) inner: *mut lh::heif_image_handle,
25}
26
27pub type ItemId = lh::heif_item_id;
28
29impl ImageHandle {
30    pub(crate) fn new(handle: *mut lh::heif_image_handle) -> Self {
31        ImageHandle { inner: handle }
32    }
33
34    #[cfg(feature = "v1_18")]
35    fn context(&self) -> HeifContext<'_> {
36        unsafe { HeifContext::from_ptr(lh::heif_image_handle_get_context(self.inner)) }
37    }
38
39    pub fn item_id(&self) -> ItemId {
40        unsafe { lh::heif_image_handle_get_item_id(self.inner) }
41    }
42
43    pub fn width(&self) -> u32 {
44        unsafe { lh::heif_image_handle_get_width(self.inner) as _ }
45    }
46
47    pub fn height(&self) -> u32 {
48        unsafe { lh::heif_image_handle_get_height(self.inner) as _ }
49    }
50
51    pub fn has_alpha_channel(&self) -> bool {
52        unsafe { lh::heif_image_handle_has_alpha_channel(self.inner) != 0 }
53    }
54
55    pub fn is_premultiplied_alpha(&self) -> bool {
56        unsafe { lh::heif_image_handle_is_premultiplied_alpha(self.inner) != 0 }
57    }
58
59    pub fn is_primary(&self) -> bool {
60        unsafe { lh::heif_image_handle_is_primary_image(self.inner) != 0 }
61    }
62
63    pub fn luma_bits_per_pixel(&self) -> u8 {
64        unsafe { lh::heif_image_handle_get_luma_bits_per_pixel(self.inner) as _ }
65    }
66
67    pub fn chroma_bits_per_pixel(&self) -> u8 {
68        unsafe { lh::heif_image_handle_get_chroma_bits_per_pixel(self.inner) as _ }
69    }
70
71    /// Get the image width from the 'ispe' box. This is the original image size without
72    /// any transformations applied to it. Do not use this unless you know exactly what
73    /// you are doing.
74    pub fn ispe_width(&self) -> i32 {
75        unsafe { lh::heif_image_handle_get_ispe_width(self.inner) as _ }
76    }
77
78    /// Get the image height from the 'ispe' box. This is the original image size without
79    /// any transformations applied to it. Do not use this unless you know exactly what
80    /// you are doing.
81    pub fn ispe_height(&self) -> i32 {
82        unsafe { lh::heif_image_handle_get_ispe_height(self.inner) as _ }
83    }
84
85    // Depth images
86
87    pub fn has_depth_image(&self) -> bool {
88        unsafe { lh::heif_image_handle_has_depth_image(self.inner) != 0 }
89    }
90
91    pub fn number_of_depth_images(&self) -> i32 {
92        unsafe { lh::heif_image_handle_get_number_of_depth_images(self.inner) }
93    }
94
95    pub fn depth_image_ids(&self, item_ids: &mut [ItemId]) -> usize {
96        if item_ids.is_empty() {
97            0
98        } else {
99            unsafe {
100                lh::heif_image_handle_get_list_of_depth_image_IDs(
101                    self.inner,
102                    item_ids.as_mut_ptr(),
103                    item_ids.len() as _,
104                ) as usize
105            }
106        }
107    }
108
109    pub fn depth_image_handle(&self, depth_image_id: ItemId) -> Result<Self> {
110        let mut out_depth_handler = MaybeUninit::<_>::uninit();
111        let err = unsafe {
112            lh::heif_image_handle_get_depth_image_handle(
113                self.inner,
114                depth_image_id,
115                out_depth_handler.as_mut_ptr(),
116            )
117        };
118        HeifError::from_heif_error(err)?;
119        let out_depth_handler = unsafe { out_depth_handler.assume_init() };
120        Ok(ImageHandle {
121            inner: out_depth_handler,
122        })
123    }
124
125    // Thumbnails
126
127    pub fn number_of_thumbnails(&self) -> usize {
128        unsafe { lh::heif_image_handle_get_number_of_thumbnails(self.inner) as _ }
129    }
130
131    pub fn thumbnail_ids(&self, item_ids: &mut [ItemId]) -> usize {
132        if item_ids.is_empty() {
133            0
134        } else {
135            unsafe {
136                lh::heif_image_handle_get_list_of_thumbnail_IDs(
137                    self.inner,
138                    item_ids.as_mut_ptr(),
139                    item_ids.len() as _,
140                ) as usize
141            }
142        }
143    }
144
145    pub fn thumbnail(&self, thumbnail_id: ItemId) -> Result<Self> {
146        let mut out_thumbnail_handler = MaybeUninit::<_>::uninit();
147        let err = unsafe {
148            lh::heif_image_handle_get_thumbnail(
149                self.inner,
150                thumbnail_id,
151                out_thumbnail_handler.as_mut_ptr(),
152            )
153        };
154        HeifError::from_heif_error(err)?;
155        let out_thumbnail_handler = unsafe { out_thumbnail_handler.assume_init() };
156        Ok(ImageHandle {
157            inner: out_thumbnail_handler,
158        })
159    }
160
161    // Metadata
162
163    fn convert_type_filter<T>(type_filter: T) -> Option<CString>
164    where
165        T: Into<FourCC>,
166    {
167        let type_filter = type_filter.into();
168        if type_filter.0.contains(&0) {
169            // We can't convert FourCC with zero byte into valid C-string
170            None
171        } else {
172            CString::new(type_filter.to_string()).ok()
173        }
174    }
175
176    pub fn number_of_metadata_blocks<T>(&self, type_filter: T) -> i32
177    where
178        T: Into<FourCC>,
179    {
180        let c_type_filter = Self::convert_type_filter(type_filter);
181        let filter_ptr: *const c_char = match &c_type_filter {
182            Some(s) => s.as_ptr(),
183            None => ptr::null(),
184        };
185        unsafe { lh::heif_image_handle_get_number_of_metadata_blocks(self.inner, filter_ptr) }
186    }
187
188    pub fn metadata_block_ids<T>(&self, item_ids: &mut [ItemId], type_filter: T) -> usize
189    where
190        T: Into<FourCC>,
191    {
192        if item_ids.is_empty() {
193            0
194        } else {
195            let c_type_filter = Self::convert_type_filter(type_filter);
196            let filter_ptr: *const c_char = match &c_type_filter {
197                Some(s) => s.as_ptr(),
198                None => ptr::null(),
199            };
200            unsafe {
201                lh::heif_image_handle_get_list_of_metadata_block_IDs(
202                    self.inner,
203                    filter_ptr,
204                    item_ids.as_mut_ptr(),
205                    item_ids.len() as _,
206                ) as usize
207            }
208        }
209    }
210
211    /// Return a string indicating the type of the metadata, as specified in the HEIF file.
212    /// Exif data will have the type string "Exif".
213    /// This string will be valid until the next call to a libheif function.
214    pub fn metadata_type(&self, metadata_id: ItemId) -> Option<&str> {
215        let c_type: *const c_char =
216            unsafe { lh::heif_image_handle_get_metadata_type(self.inner, metadata_id) };
217        cstr_to_str(c_type)
218    }
219
220    /// For EXIF, the content type is `Some("")`.
221    ///
222    /// For XMP, the content type is `Some("application/rdf+xml")`.
223    pub fn metadata_content_type(&self, metadata_id: ItemId) -> Option<&str> {
224        let c_type =
225            unsafe { lh::heif_image_handle_get_metadata_content_type(self.inner, metadata_id) };
226        cstr_to_str(c_type)
227    }
228
229    /// Get the size of the raw metadata, as stored in the HEIF file.
230    pub fn metadata_size(&self, metadata_id: ItemId) -> usize {
231        unsafe { lh::heif_image_handle_get_metadata_size(self.inner, metadata_id) }
232    }
233
234    /// Only valid for item type == "uri ", an absolute URI.
235    pub fn metadata_item_uri_type(&self, metadata_id: ItemId) -> Option<&str> {
236        let c_type =
237            unsafe { lh::heif_image_handle_get_metadata_item_uri_type(self.inner, metadata_id) };
238        cstr_to_str(c_type)
239    }
240
241    pub fn metadata(&self, metadata_id: ItemId) -> Result<Vec<u8>> {
242        let size = self.metadata_size(metadata_id);
243        if size == 0 {
244            return Err(HeifError {
245                code: HeifErrorCode::UsageError,
246                sub_code: HeifErrorSubCode::NonExistingItemReferenced,
247                message: "".to_string(),
248            });
249        }
250        let mut result: Vec<u8> = Vec::with_capacity(size);
251        unsafe {
252            let err =
253                lh::heif_image_handle_get_metadata(self.inner, metadata_id, result.as_ptr() as _);
254            HeifError::from_heif_error(err)?;
255            result.set_len(size);
256        }
257        Ok(result)
258    }
259
260    /// Return vector with all image's metadata items.
261    pub fn all_metadata(&self) -> Vec<ImageMetadata> {
262        let count = self.number_of_metadata_blocks(0).max(0) as usize;
263        let mut item_ids = vec![0; count];
264        let count = self.metadata_block_ids(&mut item_ids, 0);
265        let mut result = Vec::with_capacity(count);
266        for item_id in item_ids {
267            if item_id == 0 {
268                continue;
269            }
270            if let Some(item) = self.item_metadata(item_id) {
271                result.push(item);
272            }
273        }
274        result
275    }
276
277    fn item_metadata(&self, item_id: ItemId) -> Option<ImageMetadata> {
278        let item_type = self
279            .metadata_type(item_id)
280            .filter(|t| t.len() == 4)
281            .map(|t| FourCC::from(t.as_bytes()))?;
282        let content_type = self.metadata_content_type(item_id).map(String::from)?;
283        let uri_type = self
284            .metadata_item_uri_type(item_id)
285            .map(|s| s.to_string())?;
286        let raw_data = self.metadata(item_id).ok()?;
287        Some(ImageMetadata {
288            item_type,
289            content_type,
290            uri_type,
291            raw_data,
292        })
293    }
294
295    /// Return the colorspace that `libheif` proposes to use for decoding.
296    /// Usually, these will be either [YCbCr](ColorSpace::YCbCr)
297    /// or [Monochrome](ColorSpace::Monochrome), but it may also
298    /// propose [Rgb](ColorSpace::Rgb) for images encoded with `matrix_coefficients=0`.
299    /// It may also return [Undefined](ColorSpace::Undefined) if the file misses
300    /// relevant information to determine this without decoding.
301    pub fn preferred_decoding_colorspace(&self) -> Result<ColorSpace> {
302        let mut lh_colorspace = lh::heif_colorspace_heif_colorspace_undefined;
303        let mut lh_chroma = lh::heif_chroma_heif_chroma_undefined;
304        let err = unsafe {
305            lh::heif_image_handle_get_preferred_decoding_colorspace(
306                self.inner,
307                &mut lh_colorspace,
308                &mut lh_chroma,
309            )
310        };
311        HeifError::from_heif_error(err)?;
312        Ok(ColorSpace::from_libheif(lh_colorspace, lh_chroma).unwrap_or(ColorSpace::Undefined))
313    }
314
315    pub fn color_profile_raw(&self) -> Option<ColorProfileRaw> {
316        let size = unsafe { lh::heif_image_handle_get_raw_color_profile_size(self.inner) };
317        if size == 0 {
318            return None;
319        }
320        let mut result: Vec<u8> = Vec::with_capacity(size);
321        let err = unsafe {
322            lh::heif_image_handle_get_raw_color_profile(self.inner, result.as_ptr() as _)
323        };
324        if err.code != 0 {
325            // Only one error is possible inside `libheif` - `ColorProfileDoesNotExist`
326            return None;
327        }
328        unsafe {
329            result.set_len(size);
330        }
331        let c_profile_type = unsafe { lh::heif_image_handle_get_color_profile_type(self.inner) };
332        // `c_profile_type` on Windows will be i32, so we need to cast it to u32
333        let profile_type = ColorProfileType::from(c_profile_type as u32);
334
335        Some(ColorProfileRaw {
336            typ: profile_type,
337            data: result,
338        })
339    }
340
341    /// NOTE: This function does currently not return an NCLX profile if it is
342    /// stored in the image bitstream. Only NCLX profiles stored as colr boxes
343    /// are returned. This may change in the future.
344    pub fn color_profile_nclx(&self) -> Option<ColorProfileNCLX> {
345        let mut profile_ptr = MaybeUninit::<_>::uninit();
346        let err = unsafe {
347            lh::heif_image_handle_get_nclx_color_profile(self.inner, profile_ptr.as_mut_ptr())
348        };
349        if err.code != 0 {
350            // Only one error is possible inside `libheif` - `ColorProfileDoesNotExist`
351            return None;
352        }
353        let profile_ptr = unsafe { profile_ptr.assume_init() };
354        if profile_ptr.is_null() {
355            return None;
356        }
357        Some(ColorProfileNCLX { inner: profile_ptr })
358    }
359
360    /// Add a region item to an image.
361    ///
362    /// The region item is a collection of regions (point, polyline, polygon,
363    /// rectangle, ellipse or mask) along with a reference size
364    /// (width and height) that forms the coordinate basis for the regions.
365    ///
366    /// The concept is to add the region item, then add one or more regions
367    /// to the region item.
368    #[cfg(feature = "v1_18")]
369    pub fn add_region_item(
370        &mut self,
371        reference_width: u32,
372        reference_height: u32,
373    ) -> Result<RegionItem> {
374        let mut lh_region_item_ptr: *mut lh::heif_region_item = ptr::null_mut();
375        let err = unsafe {
376            lh::heif_image_handle_add_region_item(
377                self.inner,
378                reference_width,
379                reference_height,
380                &mut lh_region_item_ptr,
381            )
382        };
383        HeifError::from_heif_error(err)?;
384        let item_ptr = ptr::NonNull::new(lh_region_item_ptr).ok_or(HeifError {
385            code: HeifErrorCode::MemoryAllocationError,
386            sub_code: HeifErrorSubCode::Unspecified,
387            message: "".to_string(),
388        })?;
389        Ok(RegionItem::new(item_ptr))
390    }
391
392    /// Get the region items attached to the image.
393    #[cfg(feature = "v1_18")]
394    pub fn region_items(&self) -> Vec<RegionItem> {
395        let num_items = unsafe { lh::heif_image_handle_get_number_of_region_items(self.inner) };
396        let size = num_items.max(0) as usize;
397        let mut item_ids: Vec<ItemId> = Vec::with_capacity(size);
398        let mut items: Vec<RegionItem> = Vec::with_capacity(size);
399        if size > 0 {
400            unsafe {
401                lh::heif_image_handle_get_list_of_region_item_ids(
402                    self.inner,
403                    item_ids.as_mut_ptr(),
404                    num_items,
405                );
406                item_ids.set_len(size);
407            }
408            for item_id in item_ids {
409                let mut item_ptr = ptr::null_mut();
410                let err = unsafe {
411                    lh::heif_context_get_region_item(self.context().inner, item_id, &mut item_ptr)
412                };
413                if HeifError::from_heif_error(err).is_ok() {
414                    if let Some(region_item_ptr) = ptr::NonNull::new(item_ptr) {
415                        items.push(RegionItem::new(region_item_ptr));
416                    }
417                }
418            }
419        }
420        items
421    }
422
423    /// Returns the vector of auxiliary image handles assigned to this image handle.
424    pub fn auxiliary_images<T: Into<Option<AuxiliaryImagesFilter>>>(
425        &self,
426        filter: T,
427    ) -> Vec<ImageHandle> {
428        let filter = filter.into().unwrap_or_default();
429        let num_items =
430            unsafe { lh::heif_image_handle_get_number_of_auxiliary_images(self.inner, filter.0) };
431        let size = num_items.max(0) as usize;
432        let mut item_ids: Vec<ItemId> = Vec::with_capacity(size);
433        let mut image_handles = Vec::with_capacity(size);
434        if size > 0 {
435            unsafe {
436                let real_size = lh::heif_image_handle_get_list_of_auxiliary_image_IDs(
437                    self.inner,
438                    filter.0,
439                    item_ids.as_mut_ptr(),
440                    num_items,
441                );
442                item_ids.set_len(real_size as usize);
443            }
444            for item_id in item_ids {
445                let mut handle_ptr = ptr::null_mut();
446                let err = unsafe {
447                    lh::heif_image_handle_get_auxiliary_image_handle(
448                        self.inner,
449                        item_id,
450                        &mut handle_ptr,
451                    )
452                };
453                if HeifError::from_heif_error(err).is_ok() && !handle_ptr.is_null() {
454                    image_handles.push(ImageHandle::new(handle_ptr));
455                }
456            }
457        }
458        image_handles
459    }
460
461    /// Returns type of auxiliary image.
462    ///
463    /// Returns an empty string if the image handle isn't auxiliary.
464    pub fn auxiliary_type(&self) -> Result<String> {
465        let mut type_str_ptr = ptr::null();
466        let err =
467            unsafe { lh::heif_image_handle_get_auxiliary_type(self.inner, &mut type_str_ptr) };
468        HeifError::from_heif_error(err)?;
469        let res = cstr_to_str(type_str_ptr).unwrap_or("").to_owned();
470        if !type_str_ptr.is_null() {
471            unsafe { lh::heif_image_handle_release_auxiliary_type(self.inner, &mut type_str_ptr) };
472        }
473        Ok(res)
474    }
475}
476
477#[derive(Copy, Clone, Default)]
478pub struct AuxiliaryImagesFilter(libc::c_int);
479
480impl AuxiliaryImagesFilter {
481    const ALPHA_MASK: libc::c_int = 1 << 1;
482    const DEPTH_MASK: libc::c_int = 2 << 1;
483
484    pub const OMIT_ALPHA: Self = Self::new().omit_alpha();
485    pub const OMIT_DEPTH: Self = Self::new().omit_depth();
486
487    pub const fn new() -> Self {
488        Self(0)
489    }
490
491    pub const fn is_omit_alpha(&self) -> bool {
492        (self.0 & Self::ALPHA_MASK) > 0
493    }
494
495    /// Adds a flag to the filter to exclude auxiliary images that
496    /// are an alpha channel.
497    pub const fn omit_alpha(self) -> Self {
498        Self(self.0 | Self::ALPHA_MASK)
499    }
500
501    pub const fn is_omit_depth(&self) -> bool {
502        (self.0 & Self::DEPTH_MASK) > 0
503    }
504
505    /// Adds a flag to the filter to exclude auxiliary images that
506    /// are a depth channel.
507    pub const fn omit_depth(self) -> Self {
508        Self(self.0 | Self::DEPTH_MASK)
509    }
510}
511
512impl Drop for ImageHandle {
513    fn drop(&mut self) {
514        unsafe { lh::heif_image_handle_release(self.inner) };
515    }
516}