Search code examples
cgtk4

The easiest way of drawing an array of pixels in GTK 4.0 in C


I'm trying to find an example of showing an image in GTK 4.0 from an existing memory buffer, where the image is stored in the form of an array of floats of size width x height x 3. I understand I need to use GtkImage, but I don't know how to pass such an array to it.

Just for context, I wanted to implement a prototype for rendering some images, they are all rendered in memory, and as a result, I have an array of such floats, three floats for each pixel. Unfortunately, I could not find any examples for the latest GTK 4.0. There are plenty of examples of loading an image from the disk, but it seems like jumping unnecessary hoops for my case. I used to load these as OpenGL texture and show them on a quad, but it also seems like driving nails with a sledgehammer.

Thank you in advance for any help!


Solution

  • You can use GdkMemoryTexture for an image in memory and use a GtkPicture and set the texture using gtk_picture_set_paintable() to paint it. The following is an example that creates an opaque 8bit RGB image and displays it on the screen:

    /* pixel.c
     *
     * Compile: cc -ggdb pixel.c -o pixel $(pkg-config --cflags --libs gtk4) -o pixel
     * Run: ./pixel
     *
     * Author: Mohammed Sadiq <www.sadiqpk.org>
     *
     * SPDX-License-Identifier: LGPL-2.1-or-later OR CC0-1.0
     */
    
    #include <gtk/gtk.h>
    
    #define BYTES_PER_R8G8B8 3
    #define WIDTH  400
    
    static void
    fill_row (GByteArray *array,
              guint8      value,
              int         row_size)
    {
      guint i;
    
      for (i = 0; i < row_size; i++) {
        /* Fill the same values for RGB */
        g_byte_array_append (array, &value, 1); /* R */
        g_byte_array_append (array, &value, 1); /* G */
        g_byte_array_append (array, &value, 1); /* B */
      }
    }
    
    static void
    add_pixel_picture (GtkPicture *picture)
    {
      g_autoptr(GBytes) bytes = NULL;
      GdkTexture *texture;
      GByteArray *pixels;
      gsize height;
    
      /* Draw something interesting */
      pixels = g_byte_array_new ();
      for (guint i = 0; i <= 0xff ; i++)
        fill_row (pixels, i, WIDTH);
    
      for (guint i = 0; i <= 0xff; i++)
        fill_row (pixels, 0xff - i, WIDTH);
    
      height = pixels->len / (WIDTH * BYTES_PER_R8G8B8);
      bytes = g_byte_array_free_to_bytes (pixels);
    
      texture = gdk_memory_texture_new (WIDTH, height,
                                        GDK_MEMORY_R8G8B8,
                                        bytes,
                                        WIDTH * BYTES_PER_R8G8B8);
      gtk_picture_set_paintable (picture, GDK_PAINTABLE (texture));
    }
    
    static void
    app_activated_cb (GtkApplication *app)
    {
      GtkWindow *window;
      GtkWidget *picture;
    
      window = GTK_WINDOW (gtk_application_window_new (app));
      g_object_set (window,
                    "width-request", 500,
                    "height-request", 400,
                    NULL);
      picture = gtk_picture_new ();
      gtk_widget_add_css_class (picture, "frame");
      g_object_set (picture,
                    "margin-start", 96,
                    "margin-end", 96,
                    "margin-top", 96,
                    "margin-bottom", 96,
                    NULL);
    
      gtk_window_set_child (window, picture);
      add_pixel_picture (GTK_PICTURE (picture));
    
      gtk_window_present (window);
    }
    
    int
    main (int   argc,
          char *argv[])
    {
      g_autoptr(GtkApplication) app = gtk_application_new (NULL, 0);
    
      g_signal_connect (app, "activate", G_CALLBACK (app_activated_cb), NULL);
    
      return g_application_run (G_APPLICATION (app), argc, argv);
    }
    

    Use the GdkMemoryFormat that suites your data, or convert it to one that's supported.