Thread: How to display a 2D array as an image using GTK3+?

  1. #1
    Registered User
    Join Date
    Dec 2016
    Posts
    30

    How to display a 2D array as an image using GTK3+?

    Hi folks,

    I would like to know how to display an image based on the pixels defined by a 2D array using GTK3+?

    As an example, I would like to display the following myArray (in gray scale):
    Code:
    int cols = 10;
    int rows = 10;
    
    
    int[][] myArray = {  {0, 1, 2, 3},
                         {3, 222, 1, 0},
                         {3, 50, 16, 1},
                         {3, 8, 3, 4}  };
    Does GTK3+ provide functions to display a 2D array as a set of pixels?

    If not, which C libraries would?

    Thanks

  2. #2
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,613
    If these are RGB values, then I guess the right thing to do is create a cairo surface and then use it to make a GtkImage.

    Suggested functions:
    cairo_image_surface_create_for_data ()
    Image Surfaces
    gtk_image_new_from_surface ()
    GtkImage: GTK+ 3 Reference Manual

    Click on user defined types that you don't understand. Hope that helps.
    Last edited by whiteflags; 03-31-2017 at 03:00 AM.

  3. #3
    Registered User
    Join Date
    Dec 2016
    Posts
    30
    Thanks,

    Considering the first part of the question, transforming the char array into an image, I tried unsuccessfully:

    Code:
    #include <cairo.h>
    #include <stdlib.h>
    
    int
    main (int argc, char *argv[])
    {
    char blue_channel[3][4] = {  
       {155, 1, 43, 23} ,   
       {79, 57, 6, 86} ,   
       {26, 92, 57, 210}   
    };
        
    cairo_surface_t *surface = cairo_image_surface_create_for_data (blue_channel, 3, 4, 3);
            cairo_t *cr = cairo_create (surface);
            
            cairo_destroy (cr);
            
            cairo_surface_write_to_png (surface, "hello.png");
            cairo_surface_destroy (surface);
            return 0;
    }
    where I got the error:
    error: too few arguments to function ‘cairo_image_surface_create_for_data’

    How could I make this work, .e.g. create a 3 x 4 pixel blue image corresponding to the char matrix 'blue_channel'?

  4. #4
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    You don't need to use cairo.

  5. #5
    Registered User
    Join Date
    Dec 2016
    Posts
    30
    Quote Originally Posted by algorism View Post
    You don't need to use cairo.
    What do I use then?

    It would be awesome if you could paste some useful working code.

  6. #6
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    I didn't answer yesterday because you ignored my answer to your last question. Anyway, if you have b/w data you just need to convert it to rgb data (gdk only uses rgb/rgba). Then make a pixbuf and create an image from that. The arrays should be one-dimensional, which you can access two-dimensionally like this: a[row*COLUMNS + col].
    Code:
    // gcc img.c `pkg-config --cflags gtk+-3.0 --libs gtk+-3.0` -o img
    
    #include <gtk/gtk.h>
    
    #define ROWS 600
    #define COLS 600
    #define BYTES_PER_PIXEL 3
    
    void bw_to_rgb(guchar *rgb, guchar *bw, size_t sz) {
      for (size_t i = 0; i < sz; i++)
        for (size_t j = 0; j < BYTES_PER_PIXEL; j++)
          rgb[i * BYTES_PER_PIXEL + j] = bw[i];
    }
    
    int main(void) {
      // grayscale data array
      guchar bw[ROWS * COLS] = { 0 }; // start all black
    
      // draw a design
      for (int color = 0, i = 20; i < ROWS; i += 20) {
        color = (color == 0) ? 255 : 0;
        for (int r = i; r < ROWS - i; r++)
          for (int c = i; c < COLS - i; c++)
            bw[r * COLS + c] = color;
      }
    
      // convert to rgb (by tripling the values)
      guchar rgb[sizeof bw * BYTES_PER_PIXEL];
      bw_to_rgb(rgb, bw, ROWS * COLS);
    
      gtk_init(&argc, &argv);
    
      GdkPixbuf *pb = gdk_pixbuf_new_from_data(
        rgb,
        GDK_COLORSPACE_RGB,     // colorspace (must be RGB)
        0,                      // has_alpha (0 for no alpha)
        8,                      // bits-per-sample (must be 8)
        COLS, ROWS,             // cols, rows
        COLS * BYTES_PER_PIXEL, // rowstride
        NULL, NULL              // destroy_fn, destroy_fn_data
      );
      GtkWidget *image = gtk_image_new_from_pixbuf(pb);
    
      GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title(GTK_WINDOW(window), "Image");
      gtk_window_set_default_size(GTK_WINDOW(window), COLS+20, ROWS+20);
      gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
      gtk_container_add(GTK_CONTAINER(window), image);
      g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
      gtk_widget_show_all(window);
    
      gtk_main();
    
      return 0;
    }

  7. #7
    Registered User
    Join Date
    Dec 2016
    Posts
    30
    Thanks very much!

    It works very well (however i have to replace
    Code:
    int main(void)
    by
    Code:
    int main(int   argc,char *argv[])
    to get it to compile)


    Quote Originally Posted by algorism View Post
    I didn't answer yesterday because you ignored my answer to your last question.
    I'm sorry I think I checked 1h ago and my last question had 0 answers...!

  8. #8
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    Oops! I changed main's args after I copied and pasted it here (I guess I thought it wasn't being used, but of course it's being used by gtk_init).

    Now that I think about it, I believe that the "stride" should always be a multiple of 4, so here's a rewrite that does that. It also dynamically allocates the rgb array, which requires passing a function pointer to gdk_pixbuf_new_from_data to free the memory. I also moved the variable declarations for more C89 compliance (for no particular reason).
    Code:
    // gcc img.c `pkg-config --cflags gtk+-3.0 --libs gtk+-3.0` -o img
    
    #include <gtk/gtk.h>
    #include <stdlib.h>
    
    #define ROWS 500
    #define COLS 800
    #define BYTES_PER_PIXEL 3
    
    // convert grayscale to rgb
    // by tripling the values and adjusting the stride to a multiple of 4
    char *bw_to_rgb(const guchar *bw, int rows, int cols, int *stride) {
      int r, c, i, stride_adjust;
      guchar *rgb;
    
      *stride = cols * BYTES_PER_PIXEL;
      stride_adjust = (4 - *stride % 4) % 4;
      *stride += stride_adjust;
    
      rgb = malloc(*stride * rows * BYTES_PER_PIXEL);
      for (r = 0; r < rows; r++) {
        for (c = 0; c < cols; c++)
          for (i = 0; i < BYTES_PER_PIXEL; i++)
            rgb[r * *stride + c * BYTES_PER_PIXEL + i] = bw[r * cols + c];
        for (i = 0; i < stride_adjust; i++)
          rgb[r * *stride + cols * BYTES_PER_PIXEL + i] = 0;
      }
    
      return rgb;
    }
    
    void free_rgb(guchar *pixels, gpointer data) {
      free(pixels);
    }
    
    int main(int argc, char **argv) {
      GtkWidget *window, *image;
      GdkPixbuf *pb;
      guchar bw[ROWS * COLS] = {0};
      guchar *rgb;
      int r, c, stride;
    
      for (r = 0; r < ROWS; r++)
        for (c = 0; c < COLS; c++)
          if (r / 20 % 2 && c / 20 % 2)
            bw[r*COLS + c] = 255;
    
      rgb = bw_to_rgb(bw, ROWS, COLS, &stride);
    
      gtk_init(&argc, &argv);
    
      pb = gdk_pixbuf_new_from_data(
        rgb,
        GDK_COLORSPACE_RGB,     // colorspace (must be RGB)
        0,                      // has_alpha (0 for no alpha)
        8,                      // bits-per-sample (must be 8)
        COLS, ROWS,             // cols, rows
        stride,                 // rowstride
        free_rgb,               // destroy_fn
        NULL                    // destroy_fn_data
      );
      image = gtk_image_new_from_pixbuf(pb);
    
      window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title(GTK_WINDOW(window), "Image");
      gtk_window_set_default_size(GTK_WINDOW(window), COLS+20, ROWS+20);
      gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
      gtk_container_add(GTK_CONTAINER(window), image);
      g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
      gtk_widget_show_all(window);
    
      gtk_main();
    
      return 0;
    }

  9. #9
    Registered User
    Join Date
    Dec 2016
    Posts
    30
    Thanks Algorism for the improvements!

    One more question: Suppose I wanted to create an animation by updating the bw array and make things move horizontally.

    I tried the following approach using a for loop to update the bw array and display the image:

    Code:
    // gcc img.c `pkg-config --cflags gtk+-3.0 --libs gtk+-3.0` -o img
     
    #include <gtk/gtk.h>
    #include <stdlib.h>
     
    #define ROWS 500
    #define COLS 800
    #define BYTES_PER_PIXEL 3
     
    // convert grayscale to rgb
    // by tripling the values and adjusting the stride to a multiple of 4
    char *bw_to_rgb(const guchar *bw, int rows, int cols, int *stride) {
      int r, c, i, stride_adjust;
      guchar *rgb;
     
      *stride = cols * BYTES_PER_PIXEL;
      stride_adjust = (4 - *stride % 4) % 4;
      *stride += stride_adjust;
     
      rgb = malloc(*stride * rows * BYTES_PER_PIXEL);
      for (r = 0; r < rows; r++) {
        for (c = 0; c < cols; c++)
          for (i = 0; i < BYTES_PER_PIXEL; i++)
            rgb[r * *stride + c * BYTES_PER_PIXEL + i] = bw[r * cols + c];
        for (i = 0; i < stride_adjust; i++)
          rgb[r * *stride + cols * BYTES_PER_PIXEL + i] = 0;
      }
     
      return rgb;
    }
     
    void free_rgb(guchar *pixels, gpointer data) {
      free(pixels);
    }
     
    int main(int argc, char **argv) {
      GtkWidget *window, *image;
      GdkPixbuf *pb;
      guchar bw[ROWS * COLS] = {0};
      guchar *rgb;
      int r, c, stride;
     
      for (r = 0; r < ROWS; r++)
        for (c = 0; c < COLS; c++)
          if (r / 20 % 2 && (c+2) / 20 % 2)
            bw[r*COLS + c] = 255;
     
      rgb = bw_to_rgb(bw, ROWS, COLS, &stride);
     
      gtk_init(&argc, &argv);
     
     for (int N=0; N<1000; N++){
         
        for (r = 0; r < ROWS; r++)
        for (c = 0; c < COLS; c++)
          if (r / 20 % 2 && (c+N) / 20 % 2)
            bw[r*COLS + c] = 255;
     
      pb = gdk_pixbuf_new_from_data(
        rgb,
        GDK_COLORSPACE_RGB,     // colorspace (must be RGB)
        0,                      // has_alpha (0 for no alpha)
        8,                      // bits-per-sample (must be 8)
        COLS, ROWS,             // cols, rows
        stride,                 // rowstride
        free_rgb,               // destroy_fn
        NULL                    // destroy_fn_data
      );
      image = gtk_image_new_from_pixbuf(pb);
      
      window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title(GTK_WINDOW(window), "Image");
      }
      gtk_window_set_default_size(GTK_WINDOW(window), COLS+20, ROWS+20);
      gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
      gtk_container_add(GTK_CONTAINER(window), image);
      g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
      gtk_widget_show_all(window);
     
      gtk_main();
     
      return 0;
    }

    However, the squares will not move. How would I make this work?

  10. #10
    Registered User
    Join Date
    Jun 2015
    Posts
    1,640
    GTK is "event driven" which means that you should set things up in main, including event handlers, and then call gtk_main which is the main program. It goes to sleep and waits for events, which it responds to. Events include keystokes and mouse clicks, but what you want is a timer event to call a function every x milliseconds, where you would update the image and display it.

    I've changed the drawing method to draw directly to the rgb data instead of using the intervening grayscale data. And because you didn't say you had a preference, I'm back to my usual C99 mode of declaring variables close to their first use.
    Code:
    #include <gtk/gtk.h>
    #include <stdlib.h>
    
    #define ROWS 500
    #define COLS 800
    #define BYTES_PER_PIXEL 3
    
    typedef struct {
      GtkImage *image;
      int rows, cols, stride;
    } ImageData;
    
    void free_pixels(guchar *pixels, gpointer data) {
      free(pixels);
    }
    
    void setrgb(guchar *a, int row, int col, int stride,
                guchar r, guchar g, guchar b) {
      int p = row * stride + col * BYTES_PER_PIXEL;
      a[p] = r; a[p+1] = g; a[p+2] = b;
    }
    
    int update_pic(gpointer data) {
      static int N = 0;
      if (N > 100) return FALSE; // stop timer
    
      ImageData *id = (ImageData*)data;
      GdkPixbuf *pb = gtk_image_get_pixbuf(id->image);
      gdk_pixbuf_fill(pb, 0); // clear to black
    
      guchar *g = gdk_pixbuf_get_pixels(pb);
      for (int r = 0; r < ROWS; r++)
        for (int c = 0; c < COLS; c++)
          if (r / 20 % 2 && (c + N)/ 20 % 2)
            setrgb(g, r, c, id->stride, 255, 255, 255);
      N++;
    
      gtk_image_set_from_pixbuf(GTK_IMAGE(id->image), pb);
    
      return TRUE; // continue timer
    }
    
    int main(int argc, char **argv) {
      gtk_init(&argc, &argv);
    
      ImageData id;
      id.rows = ROWS;
      id.cols = COLS;
      id.stride = COLS * BYTES_PER_PIXEL;
      id.stride += (4 - id.stride % 4) % 4; // ensure multiple of 4
    
      guchar *pixels = calloc(ROWS * id.stride, 1);
    
      GdkPixbuf *pb = gdk_pixbuf_new_from_data(
        pixels,
        GDK_COLORSPACE_RGB,     // colorspace
        0,                      // has_alpha
        8,                      // bits-per-sample
        COLS, ROWS,             // cols, rows
        id.stride,              // rowstride
        free_pixels,            // destroy_fn
        NULL                    // destroy_fn_data
      );
    
      id.image = GTK_IMAGE(gtk_image_new_from_pixbuf(pb));
    
      GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title(GTK_WINDOW(window), "Image");
      gtk_window_set_default_size(GTK_WINDOW(window), COLS, ROWS);
      gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
      g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
      gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(id.image));
    
      g_timeout_add(250,         // milliseconds
                    update_pic,  // handler function
                    &id);        // data
    
      gtk_widget_show_all(window);
      gtk_main();
    
      return 0;
    }

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. How to display an image?
    By byebyebyezzz in forum C++ Programming
    Replies: 1
    Last Post: 09-11-2011, 06:40 PM
  2. SDL wont display .png image
    By bijan311 in forum C++ Programming
    Replies: 5
    Last Post: 04-26-2010, 06:55 AM
  3. display image name
    By shamee_banu in forum Windows Programming
    Replies: 1
    Last Post: 06-29-2007, 12:13 PM
  4. 8-bit image display
    By rakesh09 in forum C Programming
    Replies: 1
    Last Post: 03-20-2003, 07:28 PM
  5. Display a bitmap image
    By MysticMizra in forum Windows Programming
    Replies: 7
    Last Post: 10-21-2002, 03:36 AM

Tags for this Thread