Skip to main content

libheif_rs/
context.rs

1#[cfg(feature = "v1_18")]
2use std::num::NonZeroU16;
3use std::os::raw::c_void;
4use std::{ffi, ptr};
5
6use four_cc::FourCC;
7use libheif_sys as lh;
8
9use crate::encoder::get_encoding_options_ptr;
10use crate::reader::{Reader, HEIF_READER};
11use crate::utils::str_to_cstring;
12#[cfg(feature = "v1_19")]
13use crate::SecurityLimits;
14#[cfg(feature = "v1_20")]
15use crate::Track;
16use crate::{
17    Encoder, EncodingOptions, HeifError, HeifErrorCode, HeifErrorSubCode, Image, ImageHandle,
18    ItemId, Result,
19};
20
21#[allow(dead_code)]
22enum Source<'a> {
23    None,
24    File,
25    Memory(&'a [u8]),
26    Reader(Box<Box<dyn Reader + 'a>>),
27}
28
29pub struct HeifContext<'a> {
30    pub(crate) inner: *mut lh::heif_context,
31    source: Source<'a>,
32}
33
34impl HeifContext<'static> {
35    /// Create a new empty context.
36    pub fn new() -> Result<HeifContext<'static>> {
37        let ctx = unsafe { lh::heif_context_alloc() };
38        if ctx.is_null() {
39            Err(HeifError {
40                code: HeifErrorCode::ContextCreateFailed,
41                sub_code: HeifErrorSubCode::Unspecified,
42                message: String::from(""),
43            })
44        } else {
45            Ok(HeifContext {
46                inner: ctx,
47                source: Source::None,
48            })
49        }
50    }
51
52    /// Create a new context from a file.
53    pub fn read_from_file(name: &str) -> Result<HeifContext<'static>> {
54        let mut context = HeifContext::new()?;
55        context.read_file(name)?;
56        Ok(context)
57    }
58
59    /// # Safety
60    ///
61    /// The given pointer must be valid.
62    #[cfg(feature = "v1_18")]
63    pub(crate) unsafe fn from_ptr(ctx: *mut lh::heif_context) -> HeifContext<'static> {
64        HeifContext {
65            inner: ctx,
66            source: Source::None,
67        }
68    }
69}
70
71impl<'a> HeifContext<'a> {
72    /// Read a HEIF file from a named disk file.
73    pub fn read_file(&mut self, name: &str) -> Result<()> {
74        self.source = Source::File;
75        let c_name = ffi::CString::new(name).unwrap();
76        let err =
77            unsafe { lh::heif_context_read_from_file(self.inner, c_name.as_ptr(), ptr::null()) };
78        HeifError::from_heif_error(err)?;
79        Ok(())
80    }
81
82    /// Read a HEIF file from the reader.
83    pub fn read_reader(&mut self, reader: Box<dyn Reader + 'a>) -> Result<()> {
84        let mut reader_box = Box::new(reader);
85        let user_data = reader_box.as_mut() as *mut _ as *mut c_void;
86        let err = unsafe {
87            lh::heif_context_read_from_reader(self.inner, &HEIF_READER, user_data, ptr::null())
88        };
89        HeifError::from_heif_error(err)?;
90        self.source = Source::Reader(reader_box);
91        Ok(())
92    }
93
94    /// Create a new context from the reader.
95    pub fn read_from_reader(reader: Box<dyn Reader + 'a>) -> Result<HeifContext<'a>> {
96        let mut context = HeifContext::new()?;
97        context.read_reader(reader)?;
98        Ok(context)
99    }
100
101    /// Create a new context from bytes.
102    ///
103    /// The provided memory buffer is not copied.
104    /// That means you will have to keep the memory buffer alive as
105    /// long as you use the context.
106    pub fn read_from_bytes(bytes: &[u8]) -> Result<HeifContext<'_>> {
107        let mut context = HeifContext::new()?;
108        context.read_bytes(bytes)?;
109        Ok(context)
110    }
111
112    /// Read a HEIF file from bytes.
113    ///
114    /// The provided memory buffer is not copied.
115    /// That means you will have to keep the memory buffer alive as
116    /// long as you use the context.
117    pub fn read_bytes<'b: 'a>(&mut self, bytes: &'b [u8]) -> Result<()> {
118        self.source = Source::Memory(bytes);
119        let err = unsafe {
120            lh::heif_context_read_from_memory_without_copy(
121                self.inner,
122                bytes.as_ptr() as _,
123                bytes.len(),
124                ptr::null(),
125            )
126        };
127        HeifError::from_heif_error(err)?;
128        Ok(())
129    }
130
131    unsafe extern "C" fn vector_writer(
132        _ctx: *mut lh::heif_context,
133        data: *const c_void,
134        size: usize,
135        user_data: *mut c_void,
136    ) -> lh::heif_error {
137        let vec: &mut Vec<u8> = &mut *(user_data as *mut Vec<u8>);
138        vec.reserve(size);
139        ptr::copy_nonoverlapping::<u8>(data as _, vec.as_mut_ptr(), size);
140        vec.set_len(size);
141
142        lh::heif_error {
143            code: lh::heif_error_code_heif_error_Ok,
144            subcode: lh::heif_suberror_code_heif_suberror_Unspecified,
145            message: b"\0".as_ptr() as _,
146        }
147    }
148
149    pub fn write_to_bytes(&self) -> Result<Vec<u8>> {
150        let mut res = Vec::<u8>::new();
151        let pointer_to_res = &mut res as *mut _ as *mut c_void;
152
153        let mut writer = lh::heif_writer {
154            writer_api_version: 1,
155            write: Some(Self::vector_writer),
156        };
157
158        let err = unsafe { lh::heif_context_write(self.inner, &mut writer, pointer_to_res) };
159        HeifError::from_heif_error(err)?;
160        Ok(res)
161    }
162
163    pub fn write_to_file(&self, name: &str) -> Result<()> {
164        let c_name = ffi::CString::new(name).unwrap();
165        let err = unsafe { lh::heif_context_write_to_file(self.inner, c_name.as_ptr()) };
166        HeifError::from_heif_error(err)
167    }
168
169    /// Number of top-level images in the HEIF file.
170    ///
171    /// This does not include the thumbnails or the tile images that
172    /// are composed to an image grid.
173    /// You can get access to the thumbnails via the main image handle.
174    #[deprecated(since = "2.7.0", note = "use 'image_ids' method instead.")]
175    pub fn number_of_top_level_images(&self) -> usize {
176        unsafe { lh::heif_context_get_number_of_top_level_images(self.inner) as _ }
177    }
178
179    /// Fills in image IDs into the user-supplied preallocated array 'ID_array'.
180    ///
181    /// Method returns the total number of IDs filled into the array.
182    #[deprecated(since = "2.7.0", note = "use 'image_ids' method instead.")]
183    pub fn top_level_image_ids(&self, item_ids: &mut [ItemId]) -> usize {
184        if item_ids.is_empty() {
185            0
186        } else {
187            unsafe {
188                lh::heif_context_get_list_of_top_level_image_IDs(
189                    self.inner,
190                    item_ids.as_mut_ptr(),
191                    item_ids.len() as _,
192                ) as usize
193            }
194        }
195    }
196
197    /// Returns a vector with top level image IDs.
198    pub fn image_ids(&self) -> Vec<ItemId> {
199        let count = unsafe { lh::heif_context_get_number_of_top_level_images(self.inner) as usize };
200        let mut item_ids = vec![0; count];
201        let real_count = unsafe {
202            lh::heif_context_get_list_of_top_level_image_IDs(
203                self.inner,
204                item_ids.as_mut_ptr(),
205                count as _,
206            ) as usize
207        };
208        item_ids.truncate(real_count);
209        item_ids
210    }
211
212    /// Get the image handle for a known image ID.
213    pub fn image_handle(&self, item_id: ItemId) -> Result<ImageHandle> {
214        let mut handle: *mut lh::heif_image_handle = ptr::null_mut();
215        let err = unsafe { lh::heif_context_get_image_handle(self.inner, item_id, &mut handle) };
216        HeifError::from_heif_error(err)?;
217        Ok(ImageHandle::new(handle))
218    }
219
220    /// Get a handle to the primary image of the HEIF file.
221    ///
222    /// This is the image that should be displayed primarily
223    /// when there are several images in the file.
224    pub fn primary_image_handle(&self) -> Result<ImageHandle> {
225        let mut handle: *mut lh::heif_image_handle = ptr::null_mut();
226        let err = unsafe { lh::heif_context_get_primary_image_handle(self.inner, &mut handle) };
227        HeifError::from_heif_error(err)?;
228        Ok(ImageHandle::new(handle))
229    }
230
231    /// Returns a vector with top level image handles.
232    pub fn top_level_image_handles(&self) -> Vec<ImageHandle> {
233        let item_ids = self.image_ids();
234        let mut handles = Vec::with_capacity(item_ids.len());
235        for item_id in item_ids {
236            if let Ok(handle) = self.image_handle(item_id) {
237                handles.push(handle);
238            }
239        }
240        handles
241    }
242
243    /// Compress the input image.
244    ///
245    /// The first image added to the context is also automatically set as the primary image, but
246    /// you can change the primary image later with [`HeifContext::set_primary_image`] method.
247    pub fn encode_image(
248        &mut self,
249        image: &Image,
250        encoder: &mut Encoder,
251        encoding_options: Option<EncodingOptions>,
252    ) -> Result<ImageHandle> {
253        let mut handle: *mut lh::heif_image_handle = ptr::null_mut();
254        unsafe {
255            let err = lh::heif_context_encode_image(
256                self.inner,
257                image.inner,
258                encoder.inner,
259                get_encoding_options_ptr(&encoding_options),
260                &mut handle,
261            );
262            HeifError::from_heif_error(err)?;
263        }
264        Ok(ImageHandle::new(handle))
265    }
266
267    /// Encode the `image` as a scaled down thumbnail image.
268    ///
269    /// The image is scaled down to fit into a square area of width `bbox_size`.
270    /// If the input image is already so small that it fits into this bounding
271    /// box, no thumbnail image is encoded and `Ok(None)` is returned.
272    /// No error is returned in this case.
273    ///
274    /// The encoded thumbnail is automatically assigned to the
275    /// `master_image_handle`. Hence, you do not have to call
276    /// [`HeifContext::assign_thumbnail()`] method.
277    pub fn encode_thumbnail(
278        &mut self,
279        image: &Image,
280        master_image_handle: &ImageHandle,
281        bbox_size: u32,
282        encoder: &mut Encoder,
283        encoding_options: Option<EncodingOptions>,
284    ) -> Result<Option<ImageHandle>> {
285        let mut handle: *mut lh::heif_image_handle = ptr::null_mut();
286        unsafe {
287            let err = lh::heif_context_encode_thumbnail(
288                self.inner,
289                image.inner,
290                master_image_handle.inner,
291                encoder.inner,
292                get_encoding_options_ptr(&encoding_options),
293                bbox_size.min(i32::MAX as _) as _,
294                &mut handle,
295            );
296            HeifError::from_heif_error(err)?;
297        }
298        Ok(Some(ImageHandle::new(handle)))
299    }
300
301    /// Encodes an array of images into a grid.
302    ///
303    /// # Arguments
304    ///
305    /// * `tiles` - User allocated array of images that will form the grid.
306    /// * `rows` - The number of rows in the grid. The number of columns will
307    ///   be calculated from the size of `tiles`.
308    /// * `encoder` - Defines the encoder to use.
309    ///   See [LibHeif::encoder_for_format()](crate::LibHeif::encoder_for_format).
310    /// * `encoding_options` - Optional, may be None.
311    ///
312    /// Returns an error if `tiles` slice is empty.
313    #[cfg(feature = "v1_18")]
314    pub fn encode_grid(
315        &mut self,
316        tiles: &[Image],
317        rows: NonZeroU16,
318        encoder: &mut Encoder,
319        encoding_options: Option<EncodingOptions>,
320    ) -> Result<Option<ImageHandle>> {
321        let mut handle: *mut lh::heif_image_handle = ptr::null_mut();
322        let mut tiles_inners: Vec<*mut lh::heif_image> =
323            tiles.iter().map(|img| img.inner).collect();
324        let rows = rows.get();
325        let columns = (tiles_inners.len() as u32 / rows as u32).min(u16::MAX as _) as u16;
326        unsafe {
327            let err = lh::heif_context_encode_grid(
328                self.inner,
329                tiles_inners.as_mut_ptr(),
330                rows,
331                columns,
332                encoder.inner,
333                get_encoding_options_ptr(&encoding_options),
334                &mut handle,
335            );
336            HeifError::from_heif_error(err)?;
337        }
338        Ok(Some(ImageHandle::new(handle)))
339    }
340
341    /// Assign `master_image_handle` as the thumbnail image of `thumbnail_image_handle`.
342    pub fn assign_thumbnail(
343        &mut self,
344        master_image_handle: &ImageHandle,
345        thumbnail_image_handle: &ImageHandle,
346    ) -> Result<()> {
347        unsafe {
348            let err = lh::heif_context_assign_thumbnail(
349                self.inner,
350                master_image_handle.inner,
351                thumbnail_image_handle.inner,
352            );
353            HeifError::from_heif_error(err)
354        }
355    }
356
357    pub fn set_primary_image(&mut self, image_handle: &mut ImageHandle) -> Result<()> {
358        unsafe {
359            let err = lh::heif_context_set_primary_image(self.inner, image_handle.inner);
360            HeifError::from_heif_error(err)
361        }
362    }
363
364    /// Add generic, proprietary metadata to an image.
365    ///
366    /// You have to specify an `item_type` that will identify your metadata.
367    /// `content_type` can be an additional type.
368    ///
369    /// For example, this function can be used to add IPTC metadata
370    /// (IIM stream, not XMP) to an image. Although not standard, we propose
371    /// to store IPTC data with `item_type=b"iptc"` and `content_type=None`.
372    pub fn add_generic_metadata<T>(
373        &mut self,
374        image_handle: &ImageHandle,
375        data: &[u8],
376        item_type: T,
377        content_type: Option<&str>,
378    ) -> Result<()>
379    where
380        T: Into<FourCC>,
381    {
382        let c_item_type = str_to_cstring(&item_type.into().to_string(), "item_type")?;
383        let c_content_type = match content_type {
384            Some(s) => Some(str_to_cstring(s, "content_type")?),
385            None => None,
386        };
387        let c_content_type_ptr = c_content_type.map(|s| s.as_ptr()).unwrap_or(ptr::null());
388        let error = unsafe {
389            lh::heif_context_add_generic_metadata(
390                self.inner,
391                image_handle.inner,
392                data.as_ptr() as _,
393                data.len() as _,
394                c_item_type.as_ptr(),
395                c_content_type_ptr,
396            )
397        };
398        HeifError::from_heif_error(error)
399    }
400
401    /// Add EXIF metadata to an image.
402    pub fn add_exif_metadata(&mut self, master_image: &ImageHandle, data: &[u8]) -> Result<()> {
403        let error = unsafe {
404            lh::heif_context_add_exif_metadata(
405                self.inner,
406                master_image.inner,
407                data.as_ptr() as _,
408                data.len() as _,
409            )
410        };
411        HeifError::from_heif_error(error)
412    }
413
414    /// Add XMP metadata to an image.
415    pub fn add_xmp_metadata(&mut self, master_image: &ImageHandle, data: &[u8]) -> Result<()> {
416        let error = unsafe {
417            lh::heif_context_add_XMP_metadata(
418                self.inner,
419                master_image.inner,
420                data.as_ptr() as _,
421                data.len() as _,
422            )
423        };
424        HeifError::from_heif_error(error)
425    }
426
427    /// If the maximum threads number is set to 0, the image tiles are
428    /// decoded in the main thread. This is different from setting it to 1,
429    /// which will generate a single background thread to decode the tiles.
430    ///
431    /// Note that this setting only affects `libheif` itself. The codecs itself
432    /// may still use multi-threaded decoding. You can use it, for example,
433    /// in cases where you are decoding several images in parallel anyway you
434    /// thus want to minimize parallelism in each decoder.
435    pub fn set_max_decoding_threads(&mut self, max_threads: u32) {
436        let max_threads = max_threads.min(libc::c_int::MAX as u32) as libc::c_int;
437        unsafe { lh::heif_context_set_max_decoding_threads(self.inner, max_threads) };
438    }
439
440    /// Returns the security limits for a context.
441    ///
442    /// By default, the limits are set to the global limits,
443    /// but you can change them with the help of [`HeifContext::set_security_limits()`] method.
444    #[cfg(feature = "v1_19")]
445    pub fn security_limits(&self) -> SecurityLimits {
446        let inner_ptr = unsafe { lh::heif_context_get_security_limits(self.inner) };
447        let inner = ptr::NonNull::new(inner_ptr).unwrap();
448        SecurityLimits::from_inner(inner)
449    }
450
451    /// Overwrites the security limits of a context.
452    #[cfg(feature = "v1_19")]
453    pub fn set_security_limits(&mut self, limits: &SecurityLimits) -> Result<()> {
454        let err = unsafe { lh::heif_context_set_security_limits(self.inner, limits.as_inner()) };
455        HeifError::from_heif_error(err)
456    }
457
458    /// Check whether there is an image sequence in the HEIF file.
459    #[cfg(feature = "v1_20")]
460    pub fn has_sequence(&self) -> bool {
461        unsafe { lh::heif_context_has_sequence(self.inner) != 0 }
462    }
463
464    /// Get the timescale (clock ticks per second) for timing values in the sequence.
465    ///
466    /// Each track may have its independent timescale.
467    ///
468    /// Returns 0 if there is no sequence in the file.
469    #[cfg(feature = "v1_20")]
470    pub fn sequence_timescale(&self) -> u32 {
471        unsafe { lh::heif_context_get_sequence_timescale(self.inner) }
472    }
473
474    /// Get the total duration of the sequence in timescale clock ticks.
475    ///
476    /// Use [sequence_timescale] method to get the clock ticks per second.
477    ///
478    /// Returns 0 if there is no sequence in the file.
479    #[cfg(feature = "v1_20")]
480    pub fn sequence_duration(&self) -> u64 {
481        unsafe { lh::heif_context_get_sequence_duration(self.inner) }
482    }
483
484    /// Returns a vector with IDs for each of the tracks stored in the HEIF file.
485    #[cfg(feature = "v1_20")]
486    pub fn track_ids(&self) -> Vec<u32> {
487        let num_tracks = unsafe { lh::heif_context_number_of_sequence_tracks(self.inner) as usize };
488        let mut track_ids = vec![0u32; num_tracks];
489
490        unsafe {
491            lh::heif_context_get_track_ids(self.inner, track_ids.as_mut_ptr() as *mut [u32; 0]);
492        }
493
494        track_ids
495    }
496
497    /// Get the [Track] object for the given track ID.
498    ///
499    /// If you pass id=0, the first visual track will be returned.
500    /// If there is no track with the given ID or if 0 is passed and
501    /// there is no visual track, `None` will be returned.
502    ///
503    /// Tracks never have a zero ID.
504    /// This is why we can use this as a special value to find the first visual track.
505    #[cfg(feature = "v1_20")]
506    pub fn track(&self, id: u32) -> Option<Track> {
507        unsafe {
508            let heif_track = lh::heif_context_get_track(self.inner, id);
509            if heif_track.is_null() {
510                None
511            } else {
512                Some(Track::from_heif_track(heif_track))
513            }
514        }
515    }
516}
517
518impl Drop for HeifContext<'_> {
519    fn drop(&mut self) {
520        unsafe { lh::heif_context_free(self.inner) };
521    }
522}
523
524unsafe impl Send for HeifContext<'_> {}