Lazy Foo' Productions

SDL Forums external link SDL Tutorials Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Texture Blitting and Texture Padding

Texture Blitting and Texture Padding screenshot

Last Updated: Oct 4th, 2014

Blitting is the process of copying pixels from one image to another. In this tutorial, we're going to two halves of an image and combine them by blitting the pixel data. We'll also have to manually pad the pixel, since DevIL can't do it for us this time.
From LTexture.h
        void createPixels32( GLuint imgWidth, GLuint imgHeight );
        /*
        Pre Condition:
         -A valid OpenGL context
        Post Condition:
         -Frees existing texture data
         -Allocates 32bit pixel data for member pixels
        Side Effects:
         -None
        */

        void copyPixels32( GLuint* pixels, GLuint imgWidth, GLuint imgHeight );
        /*
        Pre Condition:
         -A valid OpenGL context
        Post Condition:
         -Copies given pixel data into member pixels
        Side Effects:
         -None
        */

        void padPixels32();
        /*
        Pre Condition:
         -Valid 32bit member pixels
        Post Condition:
         -Takes current member pixel data and gives it power of two dimensions
        Side Effects:
         -None
        */
Here are some new 32bit functions to do some new pixel operations. createPixels32() creates a blank set of pixels for us to use, copyPixels32() copies a set of external pixels, and padPixels32() pads the member pixels to be power of two.
From LTexture.h
        void createPixels8( GLuint imgWidth, GLuint imgHeight );
        /*
        Pre Condition:
         -A valid OpenGL context
        Post Condition:
         -Frees existing texture data
         -Allocates 8bit pixel data for member pixels
        Side Effects:
         -None
        */

        void copyPixels8( GLubyte* pixels, GLuint imgWidth, GLuint imgHeight );
        /*
        Pre Condition:
         -A valid OpenGL context
        Post Condition:
         -Copies given pixel data into member pixels
        Side Effects:
         -None
        */

        void padPixels8();
        /*
        Pre Condition:
         -Valid 8bit member pixels
        Post Condition:
         -Takes current member pixel data and gives it power of two dimensions
        Side Effects:
         -None
        */
Here's the 8bit versions of the previously mention functions.
From LTexture.h
        void blitPixels32( GLuint x, GLuint y, LTexture& destination );
        /*
        Pre Condition:
         -Available 32bit member and destination pixels
         -Valid blitting coordinates
        Post Condition:
         -Copies member pixels to destination member pixels at given location
         -Function will misbehave if invalid blitting coordinate or image dimensions
         are used
        Side Effects:
         -None
        */

        void blitPixels8( GLuint x, GLuint y, LTexture& destination );
        /*
        Pre Condition:
         -Available 8bit member and destination pixels
         -Valid blitting coordinates
        Post Condition:
         -Copies member pixels to destination member pixels at given location
         -Function will misbehave if invalid blitting coordinate or image dimensions
         are used
        Side Effects:
         -None
        */
Here are the 32bit and 8bit pixel blitting functions. They copy the image from the member pixels onto the destination LTexture's member pixels.
From LTexture.cpp
void LTexture::createPixels32( GLuint imgWidth, GLuint imgHeight )
{
    //Valid dimensions
    if( imgWidth > 0 && imgHeight > 0 )
    {
        //Get rid of any current texture data
        freeTexture();

        //Create pixels
        GLuint size = imgWidth * imgHeight;
        mPixels32 = new GLuint[ size ];

        //Copy pixel data
        mImageWidth = imgWidth;
        mImageHeight = imgHeight;
        mTextureWidth = mImageWidth;
        mTextureHeight = mImageWidth;

        //Set pixel formal
        mPixelFormat = GL_RGBA;
    }
}
The function createPixels32() is pretty simple. First it makes sure that a valid set of image dimensions were given. Then it frees any existing texture data because we don't want to create a conflict between new and previous texture data.

After that, we allocate the pixel data and set the LTexture attributes.
From LTexture.cpp
void LTexture::copyPixels32( GLuint* pixels, GLuint imgWidth, GLuint imgHeight )
{
    //Pixels have valid dimensions
    if( imgWidth > 0 && imgHeight > 0 )
    {
        //Get rid of any current texture data
        freeTexture();

        //Copy pixels
        GLuint size = imgWidth * imgHeight;
        mPixels32 = new GLuint[ size ];
        memcpy( mPixels32, pixels, size * 4 );

        //Copy pixel data
        mImageWidth = imgWidth;
        mImageHeight = imgHeight;
        mTextureWidth = mImageWidth;
        mTextureHeight = mImageWidth;

        //Set pixel format
        mPixelFormat = GL_RGBA;
    }
}
copyPixels32() works similarly to createPixels32() only it does a memcpy() on an external pixel pointer. Since this is RGBA pixel data with 4 bytes per pixel, the size of the memory being copied is number of pixels times four.
From LTexture.cpp
void LTexture::blitPixels32( GLuint x, GLuint y, LTexture& destination )
{
    //There are pixels to blit
    if( mPixels32 != NULL && destination.mPixels32 != NULL )
    {
        //Copy pixels rows
        GLuint* dPixels = destination.mPixels32;
        GLuint* sPixels = mPixels32;
        for( int i = 0; i < mImageHeight; ++i )
        {
            memcpy( &dPixels[ ( i + y ) * destination.mTextureWidth + x ], &sPixels[ i * mTextureWidth ], mImageWidth * 4 );
        }
    }
}
Before we go over padPixels32(), let's go over blitPixels32() and how pixel blitting works at a byte level.

First we make sure there are pixels to blit with. After that we have a call to memcpy() that copies "sPixels" (source pixels) to "dPixels" (destination pixels) row by row.

Now how do the equations in memcpy() work? Let's say we wanted to copy the pixels on the left to the pixels on the right at the bottom right corner:
pixels pixels

We're blitting the pixels in the top left corner so the offset is x = 1, y = 1. For the first iteration of the loop "i" will equal 0. This means for the first loop we'll get:
dPixels[ ( 0 + 1 ) * 4 + 1 ] and sPixels[ 0 * 3 ]

So the first row of 3 sPixels gets copied starting at dPixels[ 5 ] which is what you would expect.

For the second iteration of the loop i = 1, x = 1, and y = 1. This gives us:
dPixels[ ( 1 + 1 ) * 4 + 1 ] and sPixels[ 1 * 3 ]

So source pixels starts copying at 3 to destination pixels at 9. I'm sure you could figure out the last row.

Two things to note: 1) Remember that since it's 32bit pixels, we copy imageWidth * 4 bytes per pixels when copying pixel rows. 2) Also it's the texture width not the image width that we use to calculate how many pixels there are per row.
From LTexture.cpp
void LTexture::padPixels32()
{
    //If there are pixels to pad
    if( mPixels32 != NULL )
    {
        //Old texture attributes
        GLuint oTextureWidth = mTextureWidth;
        GLuint oTextureHeight = mTextureHeight;

        //Calculate power of two dimensions
        mTextureWidth = powerOfTwo( mImageWidth );
        mTextureHeight = powerOfTwo( mImageHeight );

        //The bitmap needs to change
        if( mTextureWidth != mImageWidth || mTextureHeight != mImageHeight )
        {
            //Allocate pixels
            GLuint size = mTextureWidth * mTextureHeight;
            GLuint* pixels = new GLuint[ size ];

            //Copy pixels rows
            for( int i = 0; i < mImageHeight; ++i )
            {
                memcpy( &pixels[ i * mTextureWidth ], &mPixels32[ i * oTextureWidth ], mImageWidth * 4 );
            }

            //Change pixels
            delete[] mPixels32;
            mPixels32 = pixels;
        }
    }
}
For padPixels32(), all we do is create 32bit pixel data that is power of two, blit the current pixel data onto the new pixel data, delete the old data and swap the pointers.
From LTexture.cpp
void LTexture::createPixels8( GLuint imgWidth, GLuint imgHeight )
{
    //Valid dimensions
    if( imgWidth > 0 && imgHeight > 0 )
    {
        //Get rid of any current texture data
        freeTexture();

        //Create pixels
        GLuint size = imgWidth * imgHeight;
        mPixels8 = new GLubyte[ size ];

        //Copy pixel data
        mImageWidth = imgWidth;
        mImageHeight = imgHeight;
        mTextureWidth = mImageWidth;
        mTextureHeight = mImageWidth;

        //Set pixel format
        mPixelFormat = GL_ALPHA;
    }
}

void LTexture::copyPixels8( GLubyte* pixels, GLuint imgWidth, GLuint imgHeight )
{
    //Pixels have valid dimensions
    if( imgWidth > 0 && imgHeight > 0 )
    {
        //Get rid of any current texture data
        freeTexture();

        //Copy pixels
        GLuint size = imgWidth * imgHeight;
        mPixels8 = new GLubyte[ size ];
        memcpy( mPixels8, pixels, size );

        //Copy pixel data
        mImageWidth = imgWidth;
        mImageHeight = imgHeight;
        mTextureWidth = mImageWidth;
        mTextureHeight = mImageWidth;

        //Set pixel format
        mPixelFormat = GL_ALPHA;
    }
}

void LTexture::padPixels8()
{
    //If there are pixels to pad
    if( mPixels8 != NULL )
    {
        //Old texture attributes
        GLuint oTextureWidth = mTextureWidth;
        GLuint oTextureHeight = mTextureHeight;

        //Calculate power of two dimensions
        mTextureWidth = powerOfTwo( mImageWidth );
        mTextureHeight = powerOfTwo( mImageHeight );

        //The bitmap needs to change
        if( mTextureWidth != mImageWidth || mTextureHeight != mImageHeight )
        {
            //Allocate pixels
            GLuint size = mTextureWidth * mTextureHeight;
            GLubyte* pixels = new GLubyte[ size ];

            //Copy pixels rows
            for( int i = 0; i < mImageHeight; ++i )
            {
                memcpy( &pixels[ i * mTextureWidth ], &mPixels8[ i * oTextureWidth ], mImageWidth );
            }

            //Change pixels
            delete[] mPixels8;
            mPixels8 = pixels;
        }
    }
}

void LTexture::blitPixels8( GLuint x, GLuint y, LTexture& destination )
{
    //There are pixels to blit
    if( mPixels8 != NULL && destination.mPixels8 != NULL )
    {
        //Copy pixels rows
        GLubyte* dPixels = destination.mPixels8;
        GLubyte* sPixels = mPixels8;
        for( int i = 0; i < mImageHeight; ++i )
        {
            memcpy( &dPixels[ ( i + y ) * destination.mTextureWidth + x ], &sPixels[ i * mTextureWidth ], mImageWidth );
        }
    }
}
The 8bit versions of these functions work the same except we're using 8bit pointers and memcopy() only copies one byte per pixel.
From LTexture.cpp
//Loaded textures
LTexture gLeft;
LTexture gRight;

//Generated combined texture
LTexture gCombined;
At the top of LUtil.cpp we declare the two textures we're going to load and the texture we're going to blit them to.
From LUtil.cpp
bool loadMedia()
{
    //Load left texture
    if( !gLeft.loadPixelsFromFile32( "22_texture_blitting_and_texture_padding/left.png" ) )
    {
        printf( "Unable to load left texture!\n" );
        return false;
    }

    //Load right texture
    if( !gRight.loadPixelsFromFile32( "22_texture_blitting_and_texture_padding/right.png" ) )
    {
        printf( "Unable to load right texture!\n" );
        return false;
    }

    //Create blank pixels
    gCombined.createPixels32( gLeft.imageWidth() + gRight.imageWidth(), gLeft.imageHeight() );
In loadMedia(), we load the two images as pixels and allocate pixel data large enough to hold both of them.
From LUtil.cpp
    //Blit images
    gLeft.blitPixels32( 0, 0, gCombined );
    gRight.blitPixels32( gLeft.imageWidth(), 0, gCombined );

    //Pad and create texture
    gCombined.padPixels32();
    gCombined.loadTextureFromPixels32();

    //Get rid of old textures
    gLeft.freeTexture();
    gRight.freeTexture();

    return true;
}
Then we blit the two images onto the blank pixels, pad the pixels so they're power of two, and create the texture.

With both the images blitted onto a new texture, we free the old images.
From LUtil.cpp
void render()
{
    //Clear color buffer
    glClear( GL_COLOR_BUFFER_BIT );
    glLoadIdentity();

    //Render combined texture
    gCombined.render( ( SCREEN_WIDTH - gCombined.imageWidth() ) / 2.f, ( SCREEN_HEIGHT - gCombined.imageHeight() ) / 2.f );

    //Update screen
    glutSwapBuffers();
}
And finally, we render the generated texture.