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

发布于 2024-12-29 09:26:03 字数 9416 浏览 2 评论 0原文

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

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

在此处输入图像描述

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

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

ma​​in.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):

enter image description here

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

苯莒 2025-01-05 09:26:03

只需执行与 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))

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

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:

(0,0)-(1,0)
|     |
(0,1)-(1,1)

Aspect ratios:

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 (touch from the inside):

(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 (touch from outside; just replace max() with 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))

For your case you'd just have to multiply the resulting texture coordinates with your width/height.

淡莣 2025-01-05 09:26:03

您可以简单地复制“保持纵横比”分支(前提是它正在工作),然后翻转比率比较符号,即:

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(或高度),反之亦然)。

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,因此无法保证最后一个代码片段没有错误。但如果有任何拼写错误,修复它们应该相当简单。

You can simply copy "keep aspect ratio" branch (provided that it is working), and just flip the ratio comparison sign, i.e.:

if (ratiox > ratioy)

becomes

if (ratiox <= ratioy)

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):

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
}

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:

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
}

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:

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();
}

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文