How to display a png image use GtkGLArea

Operation System: windows 10

BuildEnv: msys2 + mingw32

GTK Version: 3.24.33

I’m new to opengl , i use GtkGLArea widget to display a image on win32 platform, but it didn’t work, the window’s background color is changed, but image not display, main code as follow:

static gboolean render(GtkGLArea *area, GdkGLContext *context)
{
    //work, background color changed
    glClearColor (0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    cairo_surface_t* surface = cairo_image_surface_create_from_png("./test.PNG");
    unsigned char* data= cairo_image_surface_get_data(surface);
    
    //not work
    glRasterPos2f(-1,1);
    glPixelZoom( 1, -1 );
    glDrawPixels(
            cairo_image_surface_get_width(surface),
            cairo_image_surface_get_height(surface),
            GL_BGRA,GL_UNSIGNED_BYTE,data);

    cairo_surface_destroy(surface);

    return TRUE;
}

static void activate(GtkApplication *app, gpointer user_data)
{
    GtkWidget* window;
    GtkWidget* gl_area;

    window = gtk_application_window_new(app);
    gtk_window_set_title(GTK_WINDOW(window), "picplayer-gl");
    gtk_window_set_default_size(GTK_WINDOW(window), 1920, 1080);

    gl_area = gtk_gl_area_new();
    gtk_container_add(GTK_CONTAINER(window),gl_area);

    g_signal_connect(gl_area,"render",G_CALLBACK(render),NULL);

    gtk_widget_show_all(window);
}

int main (int argc, char *argv[])
{
    GtkApplication *app;
    int status;

    app = gtk_application_new("org.gtk3.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

and i also test the follow code, use texture, but not work

static gboolean render(GtkGlArea *area, GdkGlContext *content)
{
    //work, background color changed
    glClearColor (0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    cairo_surface_t* surface = cairo_image_surface_create_from_png("./test.png");
    unsigned char* data= cairo_image_surface_get_data(surface);

    //use texture , not work
    GLuint texture = 0;

    glGenTextures(1,&texture);
    glBindTexture(GL_TEXTURE_2D,texture);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  

    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,
                  cairo_image_surface_get_width(surface),
                  cairo_image_surface_get_height(surface),
                  0,GL_BGRA_EXT,GL_UNSIGNED_BYTE,data);

    cairo_surface_destroy(surface);

    glFlush();

    return TRUE;
}

what’s wrong with it ? the gtk3-opengl demo draw triangle is ok,and the above code on glfw works well

Albeit not on Windows, your code work OK for me if I have a file test.PNG in the current directory. Maybe your executable is not run form the directory you think it is, and that file is thus not found?

You can check for loading errors (be it file not found, corrupted or otherwise not loadable), with e.g. something like this:

    cairo_status_t surface_status = cairo_surface_status(surface);
    if (surface_status != CAIRO_STATUS_SUCCESS)
        fprintf(stderr, "failed to load image: %s\n", cairo_status_to_string(surface_status));

HTH

Thank you for test, i checked it before , surface load test.png successfully, but image not display on GtkGlArea, and the same codes worked ok on glfw gui library. The test.png is any pictures 1920x1080.

The complete code follow:

#include <stdlib.h>
#include <gtk/gtk.h>
#include <GL/glew.h>

#define METHOD_TYPE 1

static void on_realize (GtkGLArea *area)
{
    gtk_gl_area_make_current (area);
    if (gtk_gl_area_get_error (area) != NULL)
      return;

    if (glewInit() != GLEW_OK)
    {
        g_print("glewInit failed. \n");
    }
}

unsigned char* g_data;

static gboolean render(GtkGLArea *area, GdkGLContext *context)
{
    if (gtk_gl_area_get_error (area) != NULL)
    {
        g_print("failed render GtkGlArea. \n");
        return FALSE;
    }
        
#if METHOD_TYPE
    //work, background color changed
    glClearColor (0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    cairo_surface_t* surface = cairo_image_surface_create_from_png("./test.png");
    cairo_status_t surface_status = cairo_surface_status(surface);
    if (surface_status != CAIRO_STATUS_SUCCESS)
    {
        g_print("failed to load image: %s. \n", cairo_status_to_string(surface_status));
        return FALSE;
    }
        
    unsigned char* data= cairo_image_surface_get_data(surface);
    
    //not work
    glRasterPos2f(-1,1);
    glPixelZoom( 1, -1 );
    glDrawPixels(
            cairo_image_surface_get_width(surface),
            cairo_image_surface_get_height(surface),
            GL_BGRA,GL_UNSIGNED_BYTE,data);

    cairo_surface_destroy(surface);
#else
    //use texture , not work
    GLuint texture = 0;

    glGenTextures(1,&texture);
    glBindTexture(GL_TEXTURE_2D,texture);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  

    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,
                  cairo_image_surface_get_width(surface),
                  cairo_image_surface_get_height(surface),
                  0,GL_BGRA_EXT,GL_UNSIGNED_BYTE,data);

    cairo_surface_destroy(surface);
#endif

    glFlush ();

    return TRUE;
}

static void activate(GtkApplication *app, gpointer user_data)
{
    GtkWidget* window;
    GtkWidget* gl_area;

	window = gtk_application_window_new(app);
	gtk_window_set_title(GTK_WINDOW(window), "picplayer-gl");
	gtk_window_set_default_size(GTK_WINDOW(window), 1920, 1080);

	gl_area = gtk_gl_area_new();
	gtk_container_add(GTK_CONTAINER(window),gl_area);
    
	g_signal_connect(gl_area,"realize",G_CALLBACK(on_realize),NULL);
    g_signal_connect(gl_area,"render",G_CALLBACK(render),NULL);

	gtk_widget_show_all(window);
}

int main (int argc, char *argv[])
{
    GtkApplication *app;
    int status;

    app = gtk_application_new("org.gtk3.example", G_APPLICATION_FLAGS_NONE);
    g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
    status = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);

    return status;
}

Build command:

gcc -o gtk_picplayer main.c `pkg-config --cflags --libs glew cairo gtk+-3.0` -lopengl32

The glfw code as follow, work well

#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <cairo.h>

void processInput(GLFWwindow *window);

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_COMPAT_PROFILE);

    GLFWwindow* win = glfwCreateWindow(1920,1080,"picplayer",NULL,NULL);
    if (win == NULL)
    {
        printf("Create window fialed.\n");
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(win);

    if (glewInit() != GLEW_OK)
    {
        printf("glewInit failed. \n");
        glfwTerminate();
        return -1;
    }

    printf("%s\n",glfwGetVersionString());

    glViewport(0,0,1920,1080);

    cairo_surface_t* surface = cairo_image_surface_create_from_png("./test.png");
    unsigned char* data= cairo_image_surface_get_data(surface);

    while(!glfwWindowShouldClose(win))
    {
        processInput(win);

        glClearColor (0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glRasterPos2f(-1,1);
        glPixelZoom( 1, -1 );
        glDrawPixels(
            cairo_image_surface_get_width(surface),
            cairo_image_surface_get_height(surface),
            GL_BGRA,GL_UNSIGNED_BYTE,data);
        
        glfwSwapBuffers(win);
        glfwPollEvents();
    }

    glfwTerminate();

    return 0;
}

void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window,GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window,GLFW_TRUE);
    }
}

The main difference between GLFW and GtkGLArea is that GtkGLArea is rendering to a FBO, whereas GLFW will render to an on screen buffer. This means that there are differences in how the GL driver might decide to perform texture format conversions. On top of that, GL drivers on Windows are known to be flaky or downright bugged unless you use code paths that games might use. The fact that GLFW is working with the code you’re throwing at it is, by and large, inconsequential.

Cairo uses an ARGB format for its data: you cannot simply pass the pixel data as is and assume it’ll work. You will need to get the pixel data from the image surface in the ARGB pixel format and then convert each pixel client side to some other format, like RGBA, before uploading it to the GL driver.

Another option is to skip Cairo entirely. Using Cairo to load a PNG file is pointless: it’s like using Excel to take notes. You can use libpng to load the image data, and specify a format that is most likely going to work with GL.

Thank you, i write codes use libpng to get test.png’s pixel buffer , and print it’s color_type is PNG_COLOR_TYPE_RGB , code as follow:

static void* readPng(const char* in_filePath, int* out_width, int* out_height) 
{

    void* pixelData = NULL;
    FILE* fp = NULL;

    do
    {
        fp = fopen(in_filePath, "rb");
        if(!fp) {
            break;
        }


        unsigned char head[8];
        fread(head,1,8,fp);
        if(png_sig_cmp(head, 0, 8)) {
            g_print("File %s, is not PNG. \n", in_filePath);
            break;
        }

       /* Create and initialize the png_struct with the desired error handler
        * functions.  If you want to use the default stderr and longjump method,
        * you can supply NULL for the last three parameters.  We also supply the
        * the compiler header file version, so that we know if the application
        * was compiled with a compatible version of the library.  REQUIRED
        */
        png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        if (!pngPtr) {
            g_print("Unable to create PNG structure: %s. \n", in_filePath);
            break;
        }

        // Allocate/initialize the memory for image information.  REQUIRED
        png_infop infoPtr = png_create_info_struct(pngPtr);
        if (!infoPtr) {
            png_destroy_read_struct(&pngPtr, NULL, NULL);
            g_print("Unable to create PNG info : %s. \n", in_filePath);
            break;
        }

        png_init_io(pngPtr,fp);

        // If we have already read some of the signature
        png_set_sig_bytes(pngPtr, 8);

       /* The call to png_read_info() gives us all of the information from the
        * PNG file before the first IDAT (image data chunk).  REQUIRED
        */
        png_read_info(pngPtr, infoPtr);

        int bitDepth;
        int colorType;
        int interlaceype;

        png_uint_32 width;
        png_uint_32 height;

        png_get_IHDR(pngPtr, infoPtr,
                &width,
                &height,
                &bitDepth,&colorType, &interlaceype, NULL, NULL);

        g_print("bit_depth:%d, color_type:%d, interlace:%d. \n", bitDepth,colorType,interlaceype);

        if (out_width)
            *out_width  = width;
        if (out_height)
            *out_height = height;

        g_print("PNG width = %d, height = %d. \n", width, height);

        // Update the png info struct.
        png_read_update_info(pngPtr, infoPtr);

        // Allocate the memory to hold the image using the fields of info_ptr

        unsigned int rowBytes = png_get_rowbytes(pngPtr, infoPtr);
        g_print("Row size: %d bytes. \n", rowBytes);

        // Allocate the pixel data as a big block, to be given to openGL
        pixelData = png_malloc(pngPtr, rowBytes * height);
        if(!pixelData) {
            png_destroy_read_struct(&pngPtr, &infoPtr, NULL);
            g_print("Unable to allocate PNG pixel data while loading %s. \n", in_filePath);
            break;
        }

        /* Turn on interlace handling.  REQUIRED if you are not using
         * png_read_image().  To see how to handle interlacing passes,
         * see the png_read_row() method below:
         */
        int numberPasses = png_set_interlace_handling(pngPtr);
        g_print("interlacing passes = %d. \n", numberPasses);
        for (int pass = 0; pass < numberPasses; pass++) {
            for (int row = 0; row < height; row++) {
               png_read_row(pngPtr, ((unsigned char*)pixelData + (row * rowBytes)), NULL);
            }
        }

        // Read rest of file, and get additional chunks in info_ptr - REQUIRED
        png_read_end(pngPtr, infoPtr);

        // At this point you have read the entire image

        // Clean up after the read, and free any memory allocated - REQUIRE
        png_destroy_read_struct(&pngPtr, &infoPtr, NULL);
    } while(0);

    if (fp) fclose(fp);

    return pixelData;
}

and main render codes as follow, two method, not work. glfw ok.

    int width,height;
    unsigned char* data = readPng("./test.png",&width,&height);

#if METHOD_ONE
   //method one
    glRasterPos2f(-1,1);
    glPixelZoom( 1, -1 );
    glDrawPixels(
            width,
            height,
            GL_RGB,GL_UNSIGNED_BYTE,data);
#else
    //method two
    GLuint texture = 0;

    glGenTextures(1,&texture);
    glBindTexture(GL_TEXTURE_2D,texture);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,
                  width,
                  height,
                  0,GL_RGB,GL_UNSIGNED_BYTE,data);

    //cairo_surface_destroy(surface);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glEnable(GL_TEXTURE_2D);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(-1.0f,-1.0f, 1.0f);

    glTexCoord2f(1.0f, 1.0f);
    glVertex3f( 1.0f,-1.0f, 1.0f);

    glTexCoord2f(1.0f, 0.0f);
    glVertex3f( 1.0f, 1.0f, 1.0f);

    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glEnd();
    glDisable(GL_TEXTURE_2D);
#endif
    glflush();

so, if i need to test all formats likie RGBA , ARGB or some others ?

You are using GL1 API like glVertex and glLoadIdentity and glBegin, which will never work with GtkGLArea, since it creates a GL3 core context by default. You will need to upload the texture and use a shader to control where and how the texture is rendered.

Ok,i find the shader is complex, is there some open source demo or project to show how to display a image use GtkGlArea? I search google but all demo i found is drawing triangle, the gtk3-demo’s GtkGlArea example is drawing triangle too

I finally found a available c++ example, and changed it to GtkGlArea, it displayed test.png successfully, and something need to be improved.

For a openGL newbie, unlike glfw, it is hard to understand how to use GtkGLArea to do something simple, maybe gtk3-demo can add more example about it.

First of all, any update will be done for GTK4, and not for GTK3, at this point.

Additionally, GTK-Demo (and the GTK documentation) are meant for GTK, not for OpenGL. Loading and drawing textures isn’t really special to GTK’s integration with OpenGL; you can find tutorials on those anywhere.

Your problems were:

  1. you were using Cairo, a 2D drawing library, to load a PNG; you should have used gdk-pixbuf (an image loading library) or libpng directly
  2. in your Cairo example you were loading the texture and not drawing it anywhere, I guess because you assumed that Cairo would draw it. Cairo draws on the CPU, not on the GPU
  3. with GLFW, you’re using GL1 and GL2 API that has long since been deprecated and removed when using core GL contexts, like glDrawPixels or glTexCoord, or glVertex. This is because you’re asking for a backward compatibility profile in GLFW. GTK does not do that: by default it will ask for a core context profile, and will fall back to a legacy profile if the GL driver does not support it. You will need to ask if the context associated to the GLArea widget is a legacy context, before using legacy GL API; in practice, though, everything released in the past 10 years supports core contexts, which means you’ll have to use modern GL API—mainly the programmable pipeline.

The GtkGLArea demo in GTK already shows you how to set up the shaders and the geometry for drawing; that it does not use a texture is immaterial: a texture is just a sampling object in modern GL.

Yes , you are right . At first , i only wan’t to know if gtk3 support display image use opengl, i think it’s easy, so i didn’t waste much time on opengl itself

In fact, the reason I use the old api glDrawPixels, glEnable and glLoadIdentity etc. , because most of the tutorials and example codes i searched is old and use glfw , maybe they think the old way is simple and friendly to opengl newbie to start study, it dosn’t need newbie to know shader which is complex.

Later, i test these codes successfully on glfw, so i thought GTK was the same way. In my cognition, i thought the difference of glfw and gtk is that one need call swapbuf api and another needn’t . Obviously , i was wrong.

Thank you again for your reply

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