Integrating OpenGL Code into a Qt Project for 2D Rendering: Basic Concept and Approach

The national flag of US rendered with OpenGL as the result of our Qt project

1. What are we going to do in this Qt project?

Let us start with a very simple project and understand how OpenGL code should be integrated into a Qt project. The OpenGL code renders the National Flag of the United States and shows it on the screen. I am assuming that readers completely understand the OpenGL code. In this blog I am concentrating on Qt part of it.

In this project, we are going to design two C++ classes and use one main.cpp file. The first class, called “MainWidget”, is the top level widget/window that serves as the container of a QWidget (flag on the left side of the top screen shot), a QPushButton (the “Close” button on its right side) and a vertical Spencer (invisible in the screen shot, to the right of the flag and on top of the “Close” button). The other class, named “GLWidget”, is the class that the original QWidget in the form of Qt Designer is going to be “promoted” to. This is where our OpenGL code would render its content, namely, the flag on the left side of the top screen shot.

The project file is as follows:

QT += opengl
TARGET = 1_usFlag
TEMPLATE = app
SOURCES += main.cpp \
mainwidget.cpp \
glwidget.cpp
HEADERS += mainwidget.h \
glwidget.h
FORMS += mainwidget.ui

Step 1: Creating a Qt project

Open the Qt creator IDE by typing “qtcreator”  in an opened terminal. Select “File->New File or Project” to create a new Qt project. Name the project as “1_usFlag” and press “OK” button; select “Qt4 Gui Application”, press “OK” button, and tick “QtOpenGL” in addition to the default selection of “QtCore” and “QtGui”, finally press “Next” button.

In the opened window, type “MainWidget” as the Class Name, select “QWidget” as the Base class, “QObject” as the Inherited Class, both from drop down menu  and press “next” button to finish. The IDE would automatically generate “mainwidget.ui” form and the following three files for us:

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>

namespace Ui {
class MainWidget;
}

class MainWidget : public QWidget {
Q_OBJECT
public:
MainWidget(QWidget *parent = 0);
~MainWidget();

protected:
void changeEvent(QEvent *e);

private:
Ui::MainWidget *ui;
};

#endif // MAINWIDGET_H

mainwidget.cpp

#include "mainwidget.h"
#include "ui_mainwidget.h"

MainWidget::MainWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MainWidget)
{
ui->setupUi(this);
}

MainWidget::~MainWidget()
{
delete ui;
}

void MainWidget::changeEvent(QEvent *e)
{
QWidget::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
}

main.cpp

#include <QtGui/QApplication>
#include "mainwidget.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWidget w;
w.show();
return a.exec();
}

As mentioned early, “mainwidget.h” and “mainwidget.cpp” is our top-level window in the form of a class “MainWidget” that serves as a container of other widgets. We are not going to modify any of the three files. So keep them as they are. However, we are going to work on the “mainwidget.ui” form.

If you compile the project at this point, you are going to see a window that shows nothing. So let us add things for it to display.

Step 2: Work on the form

Now double click on the “mainwidget.ui” form under the “Forms” of the project files. This will bring up the Qt Designer, then add a QPushButton, a Spencer and a QWidget to it. The layout looks like the following screen shot:

Action 1: set maximum constraint for the close button

Action 2: Connect click() signal of "Close" button to close() slot of MainWidget

In order to allow OpenGL code to have the largest area possible to render its content by default or when the window is resizing, we take Actions 1 and 2, and lay out the three items in a grid layout. After this setting up, when the window is resized, the “Close” button and the Spencer would remain the same size, only the Widget part would be resized. We also connect the clicked() signal of the “Close” button to the close() slot of the MainWidget (the top, container window) in Action 2.

Step 3: Adding GLWidget class and have QWidget on the form promoted to it

Now right click on the “1_usFlag” project name and select “Add New”, then “C++ Class”; type “GLWidget” as the Class Name, “QGLWidget” as the Base class, “QWidget” as the Inherited Class and press “next” button to finish. Now we get “glwidget.h” and glwidget.cpp”.

We are going to promote the QWidget on the form of “mainwidget.ui” to our newly added “GLWidget” class illustrated as the following screen shots:

Action 3: select "promote to" for the QWidget on the form for class promotion

Action 4: type above info, press Add and Promote buttons

Action 5: the window should looks like this

Action 5: confirm that the "QWidget" is now changed to "GLWidget" where the cursor is in this shot

Step 4: Implementing “GLWidget” class

Now that the “Widget” in the form is promoted to “GLWidget” class, we can implement this class and add OpenGL code to render the flag into it:

glwidget.h

#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>

class GLWidget : public QGLWidget
{
Q_OBJECT
public:
explicit GLWidget(QWidget *parent = 0);

protected:
void initializeGL();
void resizeGL(int width, int height);
void paintGL();

private:
void drawStar(float fX, float fY);
void drawStars();
void drawStripes();
};

#endif // GLWIDGET_H

glwidget.cpp

#include <cmath>
#include "glwidget.h"

GLWidget::GLWidget(QWidget *parent) :
QGLWidget(parent)
{
}

void GLWidget::initializeGL()
{
glClearColor(0.0, 0.0, 102.0/255.0, 0.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}

void GLWidget::resizeGL(int width, int height)
{
glViewport(0, 0, (GLint)width, (GLint)height);
}

void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
drawStripes();
drawStars();
glFlush();
}

void GLWidget::drawStar(float fX, float fY)
{
const float kfPi = 3.1415926535897932384626433832795;
// draw ten triangles
const float kfRadius = 0.0616/2.0;
const float kfInnerRadius = kfRadius*(1.0/(sin((2.0*kfPi)/5.0)*2.0*cos(kfPi/10.0) + sin((3.0*kfPi)/10.0)));
glColor3f(1.0, 1.0, 1.0);

glBegin(GL_TRIANGLE_FAN);
glVertex3f(fX, fY, 0.0);
for (int iVertIndex = 0; iVertIndex < 10; ++iVertIndex)
{
float fAngleStart    = kfPi/2.0 + (iVertIndex*2.0*kfPi)/10.0;
float fAngleEnd        = fAngleStart + kfPi/5.0;
if (iVertIndex % 2 == 0)
{
glVertex3f(fX + kfRadius*cos(fAngleStart)/1.9, fY + kfRadius*sin(fAngleStart), 0.0);
glVertex3f(fX + kfInnerRadius*cos(fAngleEnd)/1.9, fY + kfInnerRadius*sin(fAngleEnd), 0.0);
} else
{
glVertex3f(fX + kfInnerRadius*cos(fAngleStart)/1.9, fY + kfInnerRadius*sin(fAngleStart), 0.0);
glVertex3f(fX + kfRadius*cos(fAngleEnd)/1.9, fY + kfRadius*sin(fAngleEnd), 0.0);
}
}
glEnd();
}

void GLWidget::drawStars()
{
for (int iStarRow = 0; iStarRow < 9; ++iStarRow)
{
float fY = 6.0/13.0 + (iStarRow + 1)*((7.0/13.0)/10);
// alternate between rows of five or six stars
if (iStarRow % 2 == 0)
{
for (int iStarCol = 0; iStarCol < 6; ++iStarCol)
{
drawStar(iStarCol*((0.76/1.9)/6.0) + (0.76/1.9)/12.0, fY);
}
} else
{
for (int iStarCol = 0; iStarCol < 5; ++iStarCol)
{
drawStar((iStarCol + 1)*((0.76/1.9)/6.0), fY);
}
}
}
}

void GLWidget::drawStripes()
{
for (int iStripeIndex = 0; iStripeIndex < 13; ++iStripeIndex)
{
// Alternate stripe colors
if (iStripeIndex % 2 == 0)
{
glColor3f(204.0/255.0, 0.0, 0.0);
} else
{
glColor3f(1.0, 1.0, 1.0);
}

float fStartX    = 0.0;
float fEndX    = 1.0;
float fStartY    = iStripeIndex*(1.0/13.0);
float fEndY    = (iStripeIndex + 1)*(1.0/13.0);

// the last seven stripes are shorter
if (iStripeIndex > 5)
{
fStartX = 0.76/1.9;
}

glBegin(GL_QUADS);
glVertex3f(fStartX, fStartY, 0.0);
glVertex3f(fEndX, fStartY, 0.0);
glVertex3f(fEndX, fEndY, 0.0);
glVertex3f(fStartX, fEndY, 0.0);
glEnd();
}
}

Compile and run it, we get the following output screen shot:

Result of our project

However, there is one more thing to improve: the national flag of US should has a ratio of 1.9 (W/H) as specified at usflag.org, we should modify the form as illustrated in the following screen shot by setting its minimum width to 950 pixels and height to 500 pixels:

Adjust aspect ration of the flag

Now compile and run our project, we get the screen shot at the top of this blog.

This entry was posted in Qt for 2D graphics with OpenGL. Bookmark the permalink.

Leave a comment