Масштабирование изображения (KeepAspectRatioByExpanding) через OpenGL
Я пытаюсь реализовать масштабирование изображения в OpenGL, используя только glTexCoord2f()
и glVertex2f()
.
позвольте мне объяснить: после загрузки QImage
и отправка его в gpu с glTexImage2D()
Я должен выполнение операций масштабирования изображений на основе спецификации Qt. Qt определяет эти 3 операции (см. изображение ниже):
Я думаю, что это единственный способ сделать это, так как мое приложение является плагином Qt, и эта задача должна быть сделано внутри paint()
метод класса. The IgnoreAspectRatio
операция довольно прямолинейна, и она работает прямо сейчас. The 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 : enablen"
"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 ответов
вы можете просто скопировать ветку "сохранить соотношение сторон" (при условии, что она работает) и просто перевернуть знак сравнения отношения, т. е.:
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). Остальная часть кодекса должна быть понятной. В случае, если текстура перевернута (я тестировал с видом общей текстуры, не уверен, что она была вертикальной), просто измените координаты текстуры в соответствующем направлении (swap 0s для ImgWidth (или height) и vice наоборот.)
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. Но в случае, если есть какие-либо опечатки, это должно быть довольно просто их исправить.
просто сделайте ту же математику, что и с 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))
для вашего случая вам просто нужно умножить полученные координаты текстуры на вашу ширину / высоту.