通过OpenGL进行图像缩放(KeepAspectRatioByExpanding)

我正在尝试仅使用glTexCoord2f()glVertex2f()在OpenGL中实现图像缩放

让我解释一下:在加载QImage并使用glTexImage2D()将其发送到gpu之后,我必须根据Qt的规范执行图像缩放操作 。 Qt定义了这3个操作(见下图):

在此处输入图像描述

我认为这是唯一的方法,因为我的应用程序是一个Qt插件,这个任务需要在类的paint()方法中完成。 IgnoreAspectRatio操作非常简单,现在正在运行。 KeepAspectRatio最初给了我一些麻烦,但现在它也在工作。 不幸的是, KeepAspectRatioByExpanding令我头疼

我正在分享我到目前为止所做的工作,感谢您对此问题的帮助:

main.cpp中:

 #include "oglWindow.h" #include  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  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  #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  #include  #include  #else #include  #endif #include "glwidget.h" #include  #include  #include  #include  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(ty / 4.0);" " float CrY = ImgHeight + chromaHeight_Half + floor(ty / 4.0);" " float CbCrX = floor(tx / 2.0) + chromaWidth * floor(mod(ty, 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:" <type() != QPaintEngine::OpenGL && painter.paintEngine()->type() != QPaintEngine::OpenGL2) ? "disabled" : "enabled"); QGLContext* context = const_cast(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:" <width() << "x" <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  #include  #include  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; }; 

在这里,您可以下载数据文件

你可以简单地复制“保持纵横比”分支(假设它正常工作),只需翻转比率比较符号,即:

 if (ratiox > ratioy) 

 if (ratiox <= ratioy) 

但我不确定它是否真的有效(比率计算总是让我感到烦恼 - 你的很棘手),而且没有Qt atm所以我无法尝试。 但那应该这样做。 请注意,图像将居中(不像图像上那样左对齐),但可以很容易地修复。

编辑

这是在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 } 

这通过比较屏幕宽高比和图像宽高比来工作。 实际上,您正在将图像宽度与屏幕宽度的比率与图像高度与屏幕高度进行比较。 这至少是可疑的,更不用说错了。

此外, 标准化的 OpenGL坐标(提供简单的正交视图)在左下角的范围(-1,-1)到右上角的(1,1)。 这意味着标准化的宽度和高度都是2,偏移量是(-1,-1)。 其余代码应该是不言自明的。 如果纹理被翻转(我使用通用纹理测试,不确定它是否是直立的),只需改变相应方向的纹理坐标(交换0表示ImgWidth(或高度),反之亦然)。

编辑2

使用像素坐标(不使用标准化的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::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,所以无法保证最后一段代码片段没有错误。 但是如果有任何拼写错误,修复它们应该是相当简单的。

只需使用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(从外部触摸;只需用min()替换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)) 

对于您的情况,您只需将生成的纹理坐标与您的宽度/高度相乘。