libheif_rs/
image.rs

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