Масштабирование изображения (KeepAspectRatioByExpanding) через OpenGL - PullRequest
17 голосов
/ 26 января 2012

Я пытаюсь реализовать масштабирование изображения в OpenGL, используя только glTexCoord2f() и glVertex2f().

Позвольте мне объяснить: после загрузки QImage и отправки его в gpu с glTexImage2D() мне нужно выполнить операции масштабирования изображения на основе спецификации Qt . Qt определяет эти 3 операции (см. Изображение ниже):

enter image description here

Я думаю, что это единственный способ сделать это, поскольку мое приложение представляет собой плагин 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;
};

А здесь вы можете скачать файл данных .

Ответы [ 2 ]

7 голосов
/ 26 января 2012

Просто сделайте то же самое, что вы сделали с 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))

В вашем случае вам просто нужно умножить получающиеся координаты текстуры на вашу ширину / высоту.

5 голосов
/ 02 февраля 2012

Вы можете просто скопировать ветку «сохранить соотношение сторон» (при условии, что она работает) и просто перевернуть знак сравнения коэффициентов, т.е.:

if (ratiox > ratioy)

становится

if (ratiox <= ratioy)

Но я не уверен, что это на самом деле работает (вычисления отношения всегда меня раздражали - а у вас это сложно), и у меня нет Qt atm, поэтому я не могу попробовать. Но это должно сделать это. Обратите внимание, что изображение будет отцентрировано (не выровнено по левому краю, как на вашем изображении), но это можно довольно легко исправить.

EDIT

Вот исходный код, который работает в приложении 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 (или height) и наоборот).

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. Но если есть какие-либо опечатки, исправить их будет довольно просто.

...