Saturday, June 12, 2010

glDrawPixels + Qt (Part 1)

glDrawPixels + Qt (Part 1)

This is an article following GL_POINTS + Qt.  As mentioned earlier, we can use glDrawPixels with glPixelZoom to deal with 2D graphics.
Although it appears to be more complicated than GL_POINTS, glDrawPixels is still another simple method.

The strength of glDrawPixels is its ability to scale an image in a way most people expect.  For example, a pixel in the original scale may occupy more than one pixel in a window, if the window is scaled up.
We can also increase draw rate by using buffer, but Part 1 will not cover that topic.

Now, let's see how to draw an image by using glDrawPixels and glPixelZoom.  Recall that the coordinate system of OpenGL may be different from most imaging systems.  OpenGL set the origin at the bottom left by default.

The major difference between GL_POINTS and glDrawPixels is that GL_POINTS is based on a call list, while glDrawPixels is based on an array.
This array is a flatten 3D or 4D array.  Namely, RGB(A) data is kept together in a 1D array.  We have to tell OpenGL a color model we want to use.

Assume that we want to use the RGB color model and the image is of size 128x128 pixels.  Therefore, we have to create a 1D array of size 128x128x3.  RGB values of one pixels are packed at contiguous array cells, as shown below.

/// Build a checker board, each grid cell is of size 16 x 16 pixels.
///
0x10 is a base-16 number equal to 16.
void GLImage::makeImage() {
    chkImage = new GLubyte[m_nHeight * m_nWidth * 3];

    int c;
    for (int h = 0; h < m_nHeight; ++h)
        for (int w = 0; w < m_nWidth; ++w) {
            c = ( ((h&0x10) == 0)^((w&0x10) == 0) )*255;
            chkImage[3*(h*m_nWidth + w) + 0] = (GLubyte) c;
            chkImage[3*(h*m_nWidth + w) + 1] = (GLubyte) c;
            chkImage[3*(h*m_nWidth + w) + 2] = (GLubyte) c;
        }

    return;
}


The drawing process is super easy.

void GLImage::paintGL() {
    glClear(GL_COLOR_BUFFER_BIT);
    glRasterPos2i(0, 0);
    glDrawPixels(m_nWidth, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE, chkImage);
}

The resize process needs to specify zoom factors for both x- and y-axes.  Note that we may not need to use m_nCurrWidth and m_nCurrHeight, if we do not intend to use them for other proposed.

void GLImage::resizeGL(int width, int height) {
    glViewport(0, 0, width, height);
    m_nCurrWidth = width;
    m_nCurrHeight = height;

    glPixelZoom(m_nCurrWidth / (double) m_nWidth, m_nCurrHeight / (double) m_nHeight);
}


The way for initialization may be a bit more complicated than usual, but it is a pattern we can just follow (somewhat blindly).

void GLImage::initializeGL()
{
    glClearColor(0.0, 0.0, 0.0, 0.0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, m_nWidth, 0, m_nHeight, 0, 1);
    glMatrixMode(GL_MODELVIEW);

    glShadeModel(GL_FLAT);
    makeImage();
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
}


Notice the use of glShadeModel(GL_FLAT).  glPixelStorei is employed to specify the way we store color data.  The parameter 1 indicates that it is 1-btye alignment.  Basically, there is no restriction of the beginning position of each row with this parameter.  If it is 2, each row must start on even-numbered byte.


========================================

An example Eclipse CDT / Qt project is stored at a public folder here.
The project file is valid for Eclipse CDT on Linux with Qt integration (Ubuntu 9.04 Juanty), but the main part should be applicable to most C++ environment.


Pinyo Taeprasartsit
September 2009

No comments: