Lazy Foo' Productions


Stretching and Filters

Stretching and Filters screenshot

Last Updated: Aug 9th, 2012

There's nothing that requires us to have our vertex positions to be the same size as our texture. In this tutorial we'll give different quad sizes to stretch our texture and use filtering to control how our texture looks when stretched.
From LTexture.h
        void render( GLfloat x, GLfloat y, LFRect* clip = NULL, LFRect* stretch = NULL );
        /*
        Pre Condition:
         -A valid OpenGL context
         -Active modelview matrix
        Post Condition:
         -Translates to given position and renders the texture area mapped to a quad
         -If given texture clip is NULL, the full image is rendered
         -If a stretch area is given, texture area is scaled to the stretch area size
        Side Effects:
         -Binds member texture ID
        */
Our render() function now takes in an additional argument, a rectangle to define how much we want to stretch the rendered texture.

We give it a default value of NULL in case we don't want to stretch our texture.
From LTexture.cpp
void LTexture::render( GLfloat x, GLfloat y, LFRect* clip, LFRect* stretch )
{
    //If the texture exists
    if( mTextureID != 0 )
    {
        //Remove any previous transformations
        glLoadIdentity();

        //Texture coordinates
        GLfloat texTop = 0.f;
        GLfloat texBottom = (GLfloat)mImageHeight / (GLfloat)mTextureHeight;
        GLfloat texLeft = 0.f;
        GLfloat texRight = (GLfloat)mImageWidth / (GLfloat)mTextureWidth;

        //Vertex coordinates
        GLfloat quadWidth = mImageWidth;
        GLfloat quadHeight = mImageHeight;

        //Handle clipping
        if( clip != NULL )
        {
            //Texture coordinates
            texLeft = clip->x / mTextureWidth;
            texRight = ( clip->x + clip->w ) / mTextureWidth;
            texTop = clip->y / mTextureHeight;
            texBottom = ( clip->y + clip->h ) / mTextureHeight;

            //Vertex coordinates
            quadWidth = clip->w;
            quadHeight = clip->h;
        }
At the top of our render() function, our texture coordinates work the same as they did before.
From LTexture.cpp
        //Handle Stretching
        if( stretch != NULL )
        {
            quadWidth = stretch->w;
            quadHeight = stretch->h;
        }

        //Move to rendering point
        glTranslatef( x, y, 0.f );

        //Set texture ID
        glBindTexture( GL_TEXTURE_2D, mTextureID );

        //Render textured quad
        glBegin( GL_QUADS );
            glTexCoord2f(  texLeft,    texTop ); glVertex2f(       0.f,        0.f );
            glTexCoord2f( texRight,    texTop ); glVertex2f( quadWidth,        0.f );
            glTexCoord2f( texRight, texBottom ); glVertex2f( quadWidth, quadHeight );
            glTexCoord2f(  texLeft, texBottom ); glVertex2f(       0.f, quadHeight );
        glEnd();
    }
}
What we're doing differently is giving different vertex coordinates. If the function has a stretch rectangle, we have our quad dimensions to be equal to the size of the stretch rectangle. Now when our textured quad renders, it will stretch the texture to fit the new quad size.
From LUtil.cpp
//Version: 011

#include "LUtil.h"
#include <IL/il.h>
#include <IL/ilu.h>
#include "LTexture.h"

//Stretched texture
LTexture gStretchedTexture;

//Stretch size
LFRect gStretchRect = { 0.f, 0.f, SCREEN_WIDTH, SCREEN_HEIGHT };

//Texture filtering
GLenum gFiltering = GL_LINEAR;
At the top of LUtil.cpp we declare some global variables. "gStretchedTexture" is a texture that we're going to load that's 160x120 pixels in size. "gStretchRect" is the stretch rectangle that we're going to use to stretch the texture to the size of the screen.

"gFiltering" will control how our texture is filtered when rendered. The best way to explain how filtering works is through demonstration. We'll get to that in our key handler.
From LUtil.cpp
bool loadMedia()
{
    //Load and color key texture
    if( !gStretchedTexture.loadTextureFromFile( "11_stretching_and_filters/mini_opengl.png" ) )
    {
        printf( "Unable to load mini texture!\n" );
        return false;
    }

    return true;
}

void update()
{

}

void render()
{
    //Clear color buffer
    glClear( GL_COLOR_BUFFER_BIT );

    //Render texture stretched
    gStretchedTexture.render( 0.f, 0.f, NULL, &gStretchRect );

    //Update screen
    glutSwapBuffers();
}
In loadMedia() we load our texture and in render() we stretch it to the size of the screen.
From LUtil.cpp
void handleKeys( unsigned char key, int x, int y )
{
    //If q is pressed
    if( key == 'q' )
    {
        //Bind texture for modification
        glBindTexture( GL_TEXTURE_2D, gStretchedTexture.getTextureID() );

        //Toggle linear/nearest filtering
        if( gFiltering != GL_LINEAR )
        {
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

            gFiltering = GL_LINEAR;
        }
        else
        {
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
            glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );

            gFiltering = GL_NEAREST;
        }

        //Unbind texture
        glBindTexture( GL_TEXTURE_2D, NULL );
    }
}
In this tutorial we'll use key presses to toggle our texture filtering.

When the user presses q, the texture we loaded is bound so we can do operations on it. Even though we're outside of the LTexture class, as long as we have the texture ID we can do operations on the texture.

Then we toggle and set the filtering for the texture. These function calls should look familiar because we call them in our texture loading function. "GL_TEXTURE_MAG_FILTER" controls how the texture is filtered when the texture is stretched. The default we set when we load the texture is "GL_LINEAR". This means our texture blends the pixels when stretching it which results in this (on an ATI card):
linear

When "GL_TEXTURE_MAG_FILTER" is set to "GL_NEAREST", OpenGL just grabs the nearest texel value which results in a grainier look:
nearest

Note that we also set "GL_TEXTURE_MIN_FILTER" which controls how the texture is filtered when it's made smaller. "GL_TEXTURE_MAG_FILTER" and "GL_TEXTURE_MIN_FILTER" don't have to be the same value if you want different filters applied in different situations.

Finally after toggling the filtering, we unbind the texture.