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) };
90 }
91}
92
93#[allow(unsafe_code)]
94impl<'a> Encoder<'a> {
95 #[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 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 #[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
329pub(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 #[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 #[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}