Skip to main content

libheif_rs/
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
8use crate::utils::{cstr_to_str, str_to_cstring};
9#[cfg(feature = "v1_20")]
10use crate::AlphaCompositionMode;
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
22impl DecodingOptions {
23    pub fn new() -> Option<Self> {
24        let inner_ptr = unsafe { lh::heif_decoding_options_alloc() };
25        ptr::NonNull::new(inner_ptr).map(|inner| Self {
26            inner,
27            decoder_id: None,
28            output_image_nclx_profile: None,
29        })
30    }
31}
32
33impl Drop for DecodingOptions {
34    fn drop(&mut self) {
35        #[cfg(feature = "v1_20")]
36        {
37            let inner_mut = self.inner_mut();
38            if !inner_mut.color_conversion_options_ext.is_null() {
39                unsafe {
40                    lh::heif_color_conversion_options_ext_free(
41                        inner_mut.color_conversion_options_ext,
42                    )
43                };
44                inner_mut.color_conversion_options_ext = ptr::null_mut();
45            }
46        }
47        unsafe {
48            lh::heif_decoding_options_free(self.inner.as_ptr());
49        }
50    }
51}
52
53impl DecodingOptions {
54    #[inline(always)]
55    fn inner_ref(&self) -> &lh::heif_decoding_options {
56        unsafe { self.inner.as_ref() }
57    }
58
59    #[inline(always)]
60    pub(crate) fn inner_mut(&mut self) -> &mut lh::heif_decoding_options {
61        unsafe { self.inner.as_mut() }
62    }
63
64    #[inline]
65    pub fn version(&self) -> u8 {
66        self.inner_ref().version
67    }
68
69    /// Ignore geometric transformations like cropping, rotation, mirroring.
70    /// Default: false (do not ignore).
71    #[inline]
72    pub fn ignore_transformations(&self) -> bool {
73        self.inner_ref().ignore_transformations != 0
74    }
75
76    #[inline]
77    pub fn set_ignore_transformations(&mut self, enable: bool) {
78        self.inner_mut().ignore_transformations = if enable { 1 } else { 0 }
79    }
80
81    #[inline]
82    pub fn convert_hdr_to_8bit(&self) -> bool {
83        self.inner_ref().convert_hdr_to_8bit != 0
84    }
85
86    #[inline]
87    pub fn set_convert_hdr_to_8bit(&mut self, enable: bool) {
88        self.inner_mut().convert_hdr_to_8bit = if enable { 1 } else { 0 }
89    }
90
91    /// When strict decoding is enabled, an error is returned for invalid input.
92    /// Otherwise, it will try its best and add decoding warnings to
93    /// the decoded `Image`. Default is non-strict.
94    pub fn strict_decoding(&self) -> bool {
95        self.inner_ref().strict_decoding != 0
96    }
97
98    pub fn set_strict_decoding(&mut self, enable: bool) {
99        self.inner_mut().strict_decoding = if enable { 1 } else { 0 }
100    }
101
102    /// ID of the decoder to use for the decoding.
103    /// If set to `None` (default), the highest priority decoder is chosen.
104    /// The priority is defined in the plugin.
105    pub fn decoder_id(&self) -> Option<&str> {
106        cstr_to_str(self.inner_ref().decoder_id)
107    }
108
109    pub fn set_decoder_id(&mut self, decoder_id: Option<&str>) -> Result<(), HeifError> {
110        if let Some(decoder_id) = decoder_id {
111            let c_decoder_id = str_to_cstring(decoder_id, "decoder_id")?;
112            self.inner_mut().decoder_id = c_decoder_id.as_ptr();
113            self.decoder_id = Some(c_decoder_id);
114        } else {
115            self.inner_mut().decoder_id = ptr::null() as _;
116            self.decoder_id = None;
117        }
118        Ok(())
119    }
120
121    pub fn color_conversion_options(&self) -> ColorConversionOptions {
122        let inner = self.inner_ref();
123        ColorConversionOptions::from_cc_options(&inner.color_conversion_options)
124    }
125
126    pub fn set_color_conversion_options(&mut self, options: ColorConversionOptions) {
127        let inner = self.inner_mut();
128        options.fill_cc_options(&mut inner.color_conversion_options);
129    }
130
131    #[cfg(feature = "v1_20")]
132    pub fn alpha_composition_mode(&self) -> AlphaCompositionMode {
133        let inner = self.inner_ref();
134        AlphaCompositionMode::from_libheif(inner.color_conversion_options_ext)
135    }
136
137    #[cfg(feature = "v1_20")]
138    pub fn set_alpha_composition_mode(&mut self, v: AlphaCompositionMode) {
139        let inner = self.inner_mut();
140        if inner.color_conversion_options_ext.is_null() {
141            inner.color_conversion_options_ext =
142                unsafe { lh::heif_color_conversion_options_ext_alloc() };
143        }
144        v.fill_libheif_cc_options_ext(inner.color_conversion_options_ext);
145    }
146
147    #[cfg(feature = "v1_21")]
148    /// If enabled, it will decode the media timeline,
149    /// ignoring the sequence tracks edit-list.
150    pub fn ignore_sequence_edit_list(&self) -> bool {
151        let inner = self.inner_ref();
152        inner.ignore_sequence_editlist != 0
153    }
154
155    #[cfg(feature = "v1_21")]
156    /// If enabled, it will decode the media timeline,
157    /// ignoring the sequence tracks edit-list.
158    pub fn set_ignore_sequence_edit_list(&mut self, v: bool) {
159        let inner = self.inner_mut();
160        inner.ignore_sequence_editlist = v as _;
161    }
162
163    #[cfg(feature = "v1_21")]
164    pub fn output_image_nclx_profile(&self) -> Option<&ColorProfileNCLX> {
165        self.output_image_nclx_profile.as_ref()
166    }
167
168    #[cfg(feature = "v1_21")]
169    pub fn set_output_image_nclx_profile(&mut self, v: Option<ColorProfileNCLX>) {
170        self.output_image_nclx_profile = v;
171        let profile_ptr = self
172            .output_image_nclx_profile
173            .as_ref()
174            .map(|v| v.inner)
175            .unwrap_or(ptr::null_mut());
176        let inner = self.inner_mut();
177        inner.output_image_nclx_profile = profile_ptr;
178    }
179
180    #[cfg(feature = "v1_21")]
181    /// 0 = let libheif decide (TODO, currently ignored)
182    pub fn num_library_threads(&self) -> u32 {
183        let inner = self.inner_ref();
184        inner.num_library_threads.max(0) as _
185    }
186
187    #[cfg(feature = "v1_21")]
188    /// 0 = let libheif decide (TODO, currently ignored)
189    pub fn set_num_library_threads(&mut self, v: u32) {
190        let inner = self.inner_mut();
191        inner.num_library_threads = v.min(i32::MAX as u32) as _;
192    }
193
194    #[cfg(feature = "v1_21")]
195    /// 0 = use decoder default
196    pub fn num_codec_threads(&self) -> u32 {
197        let inner = self.inner_ref();
198        inner.num_codec_threads.max(0) as _
199    }
200
201    #[cfg(feature = "v1_21")]
202    /// 0 = use decoder default
203    pub fn set_num_codec_threads(&mut self, v: u32) {
204        let inner = self.inner_mut();
205        inner.num_codec_threads = v.min(i32::MAX as u32) as _;
206    }
207
208    #[cfg(feature = "v1_23")]
209    /// If enabled, `libheif` will attempt to work around known broken-input quirks
210    /// (e.g. Sony HIF files where the NCLX colr box disagrees with the HEVC VUI
211    /// on the YCbCr range flag).
212    ///
213    /// Default: `false` (strict spec-conformant behavior).
214    pub fn autocorrect_broken_input(&self) -> bool {
215        let inner = self.inner_ref();
216        inner.autocorrect_broken_input > 0
217    }
218
219    #[cfg(feature = "v1_23")]
220    pub fn set_autocorrect_broken_input(&mut self, v: bool) {
221        let inner = self.inner_mut();
222        inner.autocorrect_broken_input = if v { 1 } else { 0 };
223    }
224
225    #[cfg(feature = "v1_23")]
226    /// Controls the meaning of `output_image_nclx_profile == None`.
227    ///
228    /// When `false` (default), a None `output_image_nclx_profile` means
229    /// "convert the decoded image to sRGB" (BT.709 primaries, sRGB transfer,
230    /// BT.601 matrix, full-range).
231    /// For HDR inputs (e.g. BT.2100 PQ) this silently discards the original
232    /// color volume.
233    ///
234    /// When `true`, a None `output_image_nclx_profile` means "keep the input
235    /// image's NCLX".
236    /// The decoded image carries the input file's primaries / transfer / matrix / range,
237    /// and no extra color-space conversion is performed solely because
238    /// the output NCLX was unspecified.
239    /// If a YCbCr<->RGB colorspace conversion fires for another reason,
240    /// the input NCLX is used to drive that conversion (so the result is
241    /// tagged consistently with the source).
242    ///
243    /// Although this flag is off by default to preserve historical behavior,
244    /// new code that wants to preserve HDR through decoding should generally
245    /// enable it.
246    /// Setting `output_image_nclx_profile` to a non-None value overrides this flag.
247    pub fn output_image_nclx_profile_passthrough(&self) -> bool {
248        let inner = self.inner_ref();
249        inner.output_image_nclx_profile_passthrough > 0
250    }
251
252    #[cfg(feature = "v1_23")]
253    pub fn set_output_image_nclx_profile_passthrough(&mut self, v: bool) {
254        let inner = self.inner_mut();
255        inner.output_image_nclx_profile_passthrough = if v { 1 } else { 0 };
256    }
257}
258
259/// This function makes sure the decoding options
260/// won't be freed too early.
261pub(crate) fn get_decoding_options_ptr(
262    options: &Option<DecodingOptions>,
263) -> *mut lh::heif_decoding_options {
264    options
265        .as_ref()
266        .map(|o| o.inner.as_ptr())
267        .unwrap_or_else(ptr::null_mut)
268}
269
270#[derive(Debug, Copy, Clone)]
271pub struct ColorConversionOptions {
272    pub preferred_chroma_downsampling_algorithm: ChromaDownsamplingAlgorithm,
273    pub preferred_chroma_upsampling_algorithm: ChromaUpsamplingAlgorithm,
274    /// When set to `false`, libheif may also use a different algorithm
275    /// if the preferred one is not available.
276    pub only_use_preferred_chroma_algorithm: bool,
277}
278
279impl Default for ColorConversionOptions {
280    fn default() -> Self {
281        #[allow(unused_mut)]
282        let mut cc_options = lh::heif_color_conversion_options {
283            version: 1,
284            preferred_chroma_downsampling_algorithm: 0,
285            preferred_chroma_upsampling_algorithm: 0,
286            only_use_preferred_chroma_algorithm: 0,
287        };
288        #[cfg(feature = "v1_19")]
289        unsafe {
290            lh::heif_color_conversion_options_set_defaults(&mut cc_options)
291        };
292        Self::from_cc_options(&cc_options)
293    }
294}
295
296impl ColorConversionOptions {
297    pub fn new() -> Self {
298        Default::default()
299    }
300
301    pub(crate) fn from_cc_options(cc_options: &lh::heif_color_conversion_options) -> Self {
302        let preferred_chroma_downsampling_algorithm =
303            ChromaDownsamplingAlgorithm::n(cc_options.preferred_chroma_downsampling_algorithm)
304                .unwrap_or(ChromaDownsamplingAlgorithm::Average);
305        let preferred_chroma_upsampling_algorithm =
306            ChromaUpsamplingAlgorithm::n(cc_options.preferred_chroma_upsampling_algorithm)
307                .unwrap_or(ChromaUpsamplingAlgorithm::Bilinear);
308        let only_use_preferred_chroma_algorithm =
309            cc_options.only_use_preferred_chroma_algorithm != 0;
310        Self {
311            preferred_chroma_downsampling_algorithm,
312            preferred_chroma_upsampling_algorithm,
313            only_use_preferred_chroma_algorithm,
314        }
315    }
316
317    pub(crate) fn fill_cc_options(&self, cc_options: &mut lh::heif_color_conversion_options) {
318        cc_options.preferred_chroma_downsampling_algorithm =
319            self.preferred_chroma_downsampling_algorithm as _;
320        cc_options.preferred_chroma_upsampling_algorithm =
321            self.preferred_chroma_upsampling_algorithm as _;
322        cc_options.only_use_preferred_chroma_algorithm =
323            self.only_use_preferred_chroma_algorithm as _;
324    }
325}
326
327#[derive(Copy, Clone)]
328pub struct DecoderDescriptor<'a> {
329    inner: &'a lh::heif_decoder_descriptor,
330}
331
332impl<'a> DecoderDescriptor<'a> {
333    pub(crate) fn new(inner: &'a lh::heif_decoder_descriptor) -> Self {
334        Self { inner }
335    }
336
337    /// A short, symbolic name for identifying the decoder.
338    /// This name should stay constant over different decoder versions.
339    pub fn id(&self) -> &str {
340        let name = unsafe { lh::heif_decoder_descriptor_get_id_name(self.inner) };
341        cstr_to_str(name).unwrap_or_default()
342    }
343
344    /// A long, descriptive name of the decoder
345    /// (including version information).
346    pub fn name(&self) -> String {
347        // Name of decoder in `libheif` is mutable static array of chars.
348        // So we must use mutex to get access this array.
349        let _lock = DECODER_MUTEX.lock();
350        let name = unsafe { lh::heif_decoder_descriptor_get_name(self.inner) };
351        cstr_to_str(name).unwrap_or_default().to_owned()
352    }
353}
354
355impl<'a> Debug for DecoderDescriptor<'a> {
356    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
357        f.debug_struct("DecoderDescriptor")
358            .field("id", &self.id())
359            .field("name", &self.name())
360            .finish()
361    }
362}