Skip to main content

xberg_libheif/
image.rs

1use std::mem::MaybeUninit;
2use std::os::raw::c_int;
3use std::{ptr, slice};
4
5use libheif_sys as lh;
6
7use crate::{
8    Channel, ColorProfileNCLX, ColorProfileRaw, ColorProfileType, ColorSpace, HeifError, HeifErrorCode,
9    HeifErrorSubCode, Result,
10};
11
12const MAX_IMAGE_SIZE: u32 = i32::MAX as _;
13
14pub struct Plane<T> {
15    pub data: T,
16    pub width: u32,
17    pub height: u32,
18    pub stride: usize,
19    pub bits_per_pixel: u8,
20    pub storage_bits_per_pixel: u8,
21}
22
23pub struct Planes<T> {
24    pub y: Option<Plane<T>>,
25    pub cb: Option<Plane<T>>,
26    pub cr: Option<Plane<T>>,
27    pub r: Option<Plane<T>>,
28    pub g: Option<Plane<T>>,
29    pub b: Option<Plane<T>>,
30    pub a: Option<Plane<T>>,
31    pub interleaved: Option<Plane<T>>,
32}
33
34pub struct Image {
35    pub(crate) inner: *mut lh::heif_image,
36}
37
38pub struct ScalingOptions {}
39
40#[allow(unsafe_code)]
41impl Image {
42    /// Create a new image of the specified resolution and colorspace.
43    /// Note: no memory for the actual image data is reserved yet. You have to use
44    /// [`Image::create_plane()`] method to add image planes required by your colorspace.
45    #[allow(unsafe_code)]
46    pub fn new(width: u32, height: u32, color_space: ColorSpace) -> Result<Image> {
47        if width > MAX_IMAGE_SIZE || height > MAX_IMAGE_SIZE {
48            return Err(HeifError {
49                code: HeifErrorCode::UsageError,
50                sub_code: HeifErrorSubCode::InvalidBoxSize,
51                message: "width or height is greater than MAX_IMAGE_SIZE".to_string(),
52            });
53        }
54
55        let mut c_image = MaybeUninit::<_>::uninit();
56        let err = unsafe {
57            lh::heif_image_create(
58                width as _,
59                height as _,
60                color_space.heif_color_space(),
61                color_space.heif_chroma(),
62                c_image.as_mut_ptr(),
63            )
64        };
65        HeifError::from_heif_error(err)?;
66        Ok(Image {
67            inner: unsafe { c_image.assume_init() },
68        })
69    }
70
71    #[inline]
72    pub(crate) fn from_heif_image(image: *mut lh::heif_image) -> Image {
73        Image { inner: image }
74    }
75
76    /// Get the width of the main channel (Y in YCbCr, or any in RGB).
77    #[allow(unsafe_code)]
78    pub fn width(&self) -> u32 {
79        unsafe { lh::heif_image_get_primary_width(self.inner).max(0) as u32 }
80    }
81
82    /// Get the height of the main channel (Y in YCbCr, or any in RGB).
83    #[allow(unsafe_code)]
84    pub fn height(&self) -> u32 {
85        unsafe { lh::heif_image_get_primary_height(self.inner).max(0) as u32 }
86    }
87
88    /// Get width of the given image channel in pixels.
89    #[allow(unsafe_code)]
90    pub fn channel_width(&self, channel: Channel) -> Option<u32> {
91        let value = unsafe { lh::heif_image_get_width(self.inner, channel as _) };
92        (value >= 0).then_some(value as _)
93    }
94
95    /// Get height of the given image channel in pixels.
96    #[allow(unsafe_code)]
97    pub fn channel_height(&self, channel: Channel) -> Option<u32> {
98        let value = unsafe { lh::heif_image_get_height(self.inner, channel as _) };
99        (value >= 0).then_some(value as _)
100    }
101
102    /// Get the number of bits per pixel in the given image channel. Returns
103    /// `None` if a non-existing channel was given.
104    ///
105    /// Note that the number of bits per pixel may be different for each color channel.
106    /// This function returns the number of bits used for storage of each pixel.
107    /// Especially for HDR images, this is probably not what you want. Have a look at
108    /// [`Image::bits_per_pixel()`] instead.
109    #[allow(unsafe_code)]
110    pub fn storage_bits_per_pixel(&self, channel: Channel) -> Option<u8> {
111        let value = unsafe { lh::heif_image_get_bits_per_pixel(self.inner, channel as _) };
112        (value >= 0).then_some(value as _)
113    }
114
115    /// Get the number of bits per pixel in the given image channel. Returns
116    /// `None` if a non-existing channel was given.
117    ///
118    /// This function returns the number of bits used for representing
119    /// the pixel value, which might be smaller than the number of bits used
120    /// in memory. For example, in 12bit HDR images, this function returns `12`,
121    /// while still 16 bits are reserved for storage. For interleaved RGBA with
122    /// 12 bit, this function also returns `12`, not `48` or `64`
123    /// ([`Image::storage_bits_per_pixel()`] returns `64` in this case).
124    #[allow(unsafe_code)]
125    pub fn bits_per_pixel(&self, channel: Channel) -> Option<u8> {
126        let value = unsafe { lh::heif_image_get_bits_per_pixel_range(self.inner, channel as _) };
127        (value >= 0).then_some(value as _)
128    }
129
130    #[cfg(feature = "v1_20")]
131    #[allow(unsafe_code)]
132    fn plane_inner(&self, channel: Channel) -> (*const u8, usize) {
133        let mut stride: usize = 1;
134        let data = unsafe { lh::heif_image_get_plane_readonly2(self.inner, channel as _, &mut stride) };
135        (data, stride)
136    }
137
138    #[cfg(not(feature = "v1_20"))]
139    #[allow(unsafe_code)]
140    fn plane_inner(&self, channel: Channel) -> (*const u8, usize) {
141        let mut stride: i32 = 1;
142        let data = unsafe { lh::heif_image_get_plane_readonly(self.inner, channel as _, &mut stride) };
143        (data, stride as _)
144    }
145
146    #[allow(unsafe_code)]
147    fn plane(&self, channel: Channel) -> Option<Plane<&[u8]>> {
148        let (data, stride) = self.plane_inner(channel);
149        if data.is_null() {
150            return None;
151        }
152
153        let width = self.channel_width(channel).unwrap_or_default();
154        let height = self.channel_height(channel).unwrap_or_default();
155        let bits_per_pixel = self.bits_per_pixel(channel).unwrap_or_default();
156        let storage_bits_per_pixel = self.storage_bits_per_pixel(channel).unwrap_or_default();
157        let size = height as usize * stride;
158        let bytes = unsafe { slice::from_raw_parts(data, size) };
159        Some(Plane {
160            data: bytes,
161            bits_per_pixel,
162            storage_bits_per_pixel,
163            width,
164            height,
165            stride,
166        })
167    }
168
169    #[cfg(feature = "v1_20")]
170    #[allow(unsafe_code)]
171    fn plane_mut_inner(&self, channel: Channel) -> (*mut u8, usize) {
172        let mut stride: usize = 1;
173        let data = unsafe { lh::heif_image_get_plane2(self.inner, channel as _, &mut stride) };
174        (data, stride)
175    }
176
177    #[cfg(not(feature = "v1_20"))]
178    #[allow(unsafe_code)]
179    fn plane_mut_inner(&self, channel: Channel) -> (*mut u8, usize) {
180        let mut stride: i32 = 1;
181        let data = unsafe { lh::heif_image_get_plane(self.inner, channel as _, &mut stride) };
182        (data, stride as _)
183    }
184
185    #[allow(clippy::mut_from_ref)]
186    #[allow(unsafe_code)]
187    fn plane_mut(&self, channel: Channel) -> Option<Plane<&mut [u8]>> {
188        let (data, stride): (*mut u8, usize) = self.plane_mut_inner(channel);
189        if data.is_null() {
190            return None;
191        }
192
193        let width = self.channel_width(channel).unwrap_or_default();
194        let height = self.channel_height(channel).unwrap_or_default();
195        let bits_per_pixel = self.bits_per_pixel(channel).unwrap_or_default();
196        let storage_bits_per_pixel = self.storage_bits_per_pixel(channel).unwrap_or_default();
197        let size = height as usize * stride;
198        let bytes = unsafe { slice::from_raw_parts_mut(data, size) };
199        Some(Plane {
200            data: bytes,
201            bits_per_pixel,
202            storage_bits_per_pixel,
203            width,
204            height,
205            stride,
206        })
207    }
208
209    #[allow(unsafe_code)]
210    pub fn planes(&self) -> Planes<&[u8]> {
211        Planes {
212            y: self.plane(Channel::Y),
213            cb: self.plane(Channel::Cb),
214            cr: self.plane(Channel::Cr),
215            r: self.plane(Channel::R),
216            g: self.plane(Channel::G),
217            b: self.plane(Channel::B),
218            a: self.plane(Channel::Alpha),
219            interleaved: self.plane(Channel::Interleaved),
220        }
221    }
222
223    #[allow(unsafe_code)]
224    pub fn planes_mut(&mut self) -> Planes<&mut [u8]> {
225        Planes {
226            y: self.plane_mut(Channel::Y),
227            cb: self.plane_mut(Channel::Cb),
228            cr: self.plane_mut(Channel::Cr),
229            r: self.plane_mut(Channel::R),
230            g: self.plane_mut(Channel::G),
231            b: self.plane_mut(Channel::B),
232            a: self.plane_mut(Channel::Alpha),
233            interleaved: self.plane_mut(Channel::Interleaved),
234        }
235    }
236
237    #[allow(unsafe_code)]
238    pub fn has_channel(&self, channel: Channel) -> bool {
239        unsafe { lh::heif_image_has_channel(self.inner, channel as _) != 0 }
240    }
241
242    #[allow(unsafe_code)]
243    pub fn color_space(&self) -> Option<ColorSpace> {
244        unsafe {
245            ColorSpace::from_libheif(
246                lh::heif_image_get_colorspace(self.inner),
247                lh::heif_image_get_chroma_format(self.inner),
248            )
249        }
250    }
251
252    /// Scale image by "nearest neighbor" method.
253    ///
254    /// Note: Currently, `_scaling_options` is not used. Pass a `None`.
255    #[allow(unsafe_code)]
256    pub fn scale(&self, width: u32, height: u32, _scaling_options: Option<ScalingOptions>) -> Result<Image> {
257        let mut c_image = MaybeUninit::<_>::uninit();
258        let err = unsafe {
259            lh::heif_image_scale_image(self.inner, c_image.as_mut_ptr(), width as _, height as _, ptr::null())
260        };
261        HeifError::from_heif_error(err)?;
262        Ok(Image {
263            inner: unsafe { c_image.assume_init() },
264        })
265    }
266
267    /// The indicated bit_depth corresponds to the bit depth per channel.
268    /// I.e. for interleaved formats like RRGGBB, the bit_depth would be,
269    /// e.g., 10 bit instead of 30 bits or 3*16=48 bits.
270    /// For backward compatibility, one can also specify 24bits for RGB and
271    /// 32bits for RGBA, instead of the preferred 8 bits.
272    #[allow(unsafe_code)]
273    pub fn create_plane(&mut self, channel: Channel, width: u32, height: u32, bit_depth: u8) -> Result<()> {
274        let err = unsafe {
275            lh::heif_image_add_plane(
276                self.inner,
277                channel as _,
278                width as _,
279                height as _,
280                c_int::from(bit_depth),
281            )
282        };
283        HeifError::from_heif_error(err)
284    }
285
286    #[allow(unsafe_code)]
287    pub fn set_premultiplied_alpha(&self, is_premultiplied_alpha: bool) {
288        unsafe { lh::heif_image_set_premultiplied_alpha(self.inner, is_premultiplied_alpha as _) };
289    }
290
291    #[allow(unsafe_code)]
292    pub fn is_premultiplied_alpha(&self) -> bool {
293        unsafe { lh::heif_image_is_premultiplied_alpha(self.inner) != 0 }
294    }
295
296    #[allow(unsafe_code)]
297    pub fn color_profile_raw(&self) -> Option<ColorProfileRaw> {
298        let size = unsafe { lh::heif_image_get_raw_color_profile_size(self.inner) };
299        if size == 0 {
300            return None;
301        }
302        let mut result: Vec<u8> = Vec::with_capacity(size);
303        let err = unsafe { lh::heif_image_get_raw_color_profile(self.inner, result.as_ptr() as _) };
304        if err.code != 0 {
305            return None;
306        }
307        unsafe {
308            result.set_len(size);
309        }
310        let c_profile_type = unsafe { lh::heif_image_get_color_profile_type(self.inner) };
311        let profile_type = ColorProfileType::from(c_profile_type as u32);
312
313        Some(ColorProfileRaw {
314            typ: profile_type,
315            data: result,
316        })
317    }
318
319    #[allow(unsafe_code)]
320    pub fn set_color_profile_raw(&mut self, profile: &ColorProfileRaw) -> Result<()> {
321        let err = unsafe {
322            let mut c_profile_type = [0u8; 5];
323            c_profile_type[0..4].copy_from_slice(&profile.typ.0);
324            lh::heif_image_set_raw_color_profile(
325                self.inner,
326                c_profile_type.as_ptr() as _,
327                profile.data.as_ptr() as _,
328                profile.data.len(),
329            )
330        };
331        HeifError::from_heif_error(err)
332    }
333
334    #[allow(unsafe_code)]
335    pub fn color_profile_nclx(&self) -> Option<ColorProfileNCLX> {
336        let mut profile_ptr = MaybeUninit::<_>::uninit();
337        let err = unsafe { lh::heif_image_get_nclx_color_profile(self.inner, profile_ptr.as_mut_ptr()) };
338        if err.code != 0 {
339            return None;
340        }
341        let profile_ptr = unsafe { profile_ptr.assume_init() };
342        Some(ColorProfileNCLX { inner: profile_ptr })
343    }
344
345    #[allow(unsafe_code)]
346    pub fn set_color_profile_nclx(&mut self, profile: &ColorProfileNCLX) -> Result<()> {
347        let err = unsafe { lh::heif_image_set_nclx_color_profile(self.inner, profile.inner) };
348        HeifError::from_heif_error(err)
349    }
350
351    #[allow(unsafe_code)]
352    pub fn pixel_aspect_ratio(&self) -> (u32, u32) {
353        let mut aspect_h = 0;
354        let mut aspect_v = 0;
355        unsafe {
356            lh::heif_image_get_pixel_aspect_ratio(self.inner, &mut aspect_h as _, &mut aspect_v as _);
357        }
358        (aspect_h, aspect_v)
359    }
360
361    #[allow(unsafe_code)]
362    pub fn set_pixel_aspect_ratio(&mut self, aspect_h: u32, aspect_v: u32) {
363        unsafe {
364            lh::heif_image_set_pixel_aspect_ratio(self.inner, aspect_h, aspect_v);
365        }
366    }
367
368    #[cfg(feature = "v1_20")]
369    #[allow(unsafe_code)]
370    pub fn duration(&self) -> u32 {
371        unsafe { lh::heif_image_get_duration(self.inner) }
372    }
373
374    #[cfg(feature = "v1_20")]
375    #[allow(unsafe_code)]
376    pub fn set_duration(&self, duration: u32) {
377        unsafe { lh::heif_image_set_duration(self.inner, duration) }
378    }
379}
380
381#[allow(unsafe_code)]
382impl Drop for Image {
383    #[allow(unsafe_code)]
384    fn drop(&mut self) {
385        unsafe { lh::heif_image_release(self.inner) };
386    }
387}
388
389#[allow(unsafe_code)]
390unsafe impl Send for Image {}