I'm trying to implement image scaling in OpenGL using only glTexCoord2f()
and glVertex2f()
.
Let me explain: after loading a QImage
and sending it to the gpu with glTexImage2D()
I have to perform image scaling operations based on Qt's specification. Qt defines these 3 operations (see image below):
I think this is the only way to do it since my application is a Qt plugin and this task needs to be done inside the paint()
method of the class. The IgnoreAspectRatio
operation is pretty straight forward and it's working right now. The KeepAspectRatio
gave me some trouble initially but now it's also working. Unfortunally, KeepAspectRatioByExpanding
is giving me headaches.
I'm sharing what I've done so far and I appreciate your help on this issue:
main.cpp:
#include "oglWindow.h"
#include <QtGui/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
oglWindow w;
w.show();
return a.exec();
}
oglWindow.cpp:
#include "oglWindow.h"
#include "glwidget.h"
#include <QGridLayout>
oglWindow::oglWindow(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
GLWidget *openGL = new GLWidget(this);
QGridLayout *layout = new QGridLayout;
setLayout(layout);
}
oglWindow::~oglWindow()
{
}
oglWindow.h:
#ifndef oglWindow_H
#define oglWindow_H
#include <QtGui/QMainWindow>
#include "ui_yuv_to_rgb.h"
class oglWindow : public QMainWindow
{
Q_OBJECT
public:
oglWindow(QWidget *parent = 0, Qt::WFlags flags = 0);
~oglWindow();
private:
Ui::oglWindowClass ui;
};
#endif // oglWindow_H
glwidget.cpp:
#ifdef _MSC_VER
#include <windows.h>
#include <GL/glew.h>
#include <GL/gl.h>
#else
#include <GL/gl.h>
#endif
#include "glwidget.h"
#include <QDebug>
#include <iostream>
#include <fstream>
#include <assert.h>
static const char *p_s_fragment_shader =
"#extension GL_ARB_texture_rectangle : enable
"
"uniform sampler2DRect tex;"
"uniform float ImgHeight, chromaHeight_Half, chromaWidth;"
"void main()"
"{"
" vec2 t = gl_TexCoord[0].xy;" // get texcoord from fixed-function pipeline
" float CbY = ImgHeight + floor(t.y / 4.0);"
" float CrY = ImgHeight + chromaHeight_Half + floor(t.y / 4.0);"
" float CbCrX = floor(t.x / 2.0) + chromaWidth * floor(mod(t.y, 2.0));"
" float Cb = texture2DRect(tex, vec2(CbCrX, CbY)).x - .5;"
" float Cr = texture2DRect(tex, vec2(CbCrX, CrY)).x - .5;"
" float y = texture2DRect(tex, t).x;" // redundant texture read optimized away by texture cache
" float r = y + 1.28033 * Cr;"
" float g = y - .21482 * Cb - .38059 * Cr;"
" float b = y + 2.12798 * Cb;"
" gl_FragColor = vec4(r, g, b, 1.0);"
"}";
GLWidget::GLWidget(QWidget *parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent), _frame(NULL)
{
setAutoFillBackground(false);
setMinimumSize(640, 480);
/* Load 1280x768 YV12 frame from the disk */
_frame = new QImage(1280, 768, QImage::Format_RGB888);
if (!_frame)
{
qDebug() << "> GLWidget::GLWidget !!! Failed to create _frame";
return;
}
std::ifstream yuv_file("bloco.yv12", std::ios::in | std::ios::binary | std::ios::ate);
if (!yuv_file.is_open())
{
qDebug() << "> GLWidget::GLWidget !!! Failed to load yuv file";
return;
}
int yuv_file_sz = yuv_file.tellg();
unsigned char* memblock = new unsigned char[yuv_file_sz];
if (!memblock)
{
qDebug() << "> GLWidget::GLWidget !!! Failed to allocate memblock";
return;
}
yuv_file.seekg(0, std::ios::beg);
yuv_file.read((char*)memblock, yuv_file_sz);
yuv_file.close();
qMemCopy(_frame->scanLine(0), memblock, yuv_file_sz);
delete[] memblock;
}
GLWidget::~GLWidget()
{
if (_frame)
delete _frame;
}
void GLWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
qDebug() << "> GLWidget::paintEvent OpenGL:" << ((painter.paintEngine()->type() != QPaintEngine::OpenGL &&
painter.paintEngine()->type() != QPaintEngine::OpenGL2) ? "disabled" : "enabled");
QGLContext* context = const_cast<QGLContext *>(QGLContext::currentContext());
if (!context)
{
qDebug() << "> GLWidget::paintEvent !!! Unable to retrieve OGL context";
return;
}
context->makeCurrent();
painter.fillRect(QRectF(QPoint(0, 0), QSize(1280, 768)), Qt::black);
painter.beginNativePainting();
/* Initialize GL extensions */
GLenum err = glewInit();
if (err != GLEW_OK)
{
qDebug() << "> GLWidget::paintEvent !!! glewInit failed with: " << err;
return;
}
if (!GLEW_VERSION_2_1) // check that the machine supports the 2.1 API.
{
qDebug() << "> GLWidget::paintEvent !!! System doesn't support GLEW_VERSION_2_1";
return;
}
/* Setting up texture and transfering data to the GPU */
static GLuint texture = 0;
if (texture != 0)
{
context->deleteTexture(texture);
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
GL_LUMINANCE, _frame->width(), _frame->height() + _frame->height() / 2, 0,
GL_LUMINANCE, GL_UNSIGNED_BYTE, _frame->bits());
assert(glGetError() == GL_NO_ERROR);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glClearColor(0.3, 0.3, 0.4, 1.0);
int img_width = _frame->width();
int img_height = _frame->height();
int offset_x = 0;
int offset_y = 0;
GLfloat gl_width = width(); // GL context size
GLfloat gl_height = height();
/* Initialize shaders and execute them */
_init_shaders();
qDebug() << "paint(): gl_width:" << gl_width << " gl_height:" << gl_height <<
" img:" << _frame->width() << "x" << _frame->height();
int fill_mode = 1;
switch (fill_mode)
{
case 0: // KeepAspectRatioByExpanding
{
// need help!
}
break;
case 1: // IgnoreAspectRatio
{
// Nothing special needs to be done for this operation.
}
break;
case 2: // KeepAspectRatio
default:
{
// Compute aspect ratio and offset Y for widescreen borders
double ratiox = img_width/gl_width;
double ratioy = img_height/gl_height;
if (ratiox > ratioy)
{
gl_height = qRound(img_height / ratiox);
offset_y = qRound((height() - gl_height) / 2);
gl_height += offset_y * 2;
}
else
{
gl_width = qRound(img_width / ratioy);
offset_x = qRound((width() - gl_width) / 2);
gl_width += offset_x * 2;
}
}
break;
}
// Mirroring texture coordinates to flip the image vertically
glBegin(GL_QUADS);
glTexCoord2f(0.0f, img_height); glVertex2f(offset_x, gl_height - offset_y);
glTexCoord2f(img_width, img_height); glVertex2f(gl_width - offset_x, gl_height - offset_y);
glTexCoord2f(img_width, 0.0f); glVertex2f(gl_width - offset_x, offset_y);
glTexCoord2f(0.0f, 0.0f); glVertex2f(offset_x, offset_y);
glEnd();
painter.endNativePainting();
}
void GLWidget::_init_shaders()
{
int f = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(f, 1, &p_s_fragment_shader, 0);
glCompileShader(f);
_shader_program = glCreateProgram();
glAttachShader(_shader_program, f);
glLinkProgram(_shader_program);
glUseProgram(_shader_program);
glUniform1i(glGetUniformLocation(_shader_program, "tex"), 0);
glUniform1f(glGetUniformLocation(_shader_program, "ImgHeight"), _frame->height());
glUniform1f(glGetUniformLocation(_shader_program, "chromaHeight_Half"), (_frame->height() / 2) / 2);
glUniform1f(glGetUniformLocation(_shader_program, "chromaWidth"), _frame->width() / 2);
}
glwidget.h:
#include <QtOpenGL/QGLWidget>
#include <QtGui/QImage>
#include <QPainter>
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
GLWidget(QWidget *parent = 0);
~GLWidget();
void paintEvent(QPaintEvent *event);
private:
void _init_shaders();
bool _checkShader(int n_shader_object);
QImage* _frame;
int _shader_program;
};
And here you can download the data file.
See Question&Answers more detail:
os