libheif_rs/
encoder.rs

1use std::collections::HashMap;
2use std::ffi::CString;
3use std::fmt::{Debug, Formatter};
4use std::marker::PhantomData;
5use std::ptr;
6use std::sync::Mutex;
7
8use libheif_sys as lh;
9
10use crate::utils::cstr_to_str;
11use crate::{
12    ChromaDownsamplingAlgorithm, ChromaUpsamplingAlgorithm, ColorConversionOptions, HeifError,
13    HeifErrorCode, HeifErrorSubCode, ImageOrientation, Result,
14};
15
16static ENCODER_MUTEX: Mutex<()> = Mutex::new(());
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, enumn::N)]
19#[non_exhaustive]
20#[repr(C)]
21pub enum CompressionFormat {
22    /// Rust equivalent of [lh::heif_compression_format_heif_compression_undefined]
23    Undefined = lh::heif_compression_format_heif_compression_undefined as _,
24    /// Rust equivalent of [lh::heif_compression_format_heif_compression_HEVC]
25    Hevc = lh::heif_compression_format_heif_compression_HEVC as _,
26    /// Rust equivalent of [lh::heif_compression_format_heif_compression_AVC]
27    Avc = lh::heif_compression_format_heif_compression_AVC as _,
28    /// Rust equivalent of [lh::heif_compression_format_heif_compression_JPEG]
29    Jpeg = lh::heif_compression_format_heif_compression_JPEG as _,
30    /// Rust equivalent of [lh::heif_compression_format_heif_compression_AV1]    
31    Av1 = lh::heif_compression_format_heif_compression_AV1 as _,
32    /// Rust equivalent of [lh::heif_compression_format_heif_compression_VVC]
33    Vvc = lh::heif_compression_format_heif_compression_VVC as _,
34    /// Rust equivalent of [lh::heif_compression_format_heif_compression_EVC]
35    Evc = lh::heif_compression_format_heif_compression_EVC as _,
36    /// Rust equivalent of [lh::heif_compression_format_heif_compression_JPEG2000]
37    Jpeg2000 = lh::heif_compression_format_heif_compression_JPEG2000 as _,
38    /// Rust equivalent of [lh::heif_compression_format_heif_compression_uncompressed]
39    Uncompressed = lh::heif_compression_format_heif_compression_uncompressed as _,
40    /// Rust equivalent of [lh::heif_compression_format_heif_compression_mask]
41    Mask = lh::heif_compression_format_heif_compression_mask as _,
42    /// Rust equivalent of [lh::heif_compression_format_heif_compression_HTJ2K]
43    #[cfg(feature = "v1_18")]
44    HtJ2k = lh::heif_compression_format_heif_compression_HTJ2K as _,
45}
46
47#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, enumn::N)]
48#[repr(C)]
49pub enum EncoderParameterType {
50    Int = lh::heif_encoder_parameter_type_heif_encoder_parameter_type_integer as _,
51    Bool = lh::heif_encoder_parameter_type_heif_encoder_parameter_type_boolean as _,
52    String = lh::heif_encoder_parameter_type_heif_encoder_parameter_type_string as _,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash)]
56pub enum EncoderParameterValue {
57    Int(i32),
58    Bool(bool),
59    String(String),
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
63pub enum EncoderQuality {
64    LossLess,
65    /// Value inside variant is a 'quality' factor (0-100).
66    /// How this is mapped to actual encoding parameters is encoder dependent.
67    Lossy(u8),
68}
69
70pub type EncoderParametersTypes = HashMap<String, EncoderParameterType>;
71
72pub struct Encoder<'a> {
73    pub(crate) inner: *mut lh::heif_encoder,
74    pub(crate) parameters_types: EncoderParametersTypes,
75    phantom: PhantomData<&'a mut lh::heif_encoder>,
76}
77
78impl<'a> Encoder<'a> {
79    pub(crate) fn new(c_encoder: &'a mut lh::heif_encoder) -> Result<Self> {
80        let parameters_types = parameters_types(c_encoder)?;
81        Ok(Self {
82            inner: c_encoder,
83            parameters_types,
84            phantom: PhantomData::default(),
85        })
86    }
87}
88
89impl<'a> Drop for Encoder<'a> {
90    fn drop(&mut self) {
91        unsafe { lh::heif_encoder_release(self.inner) };
92    }
93}
94
95impl<'a> Encoder<'a> {
96    /// Name of encoder.
97    pub fn name(&self) -> String {
98        // Name of encoder in `libheif` is mutable static array of chars.
99        // So we must use mutex to get access this array.
100        let _lock = ENCODER_MUTEX.lock();
101        let res = unsafe { lh::heif_encoder_get_name(self.inner) };
102        cstr_to_str(res).unwrap_or("").to_owned()
103    }
104
105    pub fn set_quality(&mut self, quality: EncoderQuality) -> Result<()> {
106        let err = match quality {
107            EncoderQuality::LossLess => unsafe { lh::heif_encoder_set_lossless(self.inner, 1) },
108            EncoderQuality::Lossy(value) => unsafe {
109                let middle_err = lh::heif_encoder_set_lossless(self.inner, 0);
110                HeifError::from_heif_error(middle_err)?;
111                lh::heif_encoder_set_lossy_quality(self.inner, i32::from(value))
112            },
113        };
114        HeifError::from_heif_error(err)
115    }
116
117    fn parameter_value(
118        &self,
119        name: &str,
120        parameter_type: EncoderParameterType,
121    ) -> Result<EncoderParameterValue> {
122        let c_param_name = CString::new(name).unwrap();
123        let param_value = match parameter_type {
124            EncoderParameterType::Int => {
125                let mut value = 0;
126                let err = unsafe {
127                    lh::heif_encoder_get_parameter_integer(
128                        self.inner,
129                        c_param_name.as_ptr(),
130                        &mut value as _,
131                    )
132                };
133                HeifError::from_heif_error(err)?;
134                EncoderParameterValue::Int(value)
135            }
136            EncoderParameterType::Bool => {
137                let mut value = 0;
138                let err = unsafe {
139                    lh::heif_encoder_get_parameter_boolean(
140                        self.inner,
141                        c_param_name.as_ptr(),
142                        &mut value as _,
143                    )
144                };
145                HeifError::from_heif_error(err)?;
146                EncoderParameterValue::Bool(value > 0)
147            }
148            EncoderParameterType::String => {
149                let value: Vec<u8> = vec![0; 51];
150                let err = unsafe {
151                    lh::heif_encoder_get_parameter_string(
152                        self.inner,
153                        c_param_name.as_ptr(),
154                        value.as_ptr() as _,
155                        50,
156                    )
157                };
158                HeifError::from_heif_error(err)?;
159                EncoderParameterValue::String(
160                    cstr_to_str(value.as_ptr() as _).unwrap_or("").to_string(),
161                )
162            }
163        };
164
165        Ok(param_value)
166    }
167
168    pub fn parameters_names(&self) -> Vec<String> {
169        self.parameters_types.keys().cloned().collect()
170    }
171
172    /// Get value of encoder's parameter.
173    pub fn parameter(&self, name: &str) -> Result<Option<EncoderParameterValue>> {
174        match self.parameters_types.get(name) {
175            Some(param_type) => {
176                let value = self.parameter_value(name, *param_type)?;
177                Ok(Some(value))
178            }
179            None => Ok(None),
180        }
181    }
182
183    /// Set value of encoder's parameter.
184    pub fn set_parameter_value(&self, name: &str, value: EncoderParameterValue) -> Result<()> {
185        let c_param_name = CString::new(name).unwrap();
186        let err = match value {
187            EncoderParameterValue::Bool(v) => unsafe {
188                lh::heif_encoder_set_parameter_boolean(self.inner, c_param_name.as_ptr(), v.into())
189            },
190            EncoderParameterValue::Int(v) => unsafe {
191                lh::heif_encoder_set_parameter_integer(self.inner, c_param_name.as_ptr(), v)
192            },
193            EncoderParameterValue::String(v) => unsafe {
194                let c_param_value = CString::new(v).unwrap();
195                lh::heif_encoder_set_parameter_string(
196                    self.inner,
197                    c_param_name.as_ptr(),
198                    c_param_value.as_ptr(),
199                )
200            },
201        };
202        HeifError::from_heif_error(err)?;
203        Ok(())
204    }
205}
206
207fn parameters_types(c_encoder: &mut lh::heif_encoder) -> Result<EncoderParametersTypes> {
208    let mut res = EncoderParametersTypes::new();
209    unsafe {
210        let mut param_pointers = lh::heif_encoder_list_parameters(c_encoder);
211        if !param_pointers.is_null() {
212            while let Some(raw_param) = (*param_pointers).as_ref() {
213                let c_param_type = lh::heif_encoder_parameter_get_type(raw_param);
214                let param_type = match EncoderParameterType::n(c_param_type) {
215                    Some(res) => res,
216                    None => {
217                        return Err(HeifError {
218                            code: HeifErrorCode::EncoderPluginError,
219                            sub_code: HeifErrorSubCode::UnsupportedParameter,
220                            message: format!("{} is unknown type of parameter", c_param_type),
221                        });
222                    }
223                };
224                let c_param_name = lh::heif_encoder_parameter_get_name(raw_param);
225                let name = cstr_to_str(c_param_name).unwrap_or("").to_string();
226                res.insert(name, param_type);
227                param_pointers = param_pointers.offset(1);
228            }
229        }
230    }
231    Ok(res)
232}
233
234#[derive(Debug)]
235pub struct EncodingOptions {
236    inner: ptr::NonNull<lh::heif_encoding_options>,
237}
238
239impl EncodingOptions {
240    pub fn new() -> Result<Self> {
241        let inner_ptr = unsafe { lh::heif_encoding_options_alloc() };
242        match ptr::NonNull::new(inner_ptr) {
243            Some(inner) => Ok(Self { inner }),
244            None => Err(HeifError {
245                code: HeifErrorCode::MemoryAllocationError,
246                sub_code: HeifErrorSubCode::Unspecified,
247                message: Default::default(),
248            }),
249        }
250    }
251}
252
253impl Default for EncodingOptions {
254    fn default() -> Self {
255        Self::new().expect("heif_encoding_options_alloc() returns a null pointer")
256    }
257}
258
259impl Drop for EncodingOptions {
260    fn drop(&mut self) {
261        unsafe {
262            lh::heif_encoding_options_free(self.inner.as_ptr());
263        }
264    }
265}
266
267impl EncodingOptions {
268    #[inline(always)]
269    fn inner_ref(&self) -> &lh::heif_encoding_options {
270        unsafe { self.inner.as_ref() }
271    }
272
273    #[inline(always)]
274    fn inner_mut(&mut self) -> &mut lh::heif_encoding_options {
275        unsafe { self.inner.as_mut() }
276    }
277
278    #[inline]
279    pub fn version(&self) -> u8 {
280        self.inner_ref().version
281    }
282
283    #[inline]
284    pub fn save_alpha_channel(&self) -> bool {
285        self.inner_ref().save_alpha_channel != 0
286    }
287
288    #[inline]
289    pub fn set_save_alpha_channel(&mut self, enable: bool) {
290        self.inner_mut().save_alpha_channel = if enable { 1 } else { 0 };
291    }
292
293    #[inline]
294    pub fn mac_os_compatibility_workaround(&self) -> bool {
295        self.inner_ref().macOS_compatibility_workaround != 0
296    }
297
298    #[inline]
299    pub fn set_mac_os_compatibility_workaround(&mut self, enable: bool) {
300        self.inner_mut().macOS_compatibility_workaround = if enable { 1 } else { 0 };
301    }
302
303    #[inline]
304    pub fn save_two_colr_boxes_when_icc_and_nclx_available(&self) -> bool {
305        self.inner_ref()
306            .save_two_colr_boxes_when_ICC_and_nclx_available
307            != 0
308    }
309
310    #[inline]
311    pub fn set_save_two_colr_boxes_when_icc_and_nclx_available(&mut self, enable: bool) {
312        self.inner_mut()
313            .save_two_colr_boxes_when_ICC_and_nclx_available = if enable { 1 } else { 0 };
314    }
315
316    #[inline]
317    pub fn mac_os_compatibility_workaround_no_nclx_profile(&self) -> bool {
318        self.inner_ref()
319            .macOS_compatibility_workaround_no_nclx_profile
320            != 0
321    }
322
323    #[inline]
324    pub fn set_mac_os_compatibility_workaround_no_nclx_profile(&mut self, enable: bool) {
325        self.inner_mut()
326            .macOS_compatibility_workaround_no_nclx_profile = if enable { 1 } else { 0 };
327    }
328
329    #[inline]
330    pub fn image_orientation(&self) -> ImageOrientation {
331        let orientation = self.inner_ref().image_orientation;
332        ImageOrientation::n(orientation).unwrap_or(ImageOrientation::Normal)
333    }
334
335    #[inline]
336    pub fn set_image_orientation(&mut self, orientation: ImageOrientation) {
337        self.inner_mut().image_orientation = orientation as _;
338    }
339
340    pub fn color_conversion_options(&self) -> ColorConversionOptions {
341        let lh_options = self.inner_ref().color_conversion_options;
342        ColorConversionOptions {
343            preferred_chroma_downsampling_algorithm: ChromaDownsamplingAlgorithm::n(
344                lh_options.preferred_chroma_downsampling_algorithm,
345            )
346            .unwrap_or(ChromaDownsamplingAlgorithm::Average),
347            preferred_chroma_upsampling_algorithm: ChromaUpsamplingAlgorithm::n(
348                lh_options.preferred_chroma_upsampling_algorithm,
349            )
350            .unwrap_or(ChromaUpsamplingAlgorithm::Bilinear),
351            only_use_preferred_chroma_algorithm: lh_options.only_use_preferred_chroma_algorithm
352                != 0,
353        }
354    }
355
356    pub fn set_color_conversion_options(&mut self, options: ColorConversionOptions) {
357        let lh_options = &mut self.inner_mut().color_conversion_options;
358        lh_options.preferred_chroma_downsampling_algorithm =
359            options.preferred_chroma_downsampling_algorithm as _;
360        lh_options.preferred_chroma_upsampling_algorithm =
361            options.preferred_chroma_upsampling_algorithm as _;
362        lh_options.only_use_preferred_chroma_algorithm =
363            options.only_use_preferred_chroma_algorithm as _;
364    }
365}
366
367/// This function makes sure the encoding options
368/// won't be freed too early.
369pub(crate) fn get_encoding_options_ptr(
370    options: &Option<EncodingOptions>,
371) -> *mut lh::heif_encoding_options {
372    options
373        .as_ref()
374        .map(|o| o.inner.as_ptr())
375        .unwrap_or_else(ptr::null_mut)
376}
377
378#[derive(Copy, Clone)]
379pub struct EncoderDescriptor<'a> {
380    pub(crate) inner: &'a lh::heif_encoder_descriptor,
381}
382
383impl<'a> EncoderDescriptor<'a> {
384    pub(crate) fn new(inner: &'a lh::heif_encoder_descriptor) -> Self {
385        Self { inner }
386    }
387
388    /// A short, symbolic name for identifying the encoder.
389    /// This name should stay constant over different encoder versions.
390    pub fn id(&self) -> &str {
391        let name = unsafe { lh::heif_encoder_descriptor_get_id_name(self.inner) };
392        cstr_to_str(name).unwrap_or_default()
393    }
394
395    /// A long, descriptive name of the encoder
396    /// (including version information).
397    pub fn name(&self) -> String {
398        // Name of encoder in `libheif` is mutable static array of chars.
399        // So we must use mutex to get access this array.
400        let _lock = ENCODER_MUTEX.lock();
401        let name = unsafe { lh::heif_encoder_descriptor_get_name(self.inner) };
402        cstr_to_str(name).unwrap_or_default().to_owned()
403    }
404
405    pub fn compression_format(&self) -> CompressionFormat {
406        let c_format = unsafe { lh::heif_encoder_descriptor_get_compression_format(self.inner) };
407        match CompressionFormat::n(c_format) {
408            Some(res) => res,
409            None => CompressionFormat::Undefined,
410        }
411    }
412
413    pub fn supports_lossy_compression(&self) -> bool {
414        unsafe { lh::heif_encoder_descriptor_supports_lossy_compression(self.inner) != 0 }
415    }
416
417    pub fn supports_lossless_compression(&self) -> bool {
418        unsafe { lh::heif_encoder_descriptor_supports_lossless_compression(self.inner) != 0 }
419    }
420}
421
422impl<'a> Debug for EncoderDescriptor<'a> {
423    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
424        f.debug_struct("EncoderDescriptor")
425            .field("id", &self.id())
426            .field("name", &self.name())
427            .finish()
428    }
429}