Skip to main content

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