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 Undefined = lh::heif_compression_format_heif_compression_undefined as _,
21 Hevc = lh::heif_compression_format_heif_compression_HEVC as _,
23 Avc = lh::heif_compression_format_heif_compression_AVC as _,
25 Jpeg = lh::heif_compression_format_heif_compression_JPEG as _,
27 Av1 = lh::heif_compression_format_heif_compression_AV1 as _,
29 Vvc = lh::heif_compression_format_heif_compression_VVC as _,
31 Evc = lh::heif_compression_format_heif_compression_EVC as _,
33 Jpeg2000 = lh::heif_compression_format_heif_compression_JPEG2000 as _,
35 Uncompressed = lh::heif_compression_format_heif_compression_uncompressed as _,
37 Mask = lh::heif_compression_format_heif_compression_mask as _,
39 #[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 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) };
91 }
92}
93
94#[allow(unsafe_code)]
95impl<'a> Encoder<'a> {
96 #[allow(unsafe_code)]
98 pub fn name(&self) -> String {
99 let _lock = ENCODER_MUTEX.lock();
102 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 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 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 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 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 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 #[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 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 unsafe {
199 let mut param_pointers = lh::heif_encoder_list_parameters(c_encoder);
200 if !param_pointers.is_null() {
201 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 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 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 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 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
344pub(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 #[allow(unsafe_code)]
364 pub fn id(&self) -> &str {
365 let name = unsafe { lh::heif_encoder_descriptor_get_id_name(self.inner) };
367 cstr_to_str(name).unwrap_or_default()
368 }
369
370 #[allow(unsafe_code)]
373 pub fn name(&self) -> String {
374 let _lock = ENCODER_MUTEX.lock();
377 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 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 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 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}