title: Image scaling (KeepAspectRatioByExpanding) through OpenGL
body: <p>I'm trying to implement <strong>image scaling in OpenGL using only <code>glTexCoord2f()</code> and <code>glVertex2f()</code></strong>.</p>
  
  <p>Let me explain: after loading a <code>QImage</code> and sending it to the gpu with <code>glTexImage2D()</code> I have to <strong>perform image scaling operations based on Qt's specification</strong>. Qt defines these 3 operations (see image below):</p>
  
  <p><img src="https://i.stack.imgur.com/nd6st.png" alt="enter image description here"></p>
  
  <p>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 <code>paint()</code> method of the class. The <code>IgnoreAspectRatio</code> operation is pretty straight forward and it's working right now. The <code>KeepAspectRatio</code> gave me some trouble initially but now it's also working. Unfortunally, <strong><code>KeepAspectRatioByExpanding</code> is giving me headaches</strong>.</p>
  
  <p>I'm sharing what I've done so far and I appreciate your help on this issue:</p>
  
  <p><strong>main.cpp:</strong></p>
  
  <pre><code>#include "oglWindow.h"
  #include &lt;QtGui/QApplication&gt;
  
  int main(int argc, char *argv[])
  {
      QApplication a(argc, argv);
      oglWindow w;
      w.show();
      return a.exec();
  }
  </code></pre>
  
  <p><strong>oglWindow.cpp:</strong></p>
  
  <pre><code>#include "oglWindow.h"
  #include "glwidget.h"
  
  #include &lt;QGridLayout&gt;
  
  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()
  {
  }
  </code></pre>
  
  <p><strong>oglWindow.h:</strong></p>
  
  <pre><code>#ifndef oglWindow_H
  #define oglWindow_H
  
  #include &lt;QtGui/QMainWindow&gt;
  #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
  </code></pre>
  
  <p><strong>glwidget.cpp:</strong></p>
  
  <pre><code>#ifdef _MSC_VER
      #include &lt;windows.h&gt;
      #include &lt;GL/glew.h&gt;
      #include &lt;GL/gl.h&gt;  
  #else
      #include &lt;GL/gl.h&gt;
  #endif
  
  #include "glwidget.h"
  
  #include &lt;QDebug&gt;
  
  #include &lt;iostream&gt;
  #include &lt;fstream&gt;
  #include &lt;assert.h&gt;
  
  
  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() &lt;&lt; "&gt; 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() &lt;&lt; "&gt; 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() &lt;&lt; "&gt; 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-&gt;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() &lt;&lt; "&gt; GLWidget::paintEvent OpenGL:"  &lt;&lt; ((painter.paintEngine()-&gt;type() != QPaintEngine::OpenGL &amp;&amp;
                                         painter.paintEngine()-&gt;type() != QPaintEngine::OpenGL2) ? "disabled" : "enabled");   
  
      QGLContext* context = const_cast&lt;QGLContext *&gt;(QGLContext::currentContext());
      if (!context)
      {
          qDebug() &lt;&lt; "&gt; GLWidget::paintEvent !!! Unable to retrieve OGL context";
          return;
      }
      context-&gt;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() &lt;&lt; "&gt; GLWidget::paintEvent !!! glewInit failed with: " &lt;&lt; err;
          return;
      }
      if (!GLEW_VERSION_2_1)  // check that the machine supports the 2.1 API.
      {
          qDebug() &lt;&lt; "&gt; 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-&gt;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-&gt;width(), _frame-&gt;height() + _frame-&gt;height() / 2, 0,
              GL_LUMINANCE, GL_UNSIGNED_BYTE, _frame-&gt;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-&gt;width();
      int img_height = _frame-&gt;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() &lt;&lt; "paint(): gl_width:" &lt;&lt; gl_width &lt;&lt; " gl_height:" &lt;&lt; gl_height &lt;&lt;  
          " img:" &lt;&lt; _frame-&gt;width() &lt;&lt; "x" &lt;&lt; _frame-&gt;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 &gt; 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, &amp;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-&gt;height());
      glUniform1f(glGetUniformLocation(_shader_program, "chromaHeight_Half"), (_frame-&gt;height() / 2) / 2);
      glUniform1f(glGetUniformLocation(_shader_program, "chromaWidth"), _frame-&gt;width() / 2);   
  }
  </code></pre>
  
  <p><strong>glwidget.h</strong>:</p>
  
  <pre><code>#include &lt;QtOpenGL/QGLWidget&gt;
  #include &lt;QtGui/QImage&gt;
  #include &lt;QPainter&gt;
  
  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;
  };
  </code></pre>
  
  <p><strong>And here you can <a href="http://www.mediafire.com/?jqymgthd2vab8ub" rel="noreferrer">download the data file</a></strong>.</p>