1use 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); }
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 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 #[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")]
293 #[doc(alias = "get_file_info")]
294 pub fn file_info<T: AsRef<Path>>(filename: T) -> Option<(PixbufFormat, i32, i32)> {
295 unsafe {
296 let mut width = mem::MaybeUninit::uninit();
297 let mut height = mem::MaybeUninit::uninit();
298 let ret = ffi::gdk_pixbuf_get_file_info(
299 filename.as_ref().to_glib_none().0,
300 width.as_mut_ptr(),
301 height.as_mut_ptr(),
302 );
303 if !ret.is_null() {
304 Some((
305 from_glib_none(ret),
306 width.assume_init(),
307 height.assume_init(),
308 ))
309 } else {
310 None
311 }
312 }
313 }
314
315 #[doc(alias = "gdk_pixbuf_get_file_info_async")]
331 #[doc(alias = "get_file_info_async")]
332 pub fn file_info_async<
333 P: IsA<gio::Cancellable>,
334 Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
335 T: AsRef<Path>,
336 >(
337 filename: T,
338 cancellable: Option<&P>,
339 callback: Q,
340 ) {
341 let main_context = glib::MainContext::ref_thread_default();
342 let is_main_context_owner = main_context.is_owner();
343 let has_acquired_main_context = (!is_main_context_owner)
344 .then(|| main_context.acquire().ok())
345 .flatten();
346 assert!(
347 is_main_context_owner || has_acquired_main_context.is_some(),
348 "Async operations only allowed if the thread is owning the MainContext"
349 );
350
351 let cancellable = cancellable.map(|p| p.as_ref());
352 let user_data: Box<glib::thread_guard::ThreadGuard<Q>> =
353 Box::new(glib::thread_guard::ThreadGuard::new(callback));
354 unsafe extern "C" fn get_file_info_async_trampoline<
355 Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
356 >(
357 _source_object: *mut glib::gobject_ffi::GObject,
358 res: *mut gio::ffi::GAsyncResult,
359 user_data: glib::ffi::gpointer,
360 ) {
361 let mut error = ptr::null_mut();
362 let mut width = mem::MaybeUninit::uninit();
363 let mut height = mem::MaybeUninit::uninit();
364 let ret = ffi::gdk_pixbuf_get_file_info_finish(
365 res,
366 width.as_mut_ptr(),
367 height.as_mut_ptr(),
368 &mut error,
369 );
370 let result = if !error.is_null() {
371 Err(from_glib_full(error))
372 } else if ret.is_null() {
373 Ok(None)
374 } else {
375 Ok(Some((
376 from_glib_none(ret),
377 width.assume_init(),
378 height.assume_init(),
379 )))
380 };
381 let callback: Box<glib::thread_guard::ThreadGuard<Q>> =
382 Box::from_raw(user_data as *mut _);
383 let callback = callback.into_inner();
384 callback(result);
385 }
386 let callback = get_file_info_async_trampoline::<Q>;
387 unsafe {
388 ffi::gdk_pixbuf_get_file_info_async(
389 filename.as_ref().to_glib_none().0,
390 cancellable.to_glib_none().0,
391 Some(callback),
392 Box::into_raw(user_data) as *mut _,
393 );
394 }
395 }
396
397 #[allow(clippy::type_complexity)]
398 #[doc(alias = "get_file_info_async")]
399 pub fn file_info_future<T: AsRef<Path> + Clone + 'static>(
400 filename: T,
401 ) -> Pin<Box<dyn Future<Output = Result<Option<(PixbufFormat, i32, i32)>, Error>> + 'static>>
402 {
403 Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
404 Self::file_info_async(filename, Some(cancellable), move |res| {
405 send.resolve(res);
406 });
407 }))
408 }
409
410 #[doc(alias = "gdk_pixbuf_save_to_bufferv")]
431 pub fn save_to_bufferv(&self, type_: &str, options: &[(&str, &str)]) -> Result<Vec<u8>, Error> {
432 unsafe {
433 let mut buffer = ptr::null_mut();
434 let mut buffer_size = mem::MaybeUninit::uninit();
435 let mut error = ptr::null_mut();
436 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
437 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
438 let _ = ffi::gdk_pixbuf_save_to_bufferv(
439 self.to_glib_none().0,
440 &mut buffer,
441 buffer_size.as_mut_ptr(),
442 type_.to_glib_none().0,
443 option_keys.to_glib_none().0,
444 option_values.to_glib_none().0,
445 &mut error,
446 );
447 if error.is_null() {
448 Ok(FromGlibContainer::from_glib_full_num(
449 buffer,
450 buffer_size.assume_init() as _,
451 ))
452 } else {
453 Err(from_glib_full(error))
454 }
455 }
456 }
457
458 #[doc(alias = "gdk_pixbuf_save_to_streamv")]
480 pub fn save_to_streamv<P: IsA<gio::OutputStream>, Q: IsA<gio::Cancellable>>(
481 &self,
482 stream: &P,
483 type_: &str,
484 options: &[(&str, &str)],
485 cancellable: Option<&Q>,
486 ) -> Result<(), Error> {
487 let cancellable = cancellable.map(|p| p.as_ref());
488 unsafe {
489 let mut error = ptr::null_mut();
490 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
491 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
492 let _ = ffi::gdk_pixbuf_save_to_streamv(
493 self.to_glib_none().0,
494 stream.as_ref().to_glib_none().0,
495 type_.to_glib_none().0,
496 option_keys.to_glib_none().0,
497 option_values.to_glib_none().0,
498 cancellable.to_glib_none().0,
499 &mut error,
500 );
501 if error.is_null() {
502 Ok(())
503 } else {
504 Err(from_glib_full(error))
505 }
506 }
507 }
508
509 #[doc(alias = "gdk_pixbuf_save_to_streamv_async")]
531 pub fn save_to_streamv_async<
532 P: IsA<gio::OutputStream>,
533 Q: IsA<gio::Cancellable>,
534 R: FnOnce(Result<(), Error>) + 'static,
535 >(
536 &self,
537 stream: &P,
538 type_: &str,
539 options: &[(&str, &str)],
540 cancellable: Option<&Q>,
541 callback: R,
542 ) {
543 let main_context = glib::MainContext::ref_thread_default();
544 let is_main_context_owner = main_context.is_owner();
545 let has_acquired_main_context = (!is_main_context_owner)
546 .then(|| main_context.acquire().ok())
547 .flatten();
548 assert!(
549 is_main_context_owner || has_acquired_main_context.is_some(),
550 "Async operations only allowed if the thread is owning the MainContext"
551 );
552
553 let cancellable = cancellable.map(|p| p.as_ref());
554 let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
555 Box::new(glib::thread_guard::ThreadGuard::new(callback));
556 unsafe extern "C" fn save_to_streamv_async_trampoline<
557 R: FnOnce(Result<(), Error>) + 'static,
558 >(
559 _source_object: *mut glib::gobject_ffi::GObject,
560 res: *mut gio::ffi::GAsyncResult,
561 user_data: glib::ffi::gpointer,
562 ) {
563 let mut error = ptr::null_mut();
564 let _ = ffi::gdk_pixbuf_save_to_stream_finish(res, &mut error);
565 let result = if error.is_null() {
566 Ok(())
567 } else {
568 Err(from_glib_full(error))
569 };
570 let callback: Box<glib::thread_guard::ThreadGuard<R>> =
571 Box::from_raw(user_data as *mut _);
572 let callback = callback.into_inner();
573 callback(result);
574 }
575 let callback = save_to_streamv_async_trampoline::<R>;
576 unsafe {
577 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
578 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
579 ffi::gdk_pixbuf_save_to_streamv_async(
580 self.to_glib_none().0,
581 stream.as_ref().to_glib_none().0,
582 type_.to_glib_none().0,
583 option_keys.to_glib_none().0,
584 option_values.to_glib_none().0,
585 cancellable.to_glib_none().0,
586 Some(callback),
587 Box::into_raw(user_data) as *mut _,
588 );
589 }
590 }
591
592 pub fn save_to_streamv_future<P: IsA<gio::OutputStream> + Clone + 'static>(
593 &self,
594 stream: &P,
595 type_: &str,
596 options: &[(&str, &str)],
597 ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>> {
598 let stream = stream.clone();
599 let type_ = String::from(type_);
600 let options = options
601 .iter()
602 .map(|&(k, v)| (String::from(k), String::from(v)))
603 .collect::<Vec<(String, String)>>();
604 Box::pin(gio::GioFuture::new(self, move |obj, cancellable, send| {
605 let options = options
606 .iter()
607 .map(|(k, v)| (k.as_str(), v.as_str()))
608 .collect::<Vec<(&str, &str)>>();
609
610 obj.save_to_streamv_async(
611 &stream,
612 &type_,
613 options.as_slice(),
614 Some(cancellable),
615 move |res| {
616 send.resolve(res);
617 },
618 );
619 }))
620 }
621
622 #[doc(alias = "gdk_pixbuf_savev")]
642 pub fn savev<T: AsRef<Path>>(
643 &self,
644 filename: T,
645 type_: &str,
646 options: &[(&str, &str)],
647 ) -> Result<(), Error> {
648 unsafe {
649 let mut error = ptr::null_mut();
650 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
651 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
652 let _ = ffi::gdk_pixbuf_savev(
653 self.to_glib_none().0,
654 filename.as_ref().to_glib_none().0,
655 type_.to_glib_none().0,
656 option_keys.to_glib_none().0,
657 option_values.to_glib_none().0,
658 &mut error,
659 );
660 if error.is_null() {
661 Ok(())
662 } else {
663 Err(from_glib_full(error))
664 }
665 }
666 }
667}