Gtk-rs: Creating a button with an image downloaded from a URL

I’d like to create a button that shows an image downloaded from a URL but I’m at a loss.
A Playground link isn’t possible because it lacks the required crates.

This is what I tried:

            let button = Button::new();
            let result = reqwest::get("https://picsum.photos/200").await.unwrap();
            let bytes = result.bytes().await.unwrap().to_vec();
            let pixbuf = Pixbuf::from_inline(&bytes, true).unwrap();
            let image = Image::from_pixbuf(Some(&pixbuf));
            button.set_image(Some(&image));

But that fails with thread 'main' panicked at 'called Result::unwrap()on anErr value: Error { domain: gdk-pixbuf-error-quark, code: 0, message: "Image header corrupt" }', src/main.rs:52:60

I also tried a variant like this

            let result = reqwest::get("https://picsum.photos/200").await.unwrap();
            let bytes = result.bytes().await.unwrap().to_vec();
            let bytes = glib::Bytes::from(&bytes.to_vec());
            let pixbuf = Pixbuf::from_bytes(&bytes, Colorspace::Rgb, false, 8, 200, 200, 3 * 200);
            let image = Image::from_pixbuf(Some(&pixbuf));

And that fails with
GdkPixbuf-CRITICAL **: 09:26:11.165: gdk_pixbuf_new_from_bytes: assertion 'g_bytes_get_size (data) >= width * height * (has_alpha ? 4 : 3)' failed thread 'main' panicked at 'assertion failed: ::glib::types::instance_of::<Self>(ptr as *const _)',

Unfortunately, there’s almost zero documentation for any of these crates.
Does anyone have an idea on how to achieve my goal?
I’m not tied to reqwest

I did try downloading to a file first and that works:

        let result = reqwest::get("https://picsum.photos/200").await.unwrap();
        let mut file = File::create("pic.jpg").unwrap();
        std::io::copy(&mut result.bytes().await.unwrap().as_ref(), &mut file);

This leads to a file I can open in a Browser and in Pinta which is an image editor also based on Gtk and Pixbuf.
And this also works now:

        let pixbuf = Pixbuf::from_file("pic.jpg").unwrap();
        let image = Image::from_pixbuf(Some(&pixbuf));

So the problem must be somewhere else. I must be using Gtk the wrong way somehow. I’d also love to use streams but I haven’t found a way to convert a Stream to an InputStream

Thank you

gdk_pixbuf::Pixbuf::from_inline() wants a very specific format, not just some random PNG, JPEG or similar. See the docs

Create a GdkPixbuf from a flat representation that is suitable for storing as inline data in a program. This is useful if you want to ship a program with images, but don’t want to depend on any external files.

gdk-pixbuf ships with a program called gdk-pixbuf-csource, which allows for conversion of GdkPixbufs into such a inline representation.

Also gdk_pixbuf::Pixbuf::from_bytes() and from_data() only work with raw 8 bit RGB

Creates a new GdkPixbuf out of in-memory readonly image data. Currently only RGB images with 8 bits per sample are supported. This is the GBytes variant of gdk_pixbuf_new_from_data().

You probably want gdk_pixbuf::Pixbuf::from_stream() around a gio::MemoryInputStream that wraps around your data.

@sdroege Thank you for the quick and thorough reply. I’m not sure why I didn’t find that documentation on my own yesterday.

You pointing me at MemoryInputStream was helpful and lead me to come up with this:

            let button = Button::new();
            let result = reqwest::get("https://picsum.photos/200").await.unwrap();
            let bytes = result.bytes().await.unwrap().to_vec();
            let bytes = glib::Bytes::from(&bytes.to_vec());
            let stream = MemoryInputStream::from_bytes(&bytes);
            let pixbuf = Pixbuf::from_stream(&stream, NONE_CANCELLABLE).unwrap();
            let image = Image::from_pixbuf(Some(&pixbuf));
            button.set_image(Some(&image));

which works!
I’m sure there’s a better way but I’m happy for now.

Again, thank you for your help!

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.