首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >通过OpenGL实现图像缩放(KeepAspectRatioByExpanding)

通过OpenGL实现图像缩放(KeepAspectRatioByExpanding)
EN

Stack Overflow用户
提问于 2012-01-26 06:08:56
回答 2查看 10.5K关注 0票数 17

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

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

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

我正在分享我到目前为止所做的事情,我感谢你在这个问题上的帮助:

main.cpp:

代码语言:javascript
复制
#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:

代码语言:javascript
复制
#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:

代码语言:javascript
复制
#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:

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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;
};

,在这里你可以

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2012-02-03 00:30:43

你可以简单的复制"keep aspect ratio“分支(只要它正常工作),并且只需要翻转比率比较符号,即:

代码语言:javascript
复制
if (ratiox > ratioy)

变成了

代码语言:javascript
复制
if (ratiox <= ratioy)

但我不确定它是否真的有效(比率计算一直困扰着我-你的计算很棘手),而且没有Qt自动取款机,所以我不能尝试。但这应该就够了。请注意,图像将居中(而不是像您的图像那样左对齐),但这很容易修复。

编辑

以下是在GLUT应用程序中工作的源代码(没有QT,抱歉):

代码语言:javascript
复制
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坐标)甚至更简单。您可以使用:

代码语言:javascript
复制
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纹理。要调整代码以适合您的对象,您可以这样做:

代码语言:javascript
复制
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。但是如果有任何拼写错误,修复它们应该是相当简单的。

票数 5
EN

Stack Overflow用户

发布于 2012-01-26 06:42:57

只需执行与KeepAspectRatio相同的数学运算,但这次保留高度而不是宽度。你会得到一个大于1的宽度,所以你必须使用它的倒数作为你的纹理坐标。

归一化坐标是/否无关紧要,你只需要为纹理坐标添加这些因子。为此,我假设图像填充了整个纹理(从(0,0)到(1,1);这使得将值与宽度/高度相乘很容易)。必须禁用纹理包裹。

默认纹理坐标:

代码语言:javascript
复制
(0,0)-(1,0)
|     |
(0,1)-(1,1)

纵横比:

代码语言:javascript
复制
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 (从内部触摸):

代码语言:javascript
复制
(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()):

代码语言:javascript
复制
(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))

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

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/9011108

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档