Skip to main content

kreuzberg_libheif/
decoder.rs

1use std::ffi::CString;
2use std::fmt::{Debug, Formatter};
3use std::ptr;
4use std::sync::Mutex;
5
6use libheif_sys as lh;
7
8#[cfg(feature = "v1_20")]
9use crate::AlphaCompositionMode;
10use crate::utils::{cstr_to_str, str_to_cstring};
11use crate::{ChromaDownsamplingAlgorithm, ChromaUpsamplingAlgorithm, ColorProfileNCLX, HeifError};
12static DECODER_MUTEX: Mutex<()> = Mutex::new(());
13
14#[derive(Debug)]
15pub struct DecodingOptions {
16    inner: ptr::NonNull<lh::heif_decoding_options>,
17    decoder_id: Option<CString>,
18    #[allow(dead_code)]
19    output_image_nclx_profile: Option<ColorProfileNCLX>,
20}
21
22#[allow(unsafe_code)]
23impl DecodingOptions {
24    pub fn new() -> Option<Self> {
25        // SAFETY: libheif C API; returns a heap-allocated decoding options or null.
26        let inner_ptr = unsafe { lh::heif_decoding_options_alloc() };
27        ptr::NonNull::new(inner_ptr).map(|inner| Self {
28            inner,
29            decoder_id: None,
30            output_image_nclx_profile: None,
31        })
32    }
33}
34
35#[allow(unsafe_code)]
36impl Drop for DecodingOptions {
37    fn drop(&mut self) {
38        #[cfg(feature = "v1_20")]
39        {
40            let inner_mut = self.inner_mut();
41            if !inner_mut.color_conversion_options_ext.is_null() {
42                // SAFETY: libheif C API; color_conversion_options_ext is non-null and owned.
43                unsafe { lh::heif_color_conversion_options_ext_free(inner_mut.color_conversion_options_ext) };
44                inner_mut.color_conversion_options_ext = ptr::null_mut();
45            }
46        }
47        // SAFETY: self.inner is non-null and owned by this DecodingOptions; freeing it completes our ownership.
48        unsafe {
49            lh::heif_decoding_options_free(self.inner.as_ptr());
50        }
51    }
52}
53
54#[allow(unsafe_code)]
55impl DecodingOptions {
56    #[inline(always)]
57    #[allow(unsafe_code)]
58    fn inner_ref(&self) -> &lh::heif_decoding_options {
59        // SAFETY: self.inner is a valid NonNull; we have shared access.
60        unsafe { self.inner.as_ref() }
61    }
62
63    #[inline(always)]
64    #[allow(unsafe_code)]
65    pub(crate) fn inner_mut(&mut self) -> &mut lh::heif_decoding_options {
66        // SAFETY: self.inner is a valid NonNull; we have exclusive access.
67        unsafe { self.inner.as_mut() }
68    }
69
70    #[inline]
71    pub fn version(&self) -> u8 {
72        self.inner_ref().version
73    }
74
75    /// Ignore geometric transformations like cropping, rotation, mirroring.
76    /// Default: false (do not ignore).
77    #[inline]
78    pub fn ignore_transformations(&self) -> bool {
79        self.inner_ref().ignore_transformations != 0
80    }
81
82    #[inline]
83    pub fn set_ignore_transformations(&mut self, enable: bool) {
84        self.inner_mut().ignore_transformations = if enable { 1 } else { 0 }
85    }
86
87    #[inline]
88    pub fn convert_hdr_to_8bit(&self) -> bool {
89        self.inner_ref().convert_hdr_to_8bit != 0
90    }
91
92    #[inline]
93    pub fn set_convert_hdr_to_8bit(&mut self, enable: bool) {
94        self.inner_mut().convert_hdr_to_8bit = if enable { 1 } else { 0 }
95    }
96
97    /// When strict decoding is enabled, an error is returned for invalid input.
98    /// Otherwise, it will try its best and add decoding warnings to
99    /// the decoded `Image`. Default is non-strict.
100    pub fn strict_decoding(&self) -> bool {
101        self.inner_ref().strict_decoding != 0
102    }
103
104    pub fn set_strict_decoding(&mut self, enable: bool) {
105        self.inner_mut().strict_decoding = if enable { 1 } else { 0 }
106    }
107
108    /// ID of the decoder to use for the decoding.
109    /// If set to `None` (default), the highest priority decoder is chosen.
110    /// The priority is defined in the plugin.
111    pub fn decoder_id(&self) -> Option<&str> {
112        cstr_to_str(self.inner_ref().decoder_id)
113    }
114
115    pub fn set_decoder_id(&mut self, decoder_id: Option<&str>) -> Result<(), HeifError> {
116        if let Some(decoder_id) = decoder_id {
117            let c_decoder_id = str_to_cstring(decoder_id, "decoder_id")?;
118            self.inner_mut().decoder_id = c_decoder_id.as_ptr();
119            self.decoder_id = Some(c_decoder_id);
120        } else {
121            self.inner_mut().decoder_id = ptr::null() as _;
122            self.decoder_id = None;
123        }
124        Ok(())
125    }
126
127    pub fn color_conversion_options(&self) -> ColorConversionOptions {
128        let inner = self.inner_ref();
129        ColorConversionOptions::from_cc_options(&inner.color_conversion_options)
130    }
131
132    pub fn set_color_conversion_options(&mut self, options: ColorConversionOptions) {
133        let inner = self.inner_mut();
134        options.fill_cc_options(&mut inner.color_conversion_options);
135    }
136
137    #[cfg(feature = "v1_20")]
138    pub fn alpha_composition_mode(&self) -> AlphaCompositionMode {
139        let inner = self.inner_ref();
140        AlphaCompositionMode::from_libheif(inner.color_conversion_options_ext)
141    }
142
143    #[cfg(feature = "v1_20")]
144    #[allow(unsafe_code)]
145    pub fn set_alpha_composition_mode(&mut self, v: AlphaCompositionMode) {
146        let inner = self.inner_mut();
147        if inner.color_conversion_options_ext.is_null() {
148            // SAFETY: libheif C API; returns a heap-allocated color conversion options ext or null.
149            inner.color_conversion_options_ext = unsafe { lh::heif_color_conversion_options_ext_alloc() };
150        }
151        v.fill_libheif_cc_options_ext(inner.color_conversion_options_ext);
152    }
153
154    #[cfg(feature = "v1_21")]
155    /// If enabled, it will decode the media timeline,
156    /// ignoring the sequence tracks edit-list.
157    pub fn ignore_sequence_edit_list(&self) -> bool {
158        let inner = self.inner_ref();
159        inner.ignore_sequence_editlist != 0
160    }
161
162    #[cfg(feature = "v1_21")]
163    /// If enabled, it will decode the media timeline,
164    /// ignoring the sequence tracks edit-list.
165    pub fn set_ignore_sequence_edit_list(&mut self, v: bool) {
166        let inner = self.inner_mut();
167        inner.ignore_sequence_editlist = v as _;
168    }
169
170    #[cfg(feature = "v1_21")]
171    pub fn output_image_nclx_profile(&self) -> Option<&ColorProfileNCLX> {
172        self.output_image_nclx_profile.as_ref()
173    }
174
175    #[cfg(feature = "v1_21")]
176    pub fn set_output_image_nclx_profile(&mut self, v: Option<ColorProfileNCLX>) {
177        self.output_image_nclx_profile = v;
178        let profile_ptr = self
179            .output_image_nclx_profile
180            .as_ref()
181            .map(|v| v.inner)
182            .unwrap_or(ptr::null_mut());
183        let inner = self.inner_mut();
184        inner.output_image_nclx_profile = profile_ptr;
185    }
186
187    #[cfg(feature = "v1_21")]
188    /// 0 = let libheif decide (TODO, currently ignored)
189    pub fn num_library_threads(&self) -> u32 {
190        let inner = self.inner_ref();
191        inner.num_library_threads.max(0) as _
192    }
193
194    #[cfg(feature = "v1_21")]
195    /// 0 = let libheif decide (TODO, currently ignored)
196    pub fn set_num_library_threads(&mut self, v: u32) {
197        let inner = self.inner_mut();
198        inner.num_library_threads = v.min(i32::MAX as u32) as _;
199    }
200
201    #[cfg(feature = "v1_21")]
202    /// 0 = use decoder default
203    pub fn num_codec_threads(&self) -> u32 {
204        let inner = self.inner_ref();
205        inner.num_codec_threads.max(0) as _
206    }
207
208    #[cfg(feature = "v1_21")]
209    /// 0 = use decoder default
210    pub fn set_num_codec_threads(&mut self, v: u32) {
211        let inner = self.inner_mut();
212        inner.num_codec_threads = v.min(i32::MAX as u32) as _;
213    }
214}
215
216/// This function makes sure the decoding options
217/// won't be freed too early.
218pub(crate) fn get_decoding_options_ptr(options: &Option<DecodingOptions>) -> *mut lh::heif_decoding_options {
219    options.as_ref().map(|o| o.inner.as_ptr()).unwrap_or_else(ptr::null_mut)
220}
221
222#[derive(Debug, Copy, Clone)]
223pub struct ColorConversionOptions {
224    pub preferred_chroma_downsampling_algorithm: ChromaDownsamplingAlgorithm,
225    pub preferred_chroma_upsampling_algorithm: ChromaUpsamplingAlgorithm,
226    /// When set to `false`, libheif may also use a different algorithm
227    /// if the preferred one is not available.
228    pub only_use_preferred_chroma_algorithm: bool,
229}
230
231#[allow(unsafe_code)]
232impl Default for ColorConversionOptions {
233    fn default() -> Self {
234        #[allow(unused_mut)]
235        let mut cc_options = lh::heif_color_conversion_options {
236            version: 1,
237            preferred_chroma_downsampling_algorithm: 0,
238            preferred_chroma_upsampling_algorithm: 0,
239            only_use_preferred_chroma_algorithm: 0,
240        };
241        #[cfg(feature = "v1_19")]
242        {
243            // SAFETY: libheif C API; cc_options is valid mutable struct.
244            unsafe { lh::heif_color_conversion_options_set_defaults(&mut cc_options) };
245        }
246        Self::from_cc_options(&cc_options)
247    }
248}
249
250impl ColorConversionOptions {
251    pub fn new() -> Self {
252        Default::default()
253    }
254
255    pub(crate) fn from_cc_options(cc_options: &lh::heif_color_conversion_options) -> Self {
256        let preferred_chroma_downsampling_algorithm =
257            ChromaDownsamplingAlgorithm::n(cc_options.preferred_chroma_downsampling_algorithm)
258                .unwrap_or(ChromaDownsamplingAlgorithm::Average);
259        let preferred_chroma_upsampling_algorithm =
260            ChromaUpsamplingAlgorithm::n(cc_options.preferred_chroma_upsampling_algorithm)
261                .unwrap_or(ChromaUpsamplingAlgorithm::Bilinear);
262        let only_use_preferred_chroma_algorithm = cc_options.only_use_preferred_chroma_algorithm != 0;
263        Self {
264            preferred_chroma_downsampling_algorithm,
265            preferred_chroma_upsampling_algorithm,
266            only_use_preferred_chroma_algorithm,
267        }
268    }
269
270    pub(crate) fn fill_cc_options(&self, cc_options: &mut lh::heif_color_conversion_options) {
271        cc_options.preferred_chroma_downsampling_algorithm = self.preferred_chroma_downsampling_algorithm as _;
272        cc_options.preferred_chroma_upsampling_algorithm = self.preferred_chroma_upsampling_algorithm as _;
273        cc_options.only_use_preferred_chroma_algorithm = self.only_use_preferred_chroma_algorithm as _;
274    }
275}
276
277#[derive(Copy, Clone)]
278pub struct DecoderDescriptor<'a> {
279    inner: &'a lh::heif_decoder_descriptor,
280}
281
282#[allow(unsafe_code)]
283impl<'a> DecoderDescriptor<'a> {
284    pub(crate) fn new(inner: &'a lh::heif_decoder_descriptor) -> Self {
285        Self { inner }
286    }
287
288    /// A short, symbolic name for identifying the decoder.
289    /// This name should stay constant over different decoder versions.
290    #[allow(unsafe_code)]
291    pub fn id(&self) -> &str {
292        // SAFETY: libheif C API; self.inner is non-null; returns a static c string or null.
293        let name = unsafe { lh::heif_decoder_descriptor_get_id_name(self.inner) };
294        cstr_to_str(name).unwrap_or_default()
295    }
296
297    /// A long, descriptive name of the decoder
298    /// (including version information).
299    #[allow(unsafe_code)]
300    pub fn name(&self) -> String {
301        // Name of decoder in `libheif` is mutable static array of chars.
302        // So we must use mutex to get access this array.
303        let _lock = DECODER_MUTEX.lock();
304        // SAFETY: libheif C API; self.inner is non-null; protected by mutex.
305        let name = unsafe { lh::heif_decoder_descriptor_get_name(self.inner) };
306        cstr_to_str(name).unwrap_or_default().to_owned()
307    }
308}
309
310impl<'a> Debug for DecoderDescriptor<'a> {
311    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
312        f.debug_struct("DecoderDescriptor")
313            .field("id", &self.id())
314            .field("name", &self.name())
315            .finish()
316    }
317}