通过 OpenGL 进行图像缩放 (KeepAspectRatioByExpanding)
我正在尝试仅使用 glTexCoord2f()
和 glVertex2f()
在 OpenGL 中实现图像缩放。
让我解释一下:加载 QImage
并使用 glTexImage2D()
将其发送到 GPU 后,我必须根据 Qt 规范执行图像缩放操作 。 Qt 定义了这 3 个操作(见下图):
我认为这是唯一的方法,因为我的应用程序是一个 Qt 插件,此任务需要在类的 paint()
方法内完成。 IgnoreAspectRatio
操作非常简单,并且现在正在运行。 KeepAspectRatio 最初给我带来了一些麻烦,但现在它也可以工作了。不幸的是,KeepAspectRatioByExpanding
让我头疼。
我正在分享到目前为止我所做的事情,感谢您在这个问题上的帮助:
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:< /strong>
#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\n"
"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;
};
在这里你可以下载数据文件。
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\n"
"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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
只需执行与 KeepAspectRatio 相同的数学运算,但这次保留高度而不是宽度。您将获得大于 1 的宽度,因此您必须使用它的倒数作为纹理坐标。
标准化坐标是/否并不重要,您只需为纹理坐标添加这些因素即可。为此,我假设图像填充整个纹理(从 (0,0) 到 (1,1);这样可以轻松地将值与宽度/高度相乘)。纹理环绕必须关闭。
默认纹理坐标:
长宽比:
KeepAspectRatio (从内部触摸):
KeepAspectRatioByExpanding (从外部触摸;只需将
max()
替换为min()
):对于您的情况,您只需将生成的纹理坐标乘以宽度/高度即可。
Simply do the same math you did with KeepAspectRatio, but this time keep the height instead of the width. You'll get a width bigger than 1, so you'll have to use the inverse of it for your texture coordinate.
Normalized coordinates yes/no won't matter, you just have to add these factors for the texture coordinates. For this to work I assume the image fills the whole texture (from (0,0) to (1,1); this makes it easy to multiply the values with your width/height). Texture wrapping has to be off.
Default texture coordinates:
Aspect ratios:
KeepAspectRatio (touch from the inside):
KeepAspectRatioByExpanding (touch from outside; just replace
max()
withmin()
):For your case you'd just have to multiply the resulting texture coordinates with your width/height.
您可以简单地复制“保持纵横比”分支(前提是它正在工作),然后翻转比率比较符号,即:
变成
但我不确定它是否真的有效(比率计算一直困扰着我 - 而你的是棘手),并且没有 Qt atm,所以我无法尝试。但这应该可以做到。请注意,图像将居中(不像图像上那样左对齐),但这可以很容易地修复。
编辑:
这是在 GLUT 应用程序中工作的源代码(没有 QT,抱歉):
这是通过比较屏幕宽高比与图像宽高比来工作的。您实际上是在比较图像宽度与屏幕宽度的比率以及图像高度与屏幕高度的比率。这至少是可疑的,但不能说是错误的。
此外,标准化 OpenGL 坐标(提供简单的正交视图)的范围为 (-1, -1)(左下角)到 (1, 1)(右上角)。这意味着标准化宽度和高度均为 2,偏移量为 (-1, -1)。代码的其余部分应该是不言自明的。如果纹理被翻转(我使用通用纹理进行了测试,不确定它是否是直立的),只需更改相应方向的纹理坐标(将 0 替换为 ImgWidth(或高度),反之亦然)。
EDIT2:
使用像素坐标(不使用标准化的 OpenGL 坐标)更加简单。您可以使用:
请注意,两个版本的代码都使用 NPOT 纹理。要调整代码以适合您的对象,可以执行以下操作:
由于我没有 QT,因此无法保证最后一个代码片段没有错误。但如果有任何拼写错误,修复它们应该相当简单。
You can simply copy "keep aspect ratio" branch (provided that it is working), and just flip the ratio comparison sign, i.e.:
becomes
But i'm not sure it is actually working (ratio calculations had always bugged me - and yours is tricky), and don't have Qt atm so I can't try. But that should do it. Note that the image will be centered (not left-aligned as on your image), but that can be fixed pretty easily.
EDIT:
Here is source code that works in GLUT application (no QT, sorry):
This works by comparing screen aspect ratio to image aspect ratio. You are actually comparing ratios of image width to screen width with image height to screen height. That is suspicious at least, not to say wrong.
Also, normalized OpenGL coordinates (provided a simple orthogonal view) are in range (-1, -1) for lower-left corner to (1, 1) for upper right. That means normalized width and height are both 2, and offset is (-1, -1). The rest of the code should be self-explanatory. In case texture is flipped (I tested with kind of generic texture, not sure if it was upright), just change texture coordinates in the respective direction (swap 0s for ImgWidth (or height) and vice versa).
EDIT2:
Using pixel coordinates (not using normalized OpenGL coordinates) is even simpler. You can use:
Note that both versions of the code do use NPOT textures. To adapt the code to fit into your object, one would do something like this:
Can't guarantee this last code snippet is error-free since I don't have QT. But in case there are any typos, it should be rather straightforward to fix them.