Skip to main content

kreuzberg_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        // SAFETY: libheif C API; self.inner is non-null and valid.
90        unsafe { lh::heif_encoder_release(self.inner) };
91    }
92}
93
94#[allow(unsafe_code)]
95impl<'a> Encoder<'a> {
96    /// Name of encoder.
97    #[allow(unsafe_code)]
98    pub fn name(&self) -> String {
99        // Name of encoder in `libheif` is mutable static array of chars.
100        // So we must use mutex to get access this array.
101        let _lock = ENCODER_MUTEX.lock();
102        // SAFETY: libheif C API; self.inner is non-null; protected by mutex.
103        let res = unsafe { lh::heif_encoder_get_name(self.inner) };
104        cstr_to_str(res).unwrap_or("").to_owned()
105    }
106
107    #[allow(unsafe_code)]
108    pub fn set_quality(&mut self, quality: EncoderQuality) -> Result<()> {
109        // SAFETY: libheif C API; self.inner is non-null.
110        let err = match quality {
111            EncoderQuality::LossLess => unsafe { lh::heif_encoder_set_lossless(self.inner, 1) },
112            EncoderQuality::Lossy(value) => unsafe {
113                let middle_err = lh::heif_encoder_set_lossless(self.inner, 0);
114                HeifError::from_heif_error(middle_err)?;
115                lh::heif_encoder_set_lossy_quality(self.inner, i32::from(value))
116            },
117        };
118        HeifError::from_heif_error(err)
119    }
120
121    #[allow(unsafe_code)]
122    fn parameter_value(&self, name: &str, parameter_type: EncoderParameterType) -> Result<EncoderParameterValue> {
123        let c_param_name = CString::new(name).unwrap();
124        let param_value = match parameter_type {
125            EncoderParameterType::Int => {
126                let mut value = 0;
127                // SAFETY: libheif C API; self.inner is non-null, c_param_name is valid, value is valid mutable ptr.
128                let err = unsafe {
129                    lh::heif_encoder_get_parameter_integer(self.inner, c_param_name.as_ptr(), &mut value as _)
130                };
131                HeifError::from_heif_error(err)?;
132                EncoderParameterValue::Int(value)
133            }
134            EncoderParameterType::Bool => {
135                let mut value = 0;
136                // SAFETY: libheif C API; self.inner is non-null, c_param_name is valid, value is valid mutable ptr.
137                let err = unsafe {
138                    lh::heif_encoder_get_parameter_boolean(self.inner, c_param_name.as_ptr(), &mut value as _)
139                };
140                HeifError::from_heif_error(err)?;
141                EncoderParameterValue::Bool(value > 0)
142            }
143            EncoderParameterType::String => {
144                let value: Vec<u8> = vec![0; 51];
145                // SAFETY: libheif C API; self.inner is non-null, c_param_name is valid, value is valid buffer.
146                let err = unsafe {
147                    lh::heif_encoder_get_parameter_string(self.inner, c_param_name.as_ptr(), value.as_ptr() as _, 50)
148                };
149                HeifError::from_heif_error(err)?;
150                EncoderParameterValue::String(cstr_to_str(value.as_ptr() as _).unwrap_or("").to_string())
151            }
152        };
153
154        Ok(param_value)
155    }
156
157    pub fn parameters_names(&self) -> Vec<String> {
158        self.parameters_types.keys().cloned().collect()
159    }
160
161    /// Get value of encoder's parameter.
162    pub fn parameter(&self, name: &str) -> Result<Option<EncoderParameterValue>> {
163        match self.parameters_types.get(name) {
164            Some(param_type) => {
165                let value = self.parameter_value(name, *param_type)?;
166                Ok(Some(value))
167            }
168            None => Ok(None),
169        }
170    }
171
172    /// Set value of encoder's parameter.
173    #[allow(unsafe_code)]
174    pub fn set_parameter_value(&self, name: &str, value: EncoderParameterValue) -> Result<()> {
175        let c_param_name = CString::new(name).unwrap();
176        // SAFETY: libheif C API; self.inner is non-null, c_param_name is valid.
177        let err = match value {
178            EncoderParameterValue::Bool(v) => unsafe {
179                lh::heif_encoder_set_parameter_boolean(self.inner, c_param_name.as_ptr(), v.into())
180            },
181            EncoderParameterValue::Int(v) => unsafe {
182                lh::heif_encoder_set_parameter_integer(self.inner, c_param_name.as_ptr(), v)
183            },
184            EncoderParameterValue::String(v) => unsafe {
185                let c_param_value = CString::new(v).unwrap();
186                lh::heif_encoder_set_parameter_string(self.inner, c_param_name.as_ptr(), c_param_value.as_ptr())
187            },
188        };
189        HeifError::from_heif_error(err)?;
190        Ok(())
191    }
192}
193
194#[allow(unsafe_code)]
195fn parameters_types(c_encoder: &mut lh::heif_encoder) -> Result<EncoderParametersTypes> {
196    let mut res = EncoderParametersTypes::new();
197    // SAFETY: libheif C API; c_encoder is non-null; param_pointers can be null or valid array.
198    unsafe {
199        let mut param_pointers = lh::heif_encoder_list_parameters(c_encoder);
200        if !param_pointers.is_null() {
201            // SAFETY: param_pointers is non-null; we check for null before dereferencing in loop.
202            while let Some(raw_param) = (*param_pointers).as_ref() {
203                let c_param_type = lh::heif_encoder_parameter_get_type(raw_param);
204                let param_type = match EncoderParameterType::n(c_param_type) {
205                    Some(res) => res,
206                    None => {
207                        return Err(HeifError {
208                            code: HeifErrorCode::EncoderPluginError,
209                            sub_code: HeifErrorSubCode::UnsupportedParameter,
210                            message: format!("{} is unknown type of parameter", c_param_type),
211                        });
212                    }
213                };
214                let c_param_name = lh::heif_encoder_parameter_get_name(raw_param);
215                let name = cstr_to_str(c_param_name).unwrap_or("").to_string();
216                res.insert(name, param_type);
217                param_pointers = param_pointers.offset(1);
218            }
219        }
220    }
221    Ok(res)
222}
223
224#[derive(Debug)]
225pub struct EncodingOptions {
226    inner: ptr::NonNull<lh::heif_encoding_options>,
227}
228
229#[allow(unsafe_code)]
230impl EncodingOptions {
231    pub fn new() -> Result<Self> {
232        // SAFETY: libheif C API; returns a heap-allocated encoding options or null.
233        let inner_ptr = unsafe { lh::heif_encoding_options_alloc() };
234        match ptr::NonNull::new(inner_ptr) {
235            Some(inner) => Ok(Self { inner }),
236            None => Err(HeifError {
237                code: HeifErrorCode::MemoryAllocationError,
238                sub_code: HeifErrorSubCode::Unspecified,
239                message: Default::default(),
240            }),
241        }
242    }
243}
244
245impl Default for EncodingOptions {
246    fn default() -> Self {
247        Self::new().expect("heif_encoding_options_alloc() returns a null pointer")
248    }
249}
250
251#[allow(unsafe_code)]
252impl Drop for EncodingOptions {
253    fn drop(&mut self) {
254        // SAFETY: self.inner is non-null and owned by this EncodingOptions; freeing it completes our ownership.
255        unsafe {
256            lh::heif_encoding_options_free(self.inner.as_ptr());
257        }
258    }
259}
260
261#[allow(unsafe_code)]
262impl EncodingOptions {
263    #[inline(always)]
264    #[allow(unsafe_code)]
265    fn inner_ref(&self) -> &lh::heif_encoding_options {
266        // SAFETY: self.inner is a valid NonNull; we have shared access.
267        unsafe { self.inner.as_ref() }
268    }
269
270    #[inline(always)]
271    #[allow(unsafe_code)]
272    fn inner_mut(&mut self) -> &mut lh::heif_encoding_options {
273        // SAFETY: self.inner is a valid NonNull; we have exclusive access.
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().save_two_colr_boxes_when_ICC_and_nclx_available != 0
305    }
306
307    #[inline]
308    pub fn set_save_two_colr_boxes_when_icc_and_nclx_available(&mut self, enable: bool) {
309        self.inner_mut().save_two_colr_boxes_when_ICC_and_nclx_available = if enable { 1 } else { 0 };
310    }
311
312    #[inline]
313    pub fn mac_os_compatibility_workaround_no_nclx_profile(&self) -> bool {
314        self.inner_ref().macOS_compatibility_workaround_no_nclx_profile != 0
315    }
316
317    #[inline]
318    pub fn set_mac_os_compatibility_workaround_no_nclx_profile(&mut self, enable: bool) {
319        self.inner_mut().macOS_compatibility_workaround_no_nclx_profile = if enable { 1 } else { 0 };
320    }
321
322    #[inline]
323    pub fn image_orientation(&self) -> ImageOrientation {
324        let orientation = self.inner_ref().image_orientation;
325        ImageOrientation::n(orientation).unwrap_or(ImageOrientation::Normal)
326    }
327
328    #[inline]
329    pub fn set_image_orientation(&mut self, orientation: ImageOrientation) {
330        self.inner_mut().image_orientation = orientation as _;
331    }
332
333    pub fn color_conversion_options(&self) -> ColorConversionOptions {
334        let lh_options = self.inner_ref().color_conversion_options;
335        ColorConversionOptions::from_cc_options(&lh_options)
336    }
337
338    pub fn set_color_conversion_options(&mut self, options: ColorConversionOptions) {
339        let lh_options = &mut self.inner_mut().color_conversion_options;
340        options.fill_cc_options(lh_options);
341    }
342}
343
344/// This function makes sure the encoding options
345/// won't be freed too early.
346pub(crate) fn get_encoding_options_ptr(options: &Option<EncodingOptions>) -> *mut lh::heif_encoding_options {
347    options.as_ref().map(|o| o.inner.as_ptr()).unwrap_or_else(ptr::null_mut)
348}
349
350#[derive(Copy, Clone)]
351pub struct EncoderDescriptor<'a> {
352    pub(crate) inner: &'a lh::heif_encoder_descriptor,
353}
354
355#[allow(unsafe_code)]
356impl<'a> EncoderDescriptor<'a> {
357    pub(crate) fn new(inner: &'a lh::heif_encoder_descriptor) -> Self {
358        Self { inner }
359    }
360
361    /// A short, symbolic name for identifying the encoder.
362    /// This name should stay constant over different encoder versions.
363    #[allow(unsafe_code)]
364    pub fn id(&self) -> &str {
365        // SAFETY: libheif C API; self.inner is non-null; returns a static c string or null.
366        let name = unsafe { lh::heif_encoder_descriptor_get_id_name(self.inner) };
367        cstr_to_str(name).unwrap_or_default()
368    }
369
370    /// A long, descriptive name of the encoder
371    /// (including version information).
372    #[allow(unsafe_code)]
373    pub fn name(&self) -> String {
374        // Name of encoder in `libheif` is mutable static array of chars.
375        // So we must use mutex to get access this array.
376        let _lock = ENCODER_MUTEX.lock();
377        // SAFETY: libheif C API; self.inner is non-null; protected by mutex.
378        let name = unsafe { lh::heif_encoder_descriptor_get_name(self.inner) };
379        cstr_to_str(name).unwrap_or_default().to_owned()
380    }
381
382    #[allow(unsafe_code)]
383    pub fn compression_format(&self) -> CompressionFormat {
384        // SAFETY: libheif C API; self.inner is non-null.
385        let c_format = unsafe { lh::heif_encoder_descriptor_get_compression_format(self.inner) };
386        match CompressionFormat::n(c_format) {
387            Some(res) => res,
388            None => CompressionFormat::Undefined,
389        }
390    }
391
392    #[allow(unsafe_code)]
393    pub fn supports_lossy_compression(&self) -> bool {
394        // SAFETY: libheif C API; self.inner is non-null.
395        unsafe { lh::heif_encoder_descriptor_supports_lossy_compression(self.inner) != 0 }
396    }
397
398    #[allow(unsafe_code)]
399    pub fn supports_lossless_compression(&self) -> bool {
400        // SAFETY: libheif C API; self.inner is non-null.
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}