1use std::{future::Future, io::Read, mem, path::Path, pin::Pin, ptr, slice};
4
5use glib::{Error, prelude::*, translate::*};
6use libc::{c_uchar, c_void};
7
8use crate::{Colorspace, Pixbuf, PixbufFormat, ffi};
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 unsafe {
23 let _data: Box<T> = Box::from_raw(data as *mut T); }
25 }
26 assert!(width > 0, "width must be greater than 0");
27 assert!(height > 0, "height must be greater than 0");
28 assert!(row_stride > 0, "row_stride must be greater than 0");
29 assert_eq!(
30 bits_per_sample, 8,
31 "bits_per_sample == 8 is the only supported value"
32 );
33
34 let width = width as usize;
35 let height = height as usize;
36 let row_stride = row_stride as usize;
37 let bits_per_sample = bits_per_sample as usize;
38
39 let n_channels = if has_alpha { 4 } else { 3 };
40 let last_row_len = width * (n_channels * bits_per_sample).div_ceil(8);
41
42 let mut data: Box<T> = Box::new(data);
43
44 let ptr = {
45 let data: &mut [u8] = (*data).as_mut();
46 assert!(
47 data.len() >= ((height - 1) * row_stride + last_row_len),
48 "data.len() must fit the width, height, and row_stride"
49 );
50 data.as_mut_ptr()
51 };
52
53 unsafe {
54 from_glib_full(ffi::gdk_pixbuf_new_from_data(
55 ptr,
56 colorspace.into_glib(),
57 has_alpha.into_glib(),
58 bits_per_sample as i32,
59 width as i32,
60 height as i32,
61 row_stride as i32,
62 Some(destroy::<T>),
63 Box::into_raw(data) as *mut _,
64 ))
65 }
66 }
67
68 pub fn from_read<R: Read + Send + 'static>(r: R) -> Result<Pixbuf, Error> {
79 Pixbuf::from_stream(&gio::ReadInputStream::new(r), None::<&gio::Cancellable>)
80 }
81
82 #[doc(alias = "gdk_pixbuf_new_from_stream_async")]
83 pub fn from_stream_async<
84 P: IsA<gio::InputStream>,
85 Q: IsA<gio::Cancellable>,
86 R: FnOnce(Result<Pixbuf, Error>) + 'static,
87 >(
88 stream: &P,
89 cancellable: Option<&Q>,
90 callback: R,
91 ) {
92 let main_context = glib::MainContext::ref_thread_default();
93 let is_main_context_owner = main_context.is_owner();
94 let has_acquired_main_context = (!is_main_context_owner)
95 .then(|| main_context.acquire().ok())
96 .flatten();
97 assert!(
98 is_main_context_owner || has_acquired_main_context.is_some(),
99 "Async operations only allowed if the thread is owning the MainContext"
100 );
101
102 let cancellable = cancellable.map(|p| p.as_ref());
103 let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
104 Box::new(glib::thread_guard::ThreadGuard::new(callback));
105 unsafe extern "C" fn from_stream_async_trampoline<
106 R: FnOnce(Result<Pixbuf, Error>) + 'static,
107 >(
108 _source_object: *mut glib::gobject_ffi::GObject,
109 res: *mut gio::ffi::GAsyncResult,
110 user_data: glib::ffi::gpointer,
111 ) {
112 unsafe {
113 let mut error = ptr::null_mut();
114 let ptr = ffi::gdk_pixbuf_new_from_stream_finish(res, &mut error);
115 let result = if error.is_null() {
116 Ok(from_glib_full(ptr))
117 } else {
118 Err(from_glib_full(error))
119 };
120 let callback: Box<glib::thread_guard::ThreadGuard<R>> =
121 Box::from_raw(user_data as *mut _);
122 let callback = callback.into_inner();
123 callback(result);
124 }
125 }
126 let callback = from_stream_async_trampoline::<R>;
127 unsafe {
128 ffi::gdk_pixbuf_new_from_stream_async(
129 stream.as_ref().to_glib_none().0,
130 cancellable.to_glib_none().0,
131 Some(callback),
132 Box::into_raw(user_data) as *mut _,
133 );
134 }
135 }
136
137 pub fn from_stream_future<P: IsA<gio::InputStream> + Clone + 'static>(
138 stream: &P,
139 ) -> Pin<Box<dyn Future<Output = Result<Pixbuf, Error>> + 'static>> {
140 let stream = stream.clone();
141 Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
142 Self::from_stream_async(&stream, Some(cancellable), move |res| {
143 send.resolve(res);
144 });
145 }))
146 }
147
148 #[doc(alias = "gdk_pixbuf_new_from_stream_at_scale_async")]
149 pub fn from_stream_at_scale_async<
150 P: IsA<gio::InputStream>,
151 Q: IsA<gio::Cancellable>,
152 R: FnOnce(Result<Pixbuf, Error>) + 'static,
153 >(
154 stream: &P,
155 width: i32,
156 height: i32,
157 preserve_aspect_ratio: bool,
158 cancellable: Option<&Q>,
159 callback: R,
160 ) {
161 let main_context = glib::MainContext::ref_thread_default();
162 let is_main_context_owner = main_context.is_owner();
163 let has_acquired_main_context = (!is_main_context_owner)
164 .then(|| main_context.acquire().ok())
165 .flatten();
166 assert!(
167 is_main_context_owner || has_acquired_main_context.is_some(),
168 "Async operations only allowed if the thread is owning the MainContext"
169 );
170
171 let cancellable = cancellable.map(|p| p.as_ref());
172 let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
173 Box::new(glib::thread_guard::ThreadGuard::new(callback));
174 unsafe extern "C" fn from_stream_at_scale_async_trampoline<
175 R: FnOnce(Result<Pixbuf, Error>) + 'static,
176 >(
177 _source_object: *mut glib::gobject_ffi::GObject,
178 res: *mut gio::ffi::GAsyncResult,
179 user_data: glib::ffi::gpointer,
180 ) {
181 unsafe {
182 let mut error = ptr::null_mut();
183 let ptr = ffi::gdk_pixbuf_new_from_stream_finish(res, &mut error);
184 let result = if error.is_null() {
185 Ok(from_glib_full(ptr))
186 } else {
187 Err(from_glib_full(error))
188 };
189 let callback: Box<glib::thread_guard::ThreadGuard<R>> =
190 Box::from_raw(user_data as *mut _);
191 let callback = callback.into_inner();
192 callback(result);
193 }
194 }
195 let callback = from_stream_at_scale_async_trampoline::<R>;
196 unsafe {
197 ffi::gdk_pixbuf_new_from_stream_at_scale_async(
198 stream.as_ref().to_glib_none().0,
199 width,
200 height,
201 preserve_aspect_ratio.into_glib(),
202 cancellable.to_glib_none().0,
203 Some(callback),
204 Box::into_raw(user_data) as *mut _,
205 );
206 }
207 }
208
209 pub fn from_stream_at_scale_future<P: IsA<gio::InputStream> + Clone + 'static>(
210 stream: &P,
211 width: i32,
212 height: i32,
213 preserve_aspect_ratio: bool,
214 ) -> Pin<Box<dyn Future<Output = Result<Pixbuf, Error>> + 'static>> {
215 let stream = stream.clone();
216 Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
217 Self::from_stream_at_scale_async(
218 &stream,
219 width,
220 height,
221 preserve_aspect_ratio,
222 Some(cancellable),
223 move |res| {
224 send.resolve(res);
225 },
226 );
227 }))
228 }
229
230 #[allow(clippy::mut_from_ref)]
244 #[allow(clippy::missing_safety_doc)]
245 #[doc(alias = "gdk_pixbuf_get_pixels_with_length")]
246 #[doc(alias = "get_pixels")]
247 pub unsafe fn pixels(&self) -> &mut [u8] {
248 unsafe {
249 let mut len = 0;
250 let ptr = ffi::gdk_pixbuf_get_pixels_with_length(self.to_glib_none().0, &mut len);
251 if len == 0 {
252 return &mut [];
253 }
254 slice::from_raw_parts_mut(ptr, len as usize)
255 }
256 }
257
258 pub fn put_pixel(&self, x: u32, y: u32, red: u8, green: u8, blue: u8, alpha: u8) {
259 assert!(
260 x < self.width() as u32,
261 "x must be less than the pixbuf's width"
262 );
263 assert!(
264 y < self.height() as u32,
265 "y must be less than the pixbuf's height"
266 );
267
268 unsafe {
269 let x = x as usize;
270 let y = y as usize;
271 let n_channels = self.n_channels() as usize;
272 assert!(n_channels == 3 || n_channels == 4);
273 let rowstride = self.rowstride() as usize;
274 let pixels = self.pixels();
275 let pos = y * rowstride + x * n_channels;
276
277 pixels[pos] = red;
278 pixels[pos + 1] = green;
279 pixels[pos + 2] = blue;
280 if n_channels == 4 {
281 pixels[pos + 3] = alpha;
282 }
283 }
284 }
285
286 #[doc(alias = "gdk_pixbuf_get_file_info")]
301 #[doc(alias = "get_file_info")]
302 pub fn file_info<T: AsRef<Path>>(filename: T) -> Option<(PixbufFormat, i32, i32)> {
303 unsafe {
304 let mut width = mem::MaybeUninit::uninit();
305 let mut height = mem::MaybeUninit::uninit();
306 let ret = ffi::gdk_pixbuf_get_file_info(
307 filename.as_ref().to_glib_none().0,
308 width.as_mut_ptr(),
309 height.as_mut_ptr(),
310 );
311 if !ret.is_null() {
312 Some((
313 from_glib_none(ret),
314 width.assume_init(),
315 height.assume_init(),
316 ))
317 } else {
318 None
319 }
320 }
321 }
322
323 #[doc(alias = "gdk_pixbuf_get_file_info_async")]
339 #[doc(alias = "get_file_info_async")]
340 pub fn file_info_async<
341 P: IsA<gio::Cancellable>,
342 Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
343 T: AsRef<Path>,
344 >(
345 filename: T,
346 cancellable: Option<&P>,
347 callback: Q,
348 ) {
349 let main_context = glib::MainContext::ref_thread_default();
350 let is_main_context_owner = main_context.is_owner();
351 let has_acquired_main_context = (!is_main_context_owner)
352 .then(|| main_context.acquire().ok())
353 .flatten();
354 assert!(
355 is_main_context_owner || has_acquired_main_context.is_some(),
356 "Async operations only allowed if the thread is owning the MainContext"
357 );
358
359 let cancellable = cancellable.map(|p| p.as_ref());
360 let user_data: Box<glib::thread_guard::ThreadGuard<Q>> =
361 Box::new(glib::thread_guard::ThreadGuard::new(callback));
362 unsafe extern "C" fn get_file_info_async_trampoline<
363 Q: FnOnce(Result<Option<(PixbufFormat, i32, i32)>, Error>) + 'static,
364 >(
365 _source_object: *mut glib::gobject_ffi::GObject,
366 res: *mut gio::ffi::GAsyncResult,
367 user_data: glib::ffi::gpointer,
368 ) {
369 unsafe {
370 let mut error = ptr::null_mut();
371 let mut width = mem::MaybeUninit::uninit();
372 let mut height = mem::MaybeUninit::uninit();
373 let ret = ffi::gdk_pixbuf_get_file_info_finish(
374 res,
375 width.as_mut_ptr(),
376 height.as_mut_ptr(),
377 &mut error,
378 );
379 let result = if !error.is_null() {
380 Err(from_glib_full(error))
381 } else if ret.is_null() {
382 Ok(None)
383 } else {
384 Ok(Some((
385 from_glib_none(ret),
386 width.assume_init(),
387 height.assume_init(),
388 )))
389 };
390 let callback: Box<glib::thread_guard::ThreadGuard<Q>> =
391 Box::from_raw(user_data as *mut _);
392 let callback = callback.into_inner();
393 callback(result);
394 }
395 }
396 let callback = get_file_info_async_trampoline::<Q>;
397 unsafe {
398 ffi::gdk_pixbuf_get_file_info_async(
399 filename.as_ref().to_glib_none().0,
400 cancellable.to_glib_none().0,
401 Some(callback),
402 Box::into_raw(user_data) as *mut _,
403 );
404 }
405 }
406
407 #[allow(clippy::type_complexity)]
408 #[doc(alias = "get_file_info_async")]
409 pub fn file_info_future<T: AsRef<Path> + Clone + 'static>(
410 filename: T,
411 ) -> Pin<Box<dyn Future<Output = Result<Option<(PixbufFormat, i32, i32)>, Error>> + 'static>>
412 {
413 Box::pin(gio::GioFuture::new(&(), move |_obj, cancellable, send| {
414 Self::file_info_async(filename, Some(cancellable), move |res| {
415 send.resolve(res);
416 });
417 }))
418 }
419
420 #[doc(alias = "gdk_pixbuf_save_to_bufferv")]
441 pub fn save_to_bufferv(&self, type_: &str, options: &[(&str, &str)]) -> Result<Vec<u8>, Error> {
442 unsafe {
443 let mut buffer = ptr::null_mut();
444 let mut buffer_size = mem::MaybeUninit::uninit();
445 let mut error = ptr::null_mut();
446 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
447 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
448 let _ = ffi::gdk_pixbuf_save_to_bufferv(
449 self.to_glib_none().0,
450 &mut buffer,
451 buffer_size.as_mut_ptr(),
452 type_.to_glib_none().0,
453 option_keys.to_glib_none().0,
454 option_values.to_glib_none().0,
455 &mut error,
456 );
457 if error.is_null() {
458 Ok(FromGlibContainer::from_glib_full_num(
459 buffer,
460 buffer_size.assume_init() as _,
461 ))
462 } else {
463 Err(from_glib_full(error))
464 }
465 }
466 }
467
468 #[doc(alias = "gdk_pixbuf_save_to_streamv")]
490 pub fn save_to_streamv<P: IsA<gio::OutputStream>, Q: IsA<gio::Cancellable>>(
491 &self,
492 stream: &P,
493 type_: &str,
494 options: &[(&str, &str)],
495 cancellable: Option<&Q>,
496 ) -> Result<(), Error> {
497 let cancellable = cancellable.map(|p| p.as_ref());
498 unsafe {
499 let mut error = ptr::null_mut();
500 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
501 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
502 let _ = ffi::gdk_pixbuf_save_to_streamv(
503 self.to_glib_none().0,
504 stream.as_ref().to_glib_none().0,
505 type_.to_glib_none().0,
506 option_keys.to_glib_none().0,
507 option_values.to_glib_none().0,
508 cancellable.to_glib_none().0,
509 &mut error,
510 );
511 if error.is_null() {
512 Ok(())
513 } else {
514 Err(from_glib_full(error))
515 }
516 }
517 }
518
519 #[doc(alias = "gdk_pixbuf_save_to_streamv_async")]
541 pub fn save_to_streamv_async<
542 P: IsA<gio::OutputStream>,
543 Q: IsA<gio::Cancellable>,
544 R: FnOnce(Result<(), Error>) + 'static,
545 >(
546 &self,
547 stream: &P,
548 type_: &str,
549 options: &[(&str, &str)],
550 cancellable: Option<&Q>,
551 callback: R,
552 ) {
553 let main_context = glib::MainContext::ref_thread_default();
554 let is_main_context_owner = main_context.is_owner();
555 let has_acquired_main_context = (!is_main_context_owner)
556 .then(|| main_context.acquire().ok())
557 .flatten();
558 assert!(
559 is_main_context_owner || has_acquired_main_context.is_some(),
560 "Async operations only allowed if the thread is owning the MainContext"
561 );
562
563 let cancellable = cancellable.map(|p| p.as_ref());
564 let user_data: Box<glib::thread_guard::ThreadGuard<R>> =
565 Box::new(glib::thread_guard::ThreadGuard::new(callback));
566 unsafe extern "C" fn save_to_streamv_async_trampoline<
567 R: FnOnce(Result<(), Error>) + 'static,
568 >(
569 _source_object: *mut glib::gobject_ffi::GObject,
570 res: *mut gio::ffi::GAsyncResult,
571 user_data: glib::ffi::gpointer,
572 ) {
573 unsafe {
574 let mut error = ptr::null_mut();
575 let _ = ffi::gdk_pixbuf_save_to_stream_finish(res, &mut error);
576 let result = if error.is_null() {
577 Ok(())
578 } else {
579 Err(from_glib_full(error))
580 };
581 let callback: Box<glib::thread_guard::ThreadGuard<R>> =
582 Box::from_raw(user_data as *mut _);
583 let callback = callback.into_inner();
584 callback(result);
585 }
586 }
587 let callback = save_to_streamv_async_trampoline::<R>;
588 unsafe {
589 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
590 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
591 ffi::gdk_pixbuf_save_to_streamv_async(
592 self.to_glib_none().0,
593 stream.as_ref().to_glib_none().0,
594 type_.to_glib_none().0,
595 option_keys.to_glib_none().0,
596 option_values.to_glib_none().0,
597 cancellable.to_glib_none().0,
598 Some(callback),
599 Box::into_raw(user_data) as *mut _,
600 );
601 }
602 }
603
604 pub fn save_to_streamv_future<P: IsA<gio::OutputStream> + Clone + 'static>(
605 &self,
606 stream: &P,
607 type_: &str,
608 options: &[(&str, &str)],
609 ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + 'static>> {
610 let stream = stream.clone();
611 let type_ = String::from(type_);
612 let options = options
613 .iter()
614 .map(|&(k, v)| (String::from(k), String::from(v)))
615 .collect::<Vec<(String, String)>>();
616 Box::pin(gio::GioFuture::new(self, move |obj, cancellable, send| {
617 let options = options
618 .iter()
619 .map(|(k, v)| (k.as_str(), v.as_str()))
620 .collect::<Vec<(&str, &str)>>();
621
622 obj.save_to_streamv_async(
623 &stream,
624 &type_,
625 options.as_slice(),
626 Some(cancellable),
627 move |res| {
628 send.resolve(res);
629 },
630 );
631 }))
632 }
633
634 #[doc(alias = "gdk_pixbuf_savev")]
654 pub fn savev<T: AsRef<Path>>(
655 &self,
656 filename: T,
657 type_: &str,
658 options: &[(&str, &str)],
659 ) -> Result<(), Error> {
660 unsafe {
661 let mut error = ptr::null_mut();
662 let option_keys: Vec<&str> = options.iter().map(|o| o.0).collect();
663 let option_values: Vec<&str> = options.iter().map(|o| o.1).collect();
664 let _ = ffi::gdk_pixbuf_savev(
665 self.to_glib_none().0,
666 filename.as_ref().to_glib_none().0,
667 type_.to_glib_none().0,
668 option_keys.to_glib_none().0,
669 option_values.to_glib_none().0,
670 &mut error,
671 );
672 if error.is_null() {
673 Ok(())
674 } else {
675 Err(from_glib_full(error))
676 }
677 }
678 }
679}