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")]
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}