ELEMENTOS CONFIGURADORES DEL RITMO AUDIOVISUAL
Capítulo 6. El guion
6.8. La elipsis: el generador privilegiado
6.8.1. Stockholm: La elipsis en 6 actos
Blitter Function, RAW Texture Loading
This tutorial was srcinally written by Andreas Löffler. He also wrote all of the srcinal HTML for the tutorial. A few days later Rob Fletcher emailed me an Irix version of lesson 29. In his version he rewrote most of the code. So I ported Rob's Irix / GLUT code to Visual C++ / Win32. I then modified the message loop code, and the fullscreen code. When the program is minimized it should use 0% of the CPU (or close to). When switching to and from fullscreen mode, most of the problems should be gone (screen not restoring properly, messed up display, etc).
Andreas tutorial is now better than ever. Unfortunately, the code has been modifed quite a bit, so all of the HTML has been rewritten by myself. Huge Thanks to Andreas for getting the ball rolling, and working his butt off to make a killer tutorial. Thanks to Rob for the modifications!
Lets begin... We create a device mode structure called DMsaved. We will use this structure to store information about the users default desktop resolution, color depth, etc., before we switch to fullscreen mode. More on this later! Notice we only allocate enough storage space for one texture (texture[1]).
#include <windows.h>// Header File For Windows
#include <gl\gl.h>// Header File For The OpenGL32 Library
#include <gl\glu.h>// Header File For The GLu32 Library
#include <stdio.h>// Header File For File Operation Needed HDC hDC=NULL;// Private GDI Device Context
HGLRC hRC=NULL; // Permanent Rendering Context HWND hWnd=NULL; // Holds Our Window Handle
HINSTANCE hInstance = NULL;// Holds The Instance Of The Application bool keys[256];// Array Used For The Keyboard Routine
bool active=TRUE;// Window Active Flag Set To TRUE By Default
bool fullscreen=TRUE;// Fullscreen Flag Set To Fullscreen Mode By Default DEVMODE DMsaved;// Saves The Previous Screen Settings (NEW)
GLfloat xrot;// X Rotation GLfloat yrot;// Y Rotation GLfloat zrot;// Z Rotation
GLuint texture[1];// Storage For 1 Texture
Now for the fun stuff. We create a structure called TEXTURE_IMAGE. The structure contains information about our images width, height, and format (bytes per pixel). data is a pointer to unsigned char. Later on data will point to our image data.
typedef struct Texture_Image {
int width;// Width Of Image In Pixels int height;// Height Of Image In Pixels int format;// Number Of Bytes Per Pixel unsigned char *data;// Texture Data } TEXTURE_IMAGE;
We then create a pointer called P_TEXTURE_IMAGE to the TEXTURE_IMAGE data type. The variables t1 and t2 are of type P_TEXTURE_IMAGE where P_TEXTURE_IMAGE is a redefined type of pointer to TEXTURE_IMAGE.
Lesson 29 – Blitter Function, RAW Texture Loading 186
typedef TEXTURE_IMAGE *P_TEXTURE_IMAGE;// A Pointer To The Texture Image Data Type P_TEXTURE_IMAGE t1;// Pointer To The Texture Image Data Type
P_TEXTURE_IMAGE t2;// Pointer To The Texture Image Data Type
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);// Declaration For WndProc
Below is the code to allocate memory for a texture. When we call this code, we pass it the width, height and bytes per pixel information of the image we plan to load. ti is a pointer to our TEXTURE_IMAGE data type. It's given a NULL value. c is a pointer to unsigned char, it is also set to NULL.
// Allocate An Image Structure And Inside Allocate Its Memory Requirements P_TEXTURE_IMAGE AllocateTextureBuffer( GLint w, GLint h, GLint f)
{
P_TEXTURE_IMAGE ti=NULL;// Pointer To Image Struct unsigned char *c=NULL;// Pointer To Block Memory For Image
Here is where we allocate the memory for our image structure. If everything goes well, ti will point to the allocated memory.
After allocating the memory, and checking to make sure ti is not equal to NULL, we can fill the structure with the image attributes.
First we set the width (w), then the height (h) and lastly the format (f). Keep in mind format is bytes per pixel.
ti = (P_TEXTURE_IMAGE)malloc(sizeof(TEXTURE_IMAGE));// One Image Struct Please if( ti != NULL ) {
ti->width = w;// Set Width ti->height = h;// Set Height ti->format = f;// Set Format
Now we need to allocate memory for the actual image data. The calculation is easy! We multiply the width of the image (w) by the height of the image (h) then multiply by the format (f - bytes per pixel).
c = (unsigned char *)malloc( w * h * f);
We check to see if everything went ok. If the value in c is not equal to NULL we set the data variable in our structure to point to the newly allocated memory.
If there was a problem, we pop up an error message on the screen letting the user know that the program was unable to allocate memory for the texture buffer. NULL is returned.
if ( c != NULL ) { ti->data = c;
} else {
MessageBox(NULL,"Could Not Allocate Memory For A Texture Buffer","BUFFER ERROR",MB_OK | MB_ICONINFORMATION);
return NULL;
} }
If anything went wrong when we were trying to allocate memory for our image structure, the code below would pop up an error message and return NULL.
If there were no problems, we return ti which is a pointer to our newly allocated image structure. Whew... Hope that all made sense.
else {
MessageBox(NULL,"Could Not Allocate An Image Structure","IMAGE STRUCTURE ERROR",MB_OK | MB_ICONINFORMATION);
return NULL;
}
return ti;// Return Pointer To Image Struct }
When it comes time to release the memory, the code below will deallocate the texture buffer and then free the image structure. t is a pointer to the TEXTURE_IMAGE data structure we want to deallocate.
// Free Up The Image Data
void DeallocateTexture( P_TEXTURE_IMAGE t ) {
if(t) {
if(t->data) {
free(t->data); // Free Its Image Buffer }
free(t); // Free Itself }
}
Now we read in our .RAW image. We pass the filename and a pointer to the image structure we want to load the image into. We
Lesson 29 – Blitter Function, RAW Texture Loading 187
set up our misc variables, and then calculate the size of a row. We figure out the size of a row by multiplying the width of our image by the format (bytes per pixel). So if the image was 256 pixels wide and there were 4 bytes per pixel, the width of a row would be 1024 bytes. We store the width of a row in stride.
We set up a pointer (p), and then attempt to open the file.
// Read A .RAW File In To The Allocated Image Buffer Using data In The Image Structure Header.
// Flip The Image Top To Bottom. Returns 0 For Failure Of Read, Or Number Of Bytes Read.
int ReadTextureData ( char *filename, P_TEXTURE_IMAGE buffer) {
FILE *f;
int i,j,k,done=0;
int stride = buffer->width * buffer->format;// Size Of A Row (Width * Bytes Per Pixel) unsigned char *p = NULL;
f = fopen(filename, "rb");// Open "filename" For Reading Bytes if( f != NULL )// If File Exists
{
If the file exists, we set up the loops to read in our texture. i starts at the bottom of the image and moves up a line at a time. We start at the bottom so that the image is flipped the right way. .RAW images are stored upside down. We have to set our pointer now so that the data is loaded into the proper spot in the image buffer. Each time we move up a line (i is decreased) we set the pointer to the start of the new line. data is where our image buffer starts, and to move an entire line at a time in the buffer, multiply i by stride. Remember that stride is the length of a line in bytes, and i is the current line. So by multiplying the two, we move an entire line at a time.
The j loop moves from left (0) to right (width of line in pixels, not bytes).
for( i = buffer->height-1; i >= 0 ; i-- )// Loop Through Height (Bottoms Up - Flip Image) {
p = buffer->data + (i * stride );
for ( j = 0; j < buffer->width ; j++ )// Loop Through Width {
The k loop reads in our bytes per pixel. So if format (bytes per pixel) is 4, k loops from 0 to 2 which is bytes per pixel minus one (format-1). The reason we subtract one is because most raw images don't have an alpha value. We want to make the 4th byte our alpha value, and we want to set the alpha value manually.
Notice in the loop we al so increase the pointer (p) and a variable called done. More about done later.
the line inside the loop reads a character from our file and stores it in the texture buffer at our current pointer location. If our image has 4 bytes per pixel, the first 3 bytes will be read from the .RAW file (format-1), and the 4th byte will be manually set to 255. After we set the 4th byte to 255 we increase the pointer location by one so that our 4th byte is not overwritten with the next byte in the file.
After a all of the bytes have been read in per pixel, and all of the pixels have been read in per row, and all of the rows have been read in, we are done! We can close the file.
for ( k = 0 ; k < buffer->format-1 ; k++, p++, done++ ) {
*p = fgetc(f);// Read Value From File And Store In Memory }
*p = 255; p++;// Store 255 In Alpha Channel And Increase Pointer }
}
fclose(f);// Close The File }
If there was a problem opening the file (does not exist, etc), the code below will pop up a message box letting the user know that the file could not be opened.
The last thing we do is return done. If the file couldn't be opened, done will equal 0. If everything went ok, done should equal the number of bytes read from the file. Remember, we were increasing done every time we read a byte in the loop above (k loop).
else // Otherwise {
MessageBox(NULL,"Unable To Open Image File","IMAGE ERROR",MB_OK | MB_ICONINFORMATION);
}
return done;// Returns Number Of Bytes Read In }
This shouldn't need explaining. By now you should know how to build a texture. tex is the pointer to the TEXTURE_IMAGE structure that we want to use. We build a linear filtered texture. In this example, we're building mipmaps (smoother looking). We pass the width, height and data just like we would if we were using glaux, but this time we get the information from the selected TEXTURE_IMAGE structure.
void BuildTexture (P_TEXTURE_IMAGE tex) {
glGenTextures(1, &texture[0
glBindTexture(GL_TEXTURE_2D, texture[0
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
Lesson 29 – Blitter Function, RAW Texture Loading 188
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex->width, tex->height, GL_RGBA, GL_UNSIGNED_BYTE, tex->data);
}
Now for the blitter code :) The blitter code is very powerful. It lets you copy any section of a (src) texture and paste it into a destination (dst) texture. You can combine as many textures as you want, you can set the alpha value used for blending, and you can select whether the two i mages blend together or cancel eachother out.
src is the TEXTURE_IMAGE structure to use as the source image. dst is the TEXTURE_IMAGE structure to use for the destination image. src_xstart is where you want to start copying from on the x axis of the source image. src_ystart is where you want to start copying from on the y axis of the source image. src_width is the width in pixels of the area you want to copy from the source image. src_height is the height in pixels of the area you want to copy from the source image. dst_xstart and dst_ystart is where you want to place the copied pixels from the source image onto the destination image. If blend is 1, the two images will be blended. alpha sets how tranparent the copied image will be when it mapped onto the destination image. 0 is completely clear, and 255 is solid.
We set up all our misc loop variables, along with pointers for our source image (s) and destination image (d). We check to see if the alpha value is within range. If not, we clamp it. We do the same for the blend value. If it's not 0-off or 1-on, we clamp it.
void Blit( P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height,
int dst_xstart, int dst_ystart, int blend, int alpha) {
int i,j,k;
unsigned char *s, *d;// Source & Destination // Clamp Alpha If Value Is Out Of Range if( alpha > 255 ) alpha = 255;
if( alpha < 0 ) alpha = 0;
// Check For Incorrect Blend Flag Values if( blend < 0 ) blend = 0;
if( blend > 1 ) blend = 1;
Now we have to set up the pointers. The destination pointer is the location of the destination data plus the starting location on the destination images y axis (dst_ystart) * the destination images width in pixels * the destination images bytes per pixel (format).
This should give us the starting row for our destination image.
We do pretty much the same thing for the source pointer. The source pointer is the location of the source data plus the starting location on the source images y axis (src_ystart) * the source images width i n pixels * the source images bytes per pixel (format).
This should give us the starting row for our source image.
i loops from 0 to src_height which is the number of pixels to copy up and down from the source image.
d = dst->data + (dst_ystart * dst->width * dst->format);// Start Row - dst (Row * Width In Pixels * Bytes Per Pixel)
s = src->data + (src_ystart * src->width * src->format);// Start Row - src (Row * Width In Pixels * Bytes Per Pixel)
for (i = 0 ; i < src_height ; i++ )// Height Loop {
We already set the source and destination pointers to the correct rows in each image. Now we have to move to the correct location from left to right in each image before we can start blitting the data. We increase the location of the source pointer (s) by src_xstart which is the starting location on the x axis of t he source image times the source images bytes per pixel. This moves the source (s) pointer to the starting pixel location on the x axis (from left to right) on the source image.
We do the exact same thing for the destination pointer. We increase the location of the destination pointer (d) by dst_xstart which is the starting location on the x axis of the destination image multiplied by the destination images bytes per pixel (format). This moves the destination (d) pointer to the starting pixel location on the x axis (from left to right) on the destination image.
After we have calculated where in memory we want to grab our pixels from (s) and where we want to move them to (d), we start the j loop. We'll use the j loop to travel from left to right through the source image.
s = s + (src_xstart * src->format);// Move Through Src Data By Bytes Per Pixel d = d + (dst_xstart * dst->format);// Move Through Dst Data By Bytes Per Pixel for (j = 0 ; j < src_width ; j++ )// Width Loop
{
The k loop is used to go through all the bytes per pixel. Notice as k increases, our pointer s for the source and destination i mages also increase.
Inside the loop we check to see if blending is on or off. If blend is 1, meaning we should blend, we do some fancy math to calculate the color of our blended pixels. The destination value (d) will equal our source value (s) multiplied by our alpha value + our current destination value (d) times 255 minus the alpha value. The shift operator (>>8) keeps the value in a 0-255 range.
If blending is disabled (0), we copy the data from the source image directly into the destination image. No blending is done and the alpha value is ignored.
Lesson 29 – Blitter Function, RAW Texture Loading 189
for( k = 0 ; k < src->format ; k++, d++, s++)// "n" Bytes At A Time {
if (blend)// If Blending Is On
*d = ( (*s * alpha) + (*d * (255-alpha)) ) >> 8;// Multiply Src Data*alpha Add Dst Data*(255-alpha)
else // Keep in 0-255 Range With >> 8
*d = *s;// No Blending Just Do A Straight Copy }
}
d = d + (dst->width - (src_width + dst_xstart))*dst->format;// Add End Of Row s = s + (src->width - (src_width + src_xstart))*src->format;// Add End Of Row }
}
The InitGL() code has changed quite a bit. All of the code below is new. We start off by allocating enough memory to hold a 256x256x4 Bytes Per Pixel Image. t1 will point to the allocated ram if everything went well.
After allocating memory for our image, we attempt to load the image. We pass ReadTextureData() the name of the file we wish to open, along with a pointer to our Image Structure (t1).
If we were unable to load the .RAW image, a message box will pop up on the screen to let the user know there was a problem loading the texture.
We then do the same thing for t2. We allocate memory, and attempt to read in our second .RAW image. If anything goes wrong we pop up a message box.
int InitGL(GLvoid)// This Will Be Called Right After The GL Window Is Created {
t1 = AllocateTextureBuffer( 256, 256, 4 );// Get An Image Structure
if (ReadTextureData("Data/Monitor.raw",t1)==0)// Fill The Image Structure With Data { // Nothing Read?
MessageBox(NULL,"Could Not Read 'Monitor.raw' Image Data","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);
return FALSE;
}
t2 = AllocateTextureBuffer( 256, 256, 4 );// Second Image Structure
if (ReadTextureData("Data/GL.raw",t2)==0)// Fill The Image Structure With Data { // Nothing Read?
MessageBox(NULL,"Could Not Read 'GL.raw' Image Data","TEXTURE ERROR",MB_OK | MB_ICONINFORMATION);
return FALSE;
}
If we got this far, it's safe to assume the memory has been allocated and the images have been loaded. Now to use our Blit() command to merge the two images int o one.
We start off by passing Blit() t2 and t1, both point to our TEXTURE_IMAGE structures (t2 is the second image, t1 is the first image.
Then we have to tell blit where to start grabbing data from on the source image. If you load the source image into Adobe Photoshop or any other program capable of loading .RAW images you will see that the entire image is blank except for the top right corner. The top right has a picture of the ball with GL written on it. The bottom left corner of the image is 0,0. The top right of the image is the width of the image-1 (255), the height of the image-1 (255). Knowing that we only want to copy 1/4 of the src image (top right), we tell Blit() to start grabbing from 127,127 (center of our source image).
Next we tell blit how many pixels we want to copy from our source point to the right, and from our source point up. We want to grab a 1/4 chunk of our image. Our image is 256x256 pixels, 1/4 of that is 128x128 pixels. All of the source information i s done.
Blit() now knows that i t should copy from 127 on the x axis to 127+128 (255) on the x axis, and from 127 on the y axis to 127+128 (255) on the y axis.
So Blit() knows what to copy, and where to get the data from, but it doesn't know where to put the data once it's gotten it. We want to draw the ball with GL written on it in the middle our the monitor image. You find the center of the destination image (256x256) which is 128x128 and subtract half the width and height of the source image (128x128) which is 64x64. So (128-64) x (128-64) gives us a starting location of 64,64.
Last thing to do is tell our blitter routine we want to blend the two image (A one means blend, a zero means do not blend), and how much to blend the images. If the last value is 0, we blend the images 0%, meaning anything we copy will replace what was already there. If we use a value of 127, the two images blend together at 50%, and if you use 255, the image you are copying will be completely transparent and will not show up at all.
The pixels are copied from image2 (t2) to image1 (t1). The mixed image will be stored in t1.
// Image To Blend In, Original Image, Src Start X & Y, Src Width & Height, Dst Location X & Y, Blend Flag, Alpha Value
Blit(t2,t1,127,127,128,128,64,64,1,127);// Call The Blitter Routine
After we have mixed the two images (t1 and t2) together, we build a texture from the combined images (t1).
Lesson 29 – Blitter Function, RAW Texture Loading 190
After the texture has been created, we can deallocate the memory holding our two TEXTURE_IMAGE structures.
The rest of the code is pretty standard. We enable texture mapping, depth testing, etc.
BuildTexture (t1);// Load The Texture Map Into Texture Memory DeallocateTexture( t1 );// Clean Up Image Memory Because Texture Is DeallocateTexture( t2 );// In GL Texture Memory Now
glEnable(GL_TEXTURE_2D);// Enable Texture Mapping glShadeModel(GL_SMOOTH);// Enables Smooth Color Shading
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// This Will Clear The Background Color To Black
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// This Will Clear The Background Color To Black