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