Skip to main content

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