通过OpenGL实现图像缩放(KeepAspectRatioByExpanding) 转载文章
我正在尝试仅使用OpenGL glVertex2f()
.和OpenGL在中实现图像缩放
让我解释一下:加载QImage
并使用glTexImage2D()
将其发送到图形处理器后,我必须根据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:
#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;
};
,在这里你可以 。
只需执行与KeepAspectRatio相同的数学运算,但这次保留高度而不是宽度。你会得到一个大于1的宽度,所以你必须使用它的倒数作为你的纹理坐标。
归一化坐标是/否无关紧要,你只需要为纹理坐标添加这些因子。为此,我假设图像填充了整个纹理(从(0,0)到(1,1);这使得将值与宽度/高度相乘很容易)。必须禁用纹理包裹。
默认纹理坐标:
(0,0)-(1,0)
| |
(0,1)-(1,1)
纵横比:
tex_ar = tex_width / tex_height; // aspect ratio for the texture being used
scr_ar = scr_width / scr_height; // aspect ratio for the quad being drawn
KeepAspectRatio (从内部触摸):
(0, 0)-----------------------(max(1, scr_ar / tex_ar), 0)
| |
(0, max(1, tex_ar / scr_ar))-(max(1, scr_ / tex_ar), max(1, tex_ar / scr_ar))
KeepAspectRatioByExpanding (从外部触摸;只需将max()
替换为min()
):
(0, 0)-----------------------(min(1, scr_ar / tex_ar), 0)
| |
(0, min(1, tex_ar / scr_ar))-(min(1, scr_ / tex_ar), min(1, tex_ar / scr_ar))
对于您的情况,您只需将生成的纹理坐标与您的宽度/高度相乘。
你可以简单的复制"keep aspect ratio“分支(只要它正常工作),并且只需要翻转比率比较符号,即:
if (ratiox > ratioy)
变成了
if (ratiox <= ratioy)
但我不确定它是否真的有效(比率计算一直困扰着我-你的计算很棘手),而且没有Qt自动取款机,所以我不能尝试。但这应该就够了。请注意,图像将居中(而不是像您的图像那样左对齐),但这很容易修复。
编辑
以下是在GLUT应用程序中工作的源代码(没有QT,抱歉):
static void DrawObject(void)
{
int img_width = 1280;//_frame->width();
int img_height = 720;//_frame->height();
GLfloat offset_x = -1;
GLfloat offset_y = -1;
int p_viewport[4];
glGetIntegerv(GL_VIEWPORT, p_viewport); // don't have QT :'(
GLfloat gl_width = p_viewport[2];//width(); // GL context size
GLfloat gl_height = p_viewport[3];//height();
int n_mode = 0;
switch(n_mode) {
case 0: // KeepAspectRatioByExpanding
{
float ratioImg = float(img_width) / img_height;
float ratioScreen = gl_width / gl_height;
if(ratioImg < ratioScreen) {
gl_width = 2;
gl_height = 2 * ratioScreen / ratioImg;
} else {
gl_height = 2;
gl_width = 2 / ratioScreen * ratioImg;
}
// calculate image size
}
break;
case 1: // IgnoreAspectRatio
gl_width = 2;
gl_height = 2;
// OpenGL normalized coordinates are -1 to +1 .. hence width (or height) = +1 - (-1) = 2
break;
case 2: // KeepAspectRatio
{
float ratioImg = float(img_width) / img_height;
float ratioScreen = gl_width / gl_height;
if(ratioImg > ratioScreen) {
gl_width = 2;
gl_height = 2 * ratioScreen / ratioImg;
} else {
gl_height = 2;
gl_width = 2 / ratioScreen * ratioImg;
}
// calculate image size
offset_x = -1 + (2 - gl_width) * .5f;
offset_y = -1 + (2 - gl_height) * .5f;
// center on screen
}
break;
}
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
// just simple ortho view, no fancy transform ...
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(offset_x, offset_y);
glTexCoord2f(ImgWidth, 0);
glVertex2f(offset_x + gl_width, offset_y);
glTexCoord2f(ImgWidth, ImgHeight);
glVertex2f(offset_x + gl_width, offset_y + gl_height);
glTexCoord2f(0, ImgHeight);
glVertex2f(offset_x, offset_y + gl_height);
glEnd();
// draw a single quad
}
这是通过比较screen纵横比和图像纵横比来实现的。您实际上是在比较图像宽度与屏幕宽度的比率与图像高度与屏幕高度的比率。这至少是可疑的,不能说是错误的。
此外,规范化的OpenGL坐标(提供了一个简单的正交视图)在左下角的范围(-1,-1)到右上角的(1,1)范围内。这意味着规格化的宽度和高度都是2,而偏移是(-1,-1)。代码的其余部分应该是自解释的。在纹理翻转的情况下(我测试了一种通用纹理,不确定它是否直立),只需在相应方向上更改纹理坐标(将0替换为ImgWidth (或ImgWidth),反之亦然)。
EDIT2
使用像素坐标(不使用标准化的OpenGL坐标)甚至更简单。您可以使用:
static void DrawObject(void)
{
int img_width = 1280;//_frame->width();
int img_height = 720;//_frame->height();
GLfloat offset_x = 0;
GLfloat offset_y = 0;
int p_viewport[4];
glGetIntegerv(GL_VIEWPORT, p_viewport);
GLfloat gl_width = p_viewport[2];//width(); // GL context size
GLfloat gl_height = p_viewport[3];//height();
int n_mode = 0;
switch(n_mode) {
case 0: // KeepAspectRatioByExpanding
{
float ratioImg = float(img_width) / img_height;
float ratioScreen = gl_width / gl_height;
if(ratioImg < ratioScreen)
gl_height = gl_width / ratioImg;
else
gl_width = gl_height * ratioImg;
// calculate image size
}
break;
case 1: // IgnoreAspectRatio
break;
case 2: // KeepAspectRatio
{
float ratioImg = float(img_width) / img_height;
float ratioScreen = gl_width / gl_height;
GLfloat orig_width = gl_width;
GLfloat orig_height = gl_height;
// remember those to be able to center the quad on screen
if(ratioImg > ratioScreen)
gl_height = gl_width / ratioImg;
else
gl_width = gl_height * ratioImg;
// calculate image size
offset_x = 0 + (orig_width - gl_width) * .5f;
offset_y = 0 + (orig_height - gl_height) * .5f;
// center on screen
}
break;
}
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(-1, -1, 0);
glScalef(2.0f / p_viewport[2], 2.0f / p_viewport[3], 1.0);
// just simple ortho view for vertex coordinate to pixel matching
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(offset_x, offset_y);
glTexCoord2f(img_width, 0);
glVertex2f(offset_x + gl_width, offset_y);
glTexCoord2f(img_width, img_height);
glVertex2f(offset_x + gl_width, offset_y + gl_height);
glTexCoord2f(0, img_height);
glVertex2f(offset_x, offset_y + gl_height);
glEnd();
// draw a single quad
}
请注意,这两个版本的代码都使用NPOT纹理。要调整代码以适合您的对象,您可以这样做:
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);
/* Initialize shaders and execute them */
_init_shaders();
int img_width = _frame->width();
int img_height = _frame->height();
GLfloat offset_x = 0;
GLfloat offset_y = 0;
GLfloat gl_width = width(); // GL context size
GLfloat gl_height = height();
qDebug() << "paint(): gl_width:" << gl_width << " gl_height:" << gl_height <<
" img:" << _frame->width() << "x" << _frame->height();
int fill_mode = 0;
switch(fill_mode) {
case 0: // KeepAspectRatioByExpanding
{
float ratioImg = float(img_width) / img_height;
float ratioScreen = gl_width / gl_height;
if(ratioImg < ratioScreen)
gl_height = gl_width / ratioImg;
else
gl_width = gl_height * ratioImg;
// calculate image size
}
break;
case 1: // IgnoreAspectRatio
break;
case 2: // KeepAspectRatio
{
float ratioImg = float(img_width) / img_height;
float ratioScreen = gl_width / gl_height;
GLfloat orig_width = gl_width;
GLfloat orig_height = gl_height;
// remember those to be able to center the quad on screen
if(ratioImg > ratioScreen)
gl_height = gl_width / ratioImg;
else
gl_width = gl_height * ratioImg;
// calculate image size
offset_x = 0 + (orig_width - gl_width) * .5f;
offset_y = 0 + (orig_height - gl_height) * .5f;
// center on screen
}
break;
}
glDisable(GL_CULL_FACE); // might cause problems if enabled
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(offset_x, offset_y);
glTexCoord2f(img_width, 0);
glVertex2f(offset_x + gl_width, offset_y);
glTexCoord2f(img_width, img_height);
glVertex2f(offset_x + gl_width, offset_y + gl_height);
glTexCoord2f(0, img_height);
glVertex2f(offset_x, offset_y + gl_height);
glEnd();
// draw a single quad
painter.endNativePainting();
}
我不能保证最后的代码片段是没有错误的,因为我没有QT。但是如果有任何拼写错误,修复它们应该是相当简单的。