gdk_pixbuf/
pixbuf.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{future::Future, io::Read, mem, path::Path, pin::Pin, ptr, slice};
4
5use glib::{prelude::*, translate::*, Error};
6use libc::{c_uchar, c_void};
7
8use crate::{ffi, Colorspace, Pixbuf, PixbufFormat};
9
10impl Pixbuf {
11    #[doc(alias = "gdk_pixbuf_new_from_data")]
12    pub fn from_mut_slice<T: AsMut<[u8]>>(
13        data: T,
14        colorspace: Colorspace,
15        has_alpha: bool,
16        bits_per_sample: i32,
17        width: i32,
18        height: i32,
19        row_stride: i32,
20    ) -> Pixbuf {
21        unsafe extern "C" fn destroy<T: AsMut<[u8]>>(_: *mut c_uchar, data: *mut c_void) {
22            let _data: Box<T> = Box::from_raw(data as *mut T); // the data will be destroyed now
23        }
24        assert!(width > 0, "width must be greater than 0");
25        assert!(height > 0, "height must be greater than 0");
26        assert!(row_stride > 0, "row_stride must be greater than 0");
27        assert_eq!(
28            bits_per_sample, 8,
29            "bits_per_sample == 8 is the only supported value"
30        );
31
32        let width = width as usize;
33        let height = height as usize;
34        let row_stride = row_stride as usize;
35        let bits_per_sample = bits_per_sample as usize;
36
37        let n_channels = if has_alpha { 4 } else { 3 };
38        let last_row_len = width * ((n_channels * bits_per_sample + 7) / 8);
39
40        let mut data: Box<T> = Box::new(data);
41
42        let ptr = {
43            let data: &mut [u8] = (*data).as_mut();
44            assert!(
45                data.len() >= ((height - 1) * row_stride + last_row_len),
46                "data.len() must fit the width, height, and row_stride"
47            );
48            data.as_mut_ptr()
49        };
50
51        unsafe {
52            from_glib_full(ffi::gdk_pixbuf_new_from_data(
53                ptr,
54                colorspace.into_glib(),
55                has_alpha.into_glib(),
56                bits_per_sample as i32,
57                width as i32,
58                height as i32,
59                row_stride as i32,
60                Some(destroy::<T>),
61                Box::into_raw(data) as *mut _,
62            ))
63        }
64    }
65
66    // rustdoc-stripper-ignore-next
67    /// Creates a `Pixbuf` from a type implementing `Read` (like `File`).
68    ///
69    /// ```no_run
70    /// use std::fs::File;
71    /// use gdk_pixbuf::Pixbuf;
72    ///
73    /// let f = File::open("some_file.png").expect("failed to open image");
74    /// let pixbuf = Pixbuf::from_read(f).expect("failed to load image");
75    /// ```
76    pub fn from_read<R: Read + Send + 'static>(r: R) -> Result<Pixbuf, Error> {
77        Pixbuf::from_stream(&gio::ReadInputStream::new(r), None::<&gio::Cancellable>)
78    }
79
80    #[doc(alias = "gdk_pixbuf_new_from_stream_async")]
81    pub fn from_stream_async<
82        P: IsA<gio::InputStream>,
83        Q: IsA<gio::Cancellable>,
84        R: FnOnce(Result<Pixbuf, Error>) + 'static,
85    >(
86        stream: &P,
87        cancellable: Option<&Q>,
88        callback: R,
89    ) {
90        let main_context = glib::MainContext::ref_thread_default();
91        let is_main_context_owner = main_context.is_owner();
92        let has_acquired_main_context = (!is_main_context_owner)
93            .then(|| main_context.acquire().ok())
94            .flatten();
95        assert!(
96            is_main_context_owner || has_acquired_main_context.is_some(),
97            "Async operations only allowed if the thread is owning the MainContext"
98        );
99
100        let cancellable = cancellable.map(|p| p.as_ref());
101        let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
102            Box::new(glib::thread_guard::ThreadGuard::new(callback));
103        unsafe extern "C" fn from_stream_async_trampoline<
104            R: FnOnce(Result<Pixbuf, Error>) + 'static,
105        >(
106            _source_object: *mut glib::gobject_ffi::GObject,
107            res: *mut gio::ffi::GAsyncResult,
108            user_data: glib::ffi::gpointer,
109        ) {
110            let mut error = ptr::null_mut();
111            let ptr = ffi::gdk_pixbuf_new_from_stream_finish(res, &mut error);
112            let result = if error.is_null() {
113                Ok(from_glib_full(ptr))
114            } else {
115                Err(from_glib_full(error))
116            };
117            let callback: Box<glib::thread_guard::ThreadGuard<R>> =
118                Box::from_raw(user_data as *mut _);
119            let callback = callback.into_inner();
120            callback(result);
121        }
122        let callback = from_stream_async_trampoline::<R>;
123        unsafe {
124            ffi::gdk_pixbuf_new_from_stream_async(
125                stream.as_ref().to_glib_none().0,
126                cancellable.to_glib_none().0,
127                Some(callback),
128                Box::into_raw(user_data) as *mut _,
129            );
130        }
131    }
132
133    pub fn from_stream_future<P: IsA<gio::InputStream> + Clone + 'static>(
134        stream: &P,
135    ) -> Pin<Box<dyn Future<Output = Result<Pixbuf, Error>> + 'static>> {
136        let stream = stream.clone();
137        Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
138            Self::from_stream_async(&stream, Some(cancellable), move |res| {
139                send.resolve(res);
140            });
141        }))
142    }
143
144    #[doc(alias = "gdk_pixbuf_new_from_stream_at_scale_async")]
145    pub fn from_stream_at_scale_async<
146        P: IsA<gio::InputStream>,
147        Q: IsA<gio::Cancellable>,
148        R: FnOnce(Result<Pixbuf, Error>) + 'static,
149    >(
150        stream: &P,
151        width: i32,
152        height: i32,
153        preserve_aspect_ratio: bool,
154        cancellable: Option<&Q>,
155        callback: R,
156    ) {
157        let main_context = glib::MainContext::ref_thread_default();
158        let is_main_context_owner = main_context.is_owner();
159        let has_acquired_main_context = (!is_main_context_owner)
160            .then(|| main_context.acquire().ok())
161            .flatten();
162        assert!(
163            is_main_context_owner || has_acquired_main_context.is_some(),
164            "Async operations only allowed if the thread is owning the MainContext"
165        );
166
167        let cancellable = cancellable.map(|p| p.as_ref());
168        let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
169            Box::new(glib::thread_guard::ThreadGuard::new(callback));
170        unsafe extern "C" fn from_stream_at_scale_async_trampoline<
171            R: FnOnce(Result<Pixbuf, Error>) + 'static,
172        >(
173            _source_object: *mut glib::gobject_ffi::GObject,
174            res: *mut gio::ffi::GAsyncResult,
175            user_data: glib::ffi::gpointer,
176        ) {
177            let mut error = ptr::null_mut();
178            let ptr = ffi::gdk_pixbuf_new_from_stream_finish(res, &mut error);
179            let result = if error.is_null() {
180                Ok(from_glib_full(ptr))
181            } else {
182                Err(from_glib_full(error))
183            };
184            let callback: Box<glib::thread_guard::ThreadGuard<R>> =
185                Box::from_raw(user_data as *mut _);
186            let callback = callback.into_inner();
187            callback(result);
188        }
189        let callback = from_stream_at_scale_async_trampoline::<R>;
190        unsafe {
191            ffi::gdk_pixbuf_new_from_stream_at_scale_async(
192                stream.as_ref().to_glib_none().0,
193                width,
194                height,
195                preserve_aspect_ratio.into_glib(),
196                cancellable.to_glib_none().0,
197                Some(callback),
198                Box::into_raw(user_data) as *mut _,
199            );
200        }
201    }
202
203    pub fn from_stream_at_scale_future<P: IsA<gio::InputStream> + Clone + 'static>(
204        stream: &P,
205        width: i32,
206        height: i32,
207        preserve_aspect_ratio: bool,
208    ) -> Pin<Box<dyn Future<Output = Result<Pixbuf, Error>> + 'static>> {
209        let stream = stream.clone();
210        Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
211            Self::from_stream_at_scale_async(
212                &stream,
213                width,
214                height,
215                preserve_aspect_ratio,
216                Some(cancellable),
217                move |res| {
218                    send.resolve(res);
219                },
220            );
221        }))
222    }
223
224    // rustdoc-stripper-ignore-next
225    /// Returns a mutable slice to the pixbuf's pixel data.
226    ///
227    /// This function will cause an implicit copy if the pixbuf was created from read-only data.
228    ///
229    /// Please see the section on [image data](#image-data) for information about how the pixel
230    /// data is stored in memory.
231    ///
232    /// # Safety
233    /// No other reference to this pixbuf's data must exist when this method is called.
234    ///
235    /// Until you drop the returned reference, you must not call any methods on the pixbuf which may read
236    /// or write to the data.
237    #[allow(clippy::mut_from_ref)]
238    #[allow(clippy::missing_safety_doc)]
239    #[doc(alias = "gdk_pixbuf_get_pixels_with_length")]
240    #[doc(alias = "get_pixels")]
241    pub unsafe fn pixels(&self) -> &mut [u8] {
242        let mut len = 0;
243        let ptr = ffi::gdk_pixbuf_get_pixels_with_length(self.to_glib_none().0, &mut len);
244        if len == 0 {
245            return &mut [];
246        }
247        slice::from_raw_parts_mut(ptr, len as usize)
248    }
249
250    pub fn put_pixel(&self, x: u32, y: u32, red: u8, green: u8, blue: u8, alpha: u8) {
251        assert!(
252            x < self.width() as u32,
253            "x must be less than the pixbuf's width"
254        );
255        assert!(
256            y < self.height() as u32,
257            "y must be less than the pixbuf's height"
258        );
259
260        unsafe {
261            let x = x as usize;
262            let y = y as usize;
263            let n_channels = self.n_channels() as usize;
264            assert!(n_channels == 3 || n_channels == 4);
265            let rowstride = self.rowstride() as usize;
266            let pixels = self.pixels();
267            let pos = y * rowstride + x * n_channels;
268
269            pixels[pos] = red;
270            pixels[pos + 1] = green;
271            pixels[pos + 2] = blue;
272            if n_channels == 4 {
273                pixels[pos + 3] = alpha;
274            }
275        }
276    }
277
278    #[doc(alias = "gdk_pixbuf_get_file_info")]
279    #[doc(alias = "get_file_info")]
280    pub fn file_info<T: AsRef<Path>>(filename: T) -> Option<(PixbufFormat, i32, i32)> {
281        unsafe {
282            let mut width = mem::MaybeUninit::uninit();
283            let mut height = mem::MaybeUninit::uninit();
284            let ret = ffi::gdk_pixbuf_get_file_info(
285                filename.as_ref().to_glib_none().0,
286                width.as_mut_ptr(),
287                height.as_mut_ptr(),
288            );
289            if !ret.is_null() {
290                Some((
291                    from_glib_none(ret),
292                    width.assume_init(),
293                    height.assume_init(),
294                ))
295            } else {
296                None
297            }
298        }
299    }
300
301    #[doc(alias = "gdk_pixbuf_get_file_info_async")]
302    #[doc(alias = "get_file_info_async")]
303    pub fn file_info_async<
304        P: IsA<gio::Cancellable>,
305        Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
306        T: AsRef<Path>,
307    >(
308        filename: T,
309        cancellable: Option<&P>,
310        callback: Q,
311    ) {
312        let main_context = glib::MainContext::ref_thread_default();
313        let is_main_context_owner = main_context.is_owner();
314        let has_acquired_main_context = (!is_main_context_owner)
315            .then(|| main_context.acquire().ok())
316            .flatten();
317        assert!(
318            is_main_context_owner || has_acquired_main_context.is_some(),
319            "Async operations only allowed if the thread is owning the MainContext"
320        );
321
322        let cancellable = cancellable.map(|p| p.as_ref());
323        let user_data: Box<glib::thread_guard::ThreadGuard<Q>> =
324            Box::new(glib::thread_guard::ThreadGuard::new(callback));
325        unsafe extern "C" fn get_file_info_async_trampoline<
326            Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
327        >(
328            _source_object: *mut glib::gobject_ffi::GObject,
329            res: *mut gio::ffi::GAsyncResult,
330            user_data: glib::ffi::gpointer,
331        ) {
332            let mut error = ptr::null_mut();
333            let mut width = mem::MaybeUninit::uninit();
334            let mut height = mem::MaybeUninit::uninit();
335            let ret = ffi::gdk_pixbuf_get_file_info_finish(
336                res,
337                width.as_mut_ptr(),
338                height.as_mut_ptr(),
339                &mut error,
340            );
341            let result = if !error.is_null() {
342                Err(from_glib_full(error))
343            } else if ret.is_null() {
344                Ok(None)
345            } else {
346                Ok(Some((
347                    from_glib_none(ret),
348                    width.assume_init(),
349                    height.assume_init(),
350                )))
351            };
352            let callback: Box<glib::thread_guard::ThreadGuard<Q>> =
353                Box::from_raw(user_data as *mut _);
354            let callback = callback.into_inner();
355            callback(result);
356        }
357        let callback = get_file_info_async_trampoline::<Q>;
358        unsafe {
359            ffi::gdk_pixbuf_get_file_info_async(
360                filename.as_ref().to_glib_none().0,
361                cancellable.to_glib_none().0,
362                Some(callback),
363                Box::into_raw(user_data) as *mut _,
364            );
365        }
366    }
367
368    #[allow(clippy::type_complexity)]
369    #[doc(alias = "get_file_info_async")]
370    pub fn file_info_future<T: AsRef<Path> + Clone + 'static>(
371        filename: T,
372    ) -> Pin<Box<dyn Future<Output = Result<Option<(PixbufFormat, i32, i32)>, Error>> + 'static>>
373    {
374        Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
375            Self::file_info_async(filename, Some(cancellable), move |res| {
376                send.resolve(res);
377            });
378        }))
379    }
380
381    #[doc(alias = "gdk_pixbuf_save_to_bufferv")]
382    pub fn save_to_bufferv(&self, type_: &str, options: &[(&str, &str)]) -> Result<Vec<u8>, Error> {
383        unsafe {
384            let mut buffer = ptr::null_mut();
385            let mut buffer_size = mem::MaybeUninit::uninit();
386            let mut error = ptr::null_mut();
387            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
388            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
389            let _ = ffi::gdk_pixbuf_save_to_bufferv(
390                self.to_glib_none().0,
391                &mut buffer,
392                buffer_size.as_mut_ptr(),
393                type_.to_glib_none().0,
394                option_keys.to_glib_none().0,
395                option_values.to_glib_none().0,
396                &mut error,
397            );
398            if error.is_null() {
399                Ok(FromGlibContainer::from_glib_full_num(
400                    buffer,
401                    buffer_size.assume_init() as _,
402                ))
403            } else {
404                Err(from_glib_full(error))
405            }
406        }
407    }
408
409    #[doc(alias = "gdk_pixbuf_save_to_streamv")]
410    pub fn save_to_streamv<P: IsA<gio::OutputStream>, Q: IsA<gio::Cancellable>>(
411        &self,
412        stream: &P,
413        type_: &str,
414        options: &[(&str, &str)],
415        cancellable: Option<&Q>,
416    ) -> Result<(), Error> {
417        let cancellable = cancellable.map(|p| p.as_ref());
418        unsafe {
419            let mut error = ptr::null_mut();
420            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
421            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
422            let _ = ffi::gdk_pixbuf_save_to_streamv(
423                self.to_glib_none().0,
424                stream.as_ref().to_glib_none().0,
425                type_.to_glib_none().0,
426                option_keys.to_glib_none().0,
427                option_values.to_glib_none().0,
428                cancellable.to_glib_none().0,
429                &mut error,
430            );
431            if error.is_null() {
432                Ok(())
433            } else {
434                Err(from_glib_full(error))
435            }
436        }
437    }
438
439    #[doc(alias = "gdk_pixbuf_save_to_streamv_async")]
440    pub fn save_to_streamv_async<
441        P: IsA<gio::OutputStream>,
442        Q: IsA<gio::Cancellable>,
443        R: FnOnce(Result<(), Error>) + 'static,
444    >(
445        &self,
446        stream: &P,
447        type_: &str,
448        options: &[(&str, &str)],
449        cancellable: Option<&Q>,
450        callback: R,
451    ) {
452        let main_context = glib::MainContext::ref_thread_default();
453        let is_main_context_owner = main_context.is_owner();
454        let has_acquired_main_context = (!is_main_context_owner)
455            .then(|| main_context.acquire().ok())
456            .flatten();
457        assert!(
458            is_main_context_owner || has_acquired_main_context.is_some(),
459            "Async operations only allowed if the thread is owning the MainContext"
460        );
461
462        let cancellable = cancellable.map(|p| p.as_ref());
463        let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
464            Box::new(glib::thread_guard::ThreadGuard::new(callback));
465        unsafe extern "C" fn save_to_streamv_async_trampoline<
466            R: FnOnce(Result<(), Error>) + 'static,
467        >(
468            _source_object: *mut glib::gobject_ffi::GObject,
469            res: *mut gio::ffi::GAsyncResult,
470            user_data: glib::ffi::gpointer,
471        ) {
472            let mut error = ptr::null_mut();
473            let _ = ffi::gdk_pixbuf_save_to_stream_finish(res, &mut error);
474            let result = if error.is_null() {
475                Ok(())
476            } else {
477                Err(from_glib_full(error))
478            };
479            let callback: Box<glib::thread_guard::ThreadGuard<R>> =
480                Box::from_raw(user_data as *mut _);
481            let callback = callback.into_inner();
482            callback(result);
483        }
484        let callback = save_to_streamv_async_trampoline::<R>;
485        unsafe {
486            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
487            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
488            ffi::gdk_pixbuf_save_to_streamv_async(
489                self.to_glib_none().0,
490                stream.as_ref().to_glib_none().0,
491                type_.to_glib_none().0,
492                option_keys.to_glib_none().0,
493                option_values.to_glib_none().0,
494                cancellable.to_glib_none().0,
495                Some(callback),
496                Box::into_raw(user_data) as *mut _,
497            );
498        }
499    }
500
501    pub fn save_to_streamv_future<P: IsA<gio::OutputStream> + Clone + 'static>(
502        &self,
503        stream: &P,
504        type_: &str,
505        options: &[(&str, &str)],
506    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>> {
507        let stream = stream.clone();
508        let type_ = String::from(type_);
509        let options = options
510            .iter()
511            .map(|&(k, v)| (String::from(k), String::from(v)))
512            .collect::<Vec<(String, String)>>();
513        Box::pin(gio::GioFuture::new(self, move |obj, cancellable, send| {
514            let options = options
515                .iter()
516                .map(|(k, v)| (k.as_str(), v.as_str()))
517                .collect::<Vec<(&str, &str)>>();
518
519            obj.save_to_streamv_async(
520                &stream,
521                &type_,
522                options.as_slice(),
523                Some(cancellable),
524                move |res| {
525                    send.resolve(res);
526                },
527            );
528        }))
529    }
530
531    #[doc(alias = "gdk_pixbuf_savev")]
532    pub fn savev<T: AsRef<Path>>(
533        &self,
534        filename: T,
535        type_: &str,
536        options: &[(&str, &str)],
537    ) -> Result<(), Error> {
538        unsafe {
539            let mut error = ptr::null_mut();
540            let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
541            let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
542            let _ = ffi::gdk_pixbuf_savev(
543                self.to_glib_none().0,
544                filename.as_ref().to_glib_none().0,
545                type_.to_glib_none().0,
546                option_keys.to_glib_none().0,
547                option_values.to_glib_none().0,
548                &mut error,
549            );
550            if error.is_null() {
551                Ok(())
552            } else {
553                Err(from_glib_full(error))
554            }
555        }
556    }
557}