500 likes | 515 Views
Learn the process of reading bitmaps from files by extracting header information and byte data into memory storage effectively.
E N D
DirectDraw and BitmapsPart 3 CIS 487/587 Bruce R. Maxim UM-Dearborn
Reading Bitmap Files • The process of reading bit maps from files involves reading header information and then reading byte data into memory • The size of the memory region is determined the the color depth and screen dimensions from the header
Bitmap File Header Bitmap file header Bitmap info Palette if palettized Bitmap data RGB Pixels or Indexed data
Bitmap File Header typedef struct tag8BITMAPFILEHEADFER { WORD bfType; // file type = 0xD42 for .BMP DWORD bfSize; // file size in bytes WORD bfReserved; // must be 0 WORD bfReserved2; // must be 0 DWORD bfOffBits; // length in bytes of header } BITMAPFILEHEADER;
Bitmap Info Header typedef struct tagBITMAPINFOHEADER { DWORD biSize; // structure size in bytes LONG biWidth; // bitmap width LONG biHeight; // bitmap height (<0 upside down) WORD biPlanes; // # color planes WORD biBitCount; // # bits per pixel 1,4,8,16,24,32 DWORD biCompression; // compression type = BI_RGB for .bmp DWORD biSizeIMage; // image size in bytes LONG biXPelsPerMeter; // pixels per meter x-axis LONG biYPelsPerMeter; // pixels per meter y-axis DWORD biClrUsed; // # colors used in bitmap DWORD biClrImportant; // # important colors } BITMAPFILEHEADER;
Bitmap Info Section typedef struct BITMAP_FILE_TAG { BITMAPINFOHEADER bmiHeader; // header info RGBQUAD bmiColors[1]; // palette if one is used } BITMAPINFO;
Bitmap Data Area • It is possible that the bitmap image was written line by line to the file “upside down” • It is also the case that the bitmap palette (if it exists) is written in RGB Quad which the reverse normal palette entries • It is easier to write your own bitmap loader than to tweak the data and try to use the LoadImage( ) function in Win32 API
BITMAP_FILE_TAG // container structure for bitmaps .BMP file typedef struct BITMAP_FILE_TAG { BITMAPFILEHEADER bitmapfileheader; // this contains the bitmapfile header BITMAPINFOHEADER bitmapinfoheader; // this is all the info including the palette PALETTEENTRY palette[256]; // we will store the palette here UCHAR *buffer; // this is a pointer to the data } BITMAP_FILE, *BITMAP_FILE_PTR;
Load_Bitmap_File int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char *filename) { // this function opens a bitmap file and loads the data into bitmap int file_handle, // the file handle index; // looping index UCHAR *temp_buffer = NULL; // used to convert 24 bit images to 16 bit OFSTRUCT file_data; // the file data information // open the file if it exists if ((file_handle = OpenFile(filename,&file_data,OF_READ))==-1) return(0); // now load the bitmap file header _lread(file_handle, &bitmap->bitmapfileheader,sizeof(BITMAPFILEHEADER)); // test if this is a bitmap file if (bitmap->bitmapfileheader.bfType!=BITMAP_ID) { _lclose(file_handle); // close the file return(0); // return error } // end if
Load_Bitmap_File // load the bitmap file header _lread(file_handle, &bitmap->bitmapinfoheader,sizeof(BITMAPINFOHEADER)); // now load the color palette if there is one if (bitmap->bitmapinfoheader.biBitCount == 8) { _lread(file_handle, &bitmap->palette,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY)); // now set all the flags in the palette correctly and fix the reversed // BGR RGBQUAD data format for (index=0; index < MAX_COLORS_PALETTE; index++) { // reverse the red and green fields int temp_color = bitmap->palette[index].peRed; bitmap->palette[index].peRed = bitmap->palette[index].peBlue; bitmap->palette[index].peBlue = temp_color; // always set the flags word to this bitmap->palette[index].peFlags = PC_NOCOLLAPSE; } // end for index } // end if
Load_Bitmap_File // finally the image data itself _lseek(file_handle,-(int)(bitmap->bitmapinfoheader.biSizeImage),SEEK_END); // now read in the image if (bitmap->bitmapinfoheader.biBitCount==8 || bitmap->bitmapinfoheader.biBitCount==16 || bitmap->bitmapinfoheader.biBitCount==24) { // delete the last image if there was one if (bitmap->buffer) free(bitmap->buffer); // allocate the memory for the image if (!(bitmap->buffer=(UCHAR *)malloc(bitmap->bitmapinfoheader.biSizeImage))) { _lclose(file_handle); // close the file return(0); // return error } // end if // now read it in _lread(file_handle,bitmap->buffer,bitmap->bitmapinfoheader.biSizeImage); } // end if
Load_Bitmap_File // close the file _lclose(file_handle); // flip the bitmap Flip_Bitmap(bitmap->buffer, bitmap->bitmapinfoheader.biWidth*(bitmap >bitmapinfoheader.biBitCount/8), bitmap->bitmapinfoheader.biHeight); // return success return(1); } // end Load_Bitmap_File
Unload_Bitmap_File int Unload_Bitmap_File(BITMAP_FILE_PTR bitmap) { // this function releases all memory associated with "bitmap" if (bitmap->buffer) { // release memory free(bitmap->buffer); // reset pointer bitmap->buffer = NULL; } // end if // return success return(1); } // end Unload_Bitmap_File
Flip_BitMap int Flip_Bitmap(UCHAR *image, int bytes_per_line, int height) { // used to flip bottom-up .BMP images UCHAR *buffer; // used to perform the image processing int index; // looping index // allocate the temporary buffer if (!(buffer = (UCHAR *)malloc(bytes_per_line*height))) return(0); // copy image to work area memcpy(buffer,image,bytes_per_line*height); // flip vertically for (index=0; index < height; index++) memcpy(&image[((height-1) - index)*bytes_per_line], &buffer[index*bytes_per_line], bytes_per_line); // release the memory free(buffer); // return success return(1); } // end Flip_Bitmap
DDraw_Fill_Surface int DDraw_Fill_Surface(LPDIRECTDRAWSURFACE7 lpdds,int color) { DDBLTFX ddbltfx; // this contains the DDBLTFX structure // clear out the structure and set the size field DDRAW_INIT_STRUCT(ddbltfx); // set the dwfillcolor field to the desired color ddbltfx.dwFillColor = color; // ready to blt to surface lpdds->Blt(NULL, // ptr to dest rectangle NULL, // ptr to source surface, NA NULL, // ptr to source rectangle, NA DDBLT_COLORFILL | DDBLT_WAIT, // fill and wait &ddbltfx); // ptr to DDBLTFX structure // return success return(1); } // end DDraw_Fill_Surface
Loading 16-Bit Bitmap • There is no palette to load • Very few paint programs actually create 16-bit bitmaps, so you may need to convert 24-bit to 16-bit before drawing • LaMothe’s Load_Bitmap_File( ) does all this work for you
Loading 24-Bit Bitmaps • This is simplest of the 3 cases • However since many graphics cards support 32-bit pixel data rather than 24-bit and extra byte is used for padding or alpha channeling • Again Load_Bitmap_File( ) does most of the work for you
Using Offscreen Surfaces • Create a DirectDraw application • Create primary and secondary surfaces • Load bitmap into secondary surface • Load palette info in primary surface palette • Scan rectangles from regions on secondary surface and blit them to the primary surface Note: Steps 1 to 4 done in GameInit( ) and ScanImage( ) used to extract animation frames
Game_Init( ) // create IDirectDraw interface 7.0 object and test for error // set cooperation to full screen // set display mode to 640x480x8 // we need a complex surface system with a primary and backbuffer // set the backbuffer count field to 1 // request a complex, flippable // create the primary surface // now query for attached surface from the primary surface // get the attached back buffer surface // create the palette object // finally attach the palette to the primary surface // set clipper up on back buffer since that's where well clip
Game_Init( ) // load the 8-bit image if (!Load_Bitmap_File(&bitmap,"alley8.bmp")) return(0); // load it's palette into directdraw if (FAILED(lpddpal->SetEntries(0,0,MAX_COLORS_PALETTE,bitmap.palette))) return(0); // clean the surfaces DDraw_Fill_Surface(lpddsprimary,0); DDraw_Fill_Surface(lpddsback,0); // create the buffer to hold the background lpddsbackground = DDraw_Create_Surface(640,480,0,-1); // copy the background bitmap image to the background surface // lock the surface lpddsbackground->Lock(NULL,&ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL); // get video pointer to primary surfce UCHAR *image_buffer = (UCHAR *)ddsd.lpSurface;
Game_Init( ) // copy memory from double buffer to primary buffer // now unlock the primary surface // unload the bitmap file, we no longer need it Unload_Bitmap_File(&bitmap); // seed random number generator srand(GetTickCount()); // initialize all the aliens // alien on level 1 of complex aliens[0].x = rand()%SCREEN_WIDTH; aliens[0].y = 116 - 72; aliens[0].velocity = 2+rand()%4; aliens[0].current_frame = 0; aliens[0].counter = 0;
Game_Init( ) // now load the bitmap containing the alien imagery // then scan the images out into the surfaces of alien[0] // and copy then into the other two, be careful of reference counts! // load the 8-bit image if (!Load_Bitmap_File(&bitmap,"dedsp0.bmp")) return(0); // create each surface and load bits for (int index = 0; index < 3; index++) { // create surface to hold image aliens[0].frames[index] = DDraw_Create_Surface(72,80,0); // now load bits... Scan_Image_Bitmap(&bitmap, // bitmap file to scan image data from aliens[0].frames[index], // surface to hold data index, 0); // cell to scan image from } // end for index
Game_Init( ) // unload the bitmap file, we no longer need it Unload_Bitmap_File(&bitmap); // now for the tricky part. There is no need to create more // surfaces with the samedata, so you copy the surface // pointers member for member to each alien // however, be careful, since the reference counts do NOT go // up, you still only need // to release() each surface once! for (index = 0; index < 3; index++) aliens[1].frames[index] = aliens[2].frames[index] = aliens[0].frames[index];
Scan_Image_Bitmap int Scan_Image_Bitmap(BITMAP_FILE_PTR bitmap, // bitmap file LPDIRECTDRAWSURFACE7 lpdds, // surface to hold data int cx, int cy) // cell to scan image from { // this function extracts a bitmap out of a bitmap file UCHAR *source_ptr, // working pointers *dest_ptr; DDSURFACEDESC2 ddsd; // direct draw surface description // get the addr to destination surface memory // set size of the structure ddsd.dwSize = sizeof(ddsd); // lock the display surface lpdds->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);
Scan_Image_Bitmap // compute position to start scanning bits from cx = cx*(ddsd.dwWidth+1) + 1; cy = cy*(ddsd.dwHeight+1) + 1; gwidth = ddsd.dwWidth; gheight = ddsd.dwHeight; // extract bitmap data source_ptr = bitmap->buffer + cy*bitmap->bitmapinfoheader.biWidth+cx; // assign a pointer to the memory surface for manipulation dest_ptr = (UCHAR *)ddsd.lpSurface;
Scan_Image_Bitmap // iterate thru each scanline and copy bitmap for (int index_y=0; index_y < ddsd.dwHeight; index_y++) { // copy next line of data to destination memcpy(dest_ptr, source_ptr, ddsd.dwWidth); // advance pointers dest_ptr += (ddsd.lPitch); // (ddsd.dwWidth); source_ptr += bitmap->bitmapinfoheader.biWidth; } // end for index_y // unlock the surface lpdds->Unlock(NULL);
Drawing Blitter Objects int DDraw_Draw_Surface(LPDIRECTDRAWSURFACE7 source, // source surface to draw int x, int y, // position to draw at int width, int height, // size of source surface LPDIRECTDRAWSURFACE7 dest, // surface to draw bob on int transparent = 1) // transparency flag { // draw a bob at the x,y defined in the BOB // on the destination surface defined in dest RECT dest_rect, // the destination rectangle source_rect; // the source rectangle // fill in the destination rect dest_rect.left = x; dest_rect.top = y; dest_rect.right = x+width-1; dest_rect.bottom = y+height-1; // fill in the source rect source_rect.left = 0; source_rect.top = 0; source_rect.right = width-1; source_rect.bottom = height-1;
Drawing Blitter Objects // test transparency flag if (transparent) { // enable color key blit // blt to destination surface if (FAILED(dest->Blt(&dest_rect, source,&source_rect, (DDBLT_WAIT | DDBLT_KEYSRC), NULL))) return(0); } // end if else { // perform blit without color key // blt to destination surface if (FAILED(dest->Blt(&dest_rect, source, &source_rect,(DDBLT_WAIT), NULL))) return(0); } // end if
Transparency • When your bitmap does not cover an entire rectangle you need to designate the background color as a transparent color so that screen in not wiped out when you blit • Most people use RGB black (0,0,0) when in RGB mode and color index 0 in palette color mode
Transparency • Each pixel is tested for transparency as it is drawn • To keep black objects in the foreground you might decide that anything blue in the destination can’t be blitted
Color Keying • One nice thing about Direct Draw is that you can control which colors are transparent and then the blitter handles testing pixels for transparency • Source key blits are accomplished by using SetColorKey( ) to make a range of colors transparent • Destination key are also possible but not used in the LaMothe text
Rotaton and Scaling • The Direct Draw allows blitting with size scaling, but does not allow blitting with rotation • To get the effect of blitting with rotation in Direct Draw you need to set up your bitmap frames to show the rotation
Scaling (jaggies) 1x4 pixels 1x8 pixels 0 20 0 20 • 5 1 20 • 12 2 5 • 85 3 5 4 12 5 12 6 85 7 85
Scaling (data loss) 1x4 pixels 1x2 pixels 0 20 0 20 • 5 1 12 • 12 • 85
Filtering • To avoid severe data loss you could use a could probability filter to select colors to copy (e.g. sample_rate = 4 / 3 = 1.33) 0 12 0 0 12 1 34 1.33 1 34 2 56 2.66 2 56 3 90
Game_Init // initialize all the aliens // alien on level 1 of complex aliens[0].x = rand()%SCREEN_WIDTH; aliens[0].y = 116 - 72; aliens[0].velocity = 2+rand()%4; aliens[0].current_frame = 0; aliens[0].counter = 0; aliens[0].width = 72; // set real size aliens[0].height = 80; aliens[0].scale = ((float)(1+rand()%20))/10; // scale 0.1 to 2.0 // fix up feet so they still contact floor aliens[0].y+=(72 - aliens[0].scale*72); // do the same for each alien and then load bitmaps as before
DDRaw_Draw_Surface_Scaled int DDraw_Draw_Surface_Scaled(LPDIRECTDRAWSURFACE7 source, // source int x, int y, // position to draw int width_src, int height_src, // size of source int width_dest, int height_dest,// size of dest LPDIRECTDRAWSURFACE7 dest, // surface to draw on int transparent = 1) // transparency flag { // draw the surface at the x,y defined by dest, send both the original // source size of surface, along with the desired size, if they are // different then directdraw will scale the bitmap for you // note that we are sending the size of the surface
DDRaw_Draw_Surface_Scaled RECT dest_rect, // the destination rectangle source_rect; // the source rectangle // fill in the destination rect dest_rect.left = x; dest_rect.top = y; dest_rect.right = x+width_dest-1; dest_rect.bottom = y+height_dest-1; // fill in the source rect source_rect.left = 0; source_rect.top = 0; source_rect.right = width_src-1; source_rect.bottom = height_src-1;
DDRaw_Draw_Surface_Scaled // test transparency flag if (transparent) { // enable color key blitblt to destination surface if (FAILED(dest->Blt(&dest_rect, source, &source_rect,(DDBLT_WAIT | DDBLT_KEYSRC), NULL))) return(0); } // end if else { // perform blit without color keyblt to destination surface if (FAILED(dest->Blt(&dest_rect, source, &source_rect,(DDBLT_WAIT), NULL))) return(0); } // end if
Blinking Lights • The palette can be used to animate blinking lights • Basically, you need to pick 2 colors like 253 and 254 • Then write code to toggle the colors in the bit map using SetEntries( )
Blinking Lights // defines for Blink_Colors #define BLINKER_ADD 0 // add a light to database #define BLINKER_DELETE 1 // delete a light from database #define BLINKER_UPDATE 2 // update a light #define BLINKER_RUN 3 // run normal // blinking light structure typedef struct BLINKER_TYP { // user sets these int color_index; // index of color to blink PALETTEENTRY on_color; // RGB value of "on" color PALETTEENTRY off_color; // RGB value of "off" color int on_time; // number of frames to keep "on" int off_time; // number of frames to keep "off" // internal member int counter; // counter for state transitions int state; // state of light, -1 off, 1 on, 0 dead } BLINKER, *BLINKER_PTR; int green_id = -1, red_id = -1; // these hold the ids of blinkers
Blink_Colors( ) int Blink_Colors(int command, BLINKER_PTR new_light, int id) { // this function blinks a set of lights static BLINKER lights[256]; // supports up to 256 blinking lights static int initialized = 0; // tracks if function has initialized // test if this is the first time function has ran if (!initialized) { // set initialized initialized = 1; // clear out all structures memset((void *)lights,0, sizeof(lights)); } // end if
Blink_Colors( ) // clear out all structures memset((void *)lights,0, sizeof(lights)); } // end if // now test what command user is sending switch (command) { case BLINKER_ADD: // add a light to the database { … // update palette entry lpddpal->SetEntries(0,lights[index].color_index,1,&lights[index].off_color); } break; case BLINKER_DELETE: // delete light from database { … // delete the light sent in id if (lights[id].state != 0) { // kill the light memset((void *)&lights[id],0,sizeof(BLINKER)); } break;
Blink_Colors( ) case BLINKER_UPDATE: // update the light indicated by id { … // update palette entry if (lights[id].state == -1) lpddpal->SetEntries(0,lights[id].color_index,1,&lights[id].off_color); else lpddpal->SetEntries(0,lights[id].color_index,1,&lights[id].on_color); } break; case BLINKER_RUN: // run the algorithm { // run thru database and process each light … // update color lpddpal->SetEntries(0,lights[index].color_index,1,&lights[index].on_color); } default: break; } // end switch
Color Rotation • Easy way to simulate motion • Used to simulate flying through trenches on the Death Star • Done by retrieving a palette entry using GetEntries( ) • The shifting the entry one place to the right and writing it back using SetEntries( )
Rotate_Colors( ) int Rotate_Colors(int start_index, int end_index) { // this function rotates the color between start and end int colors = end_index - start_index + 1; PALETTEENTRY work_pal[MAX_COLORS_PALETTE]; // working palett // get the color palette lpddpal->GetEntries(0,start_index,colors,work_pal); // shift the colors lpddpal->SetEntries(0,start_index+1,colors-1,work_pal); // fix up the last color lpddpal->SetEntries(0,start_index,1,&work_pal[colors - 1]); // update shadow palette lpddpal->GetEntries(0,0,MAX_COLORS_PALETTE,palette); // return success return(1); } // end Rotate_Colors
Mixing GDI and DirectX • You are allowed to mix calls to these two libraries, but you need to do the work yourself since the cooperative level is set to “full screen, exlcusive” • The reason you might like to do this is that GDI is much better at drawing text than Direct Draw
GDI Options • Via high level controls (e.g. dialog boxes, message boxes, and avoiding graphic device contexts) • Via GDC (by requesting a GDC from inside DirectDraw using the function IDIRECTDRAWSURFACE) • Via one of the standard windowed modes, but writing games to work in windowed mode is tough
Draw_Text_GDI( ) int Draw_Text_GDI(char *text, int x,int y, COLORREF color, LPDIRECTDRAWSURFACE7 lpdds) { // this function draws the sent text on the sent surface // using color index as the color in the palette HDC xdc; // the working dc // get the dc from surface if (FAILED(lpdds->GetDC(&xdc))) return(0); // set the colors for the text up SetTextColor(xdc,color); // set background mode to transparent so black isn't copied SetBkMode(xdc, TRANSPARENT); TextOut(xdc,x,y,text,strlen(text)); // release the dc lpdds->ReleaseDC(xdc); return(1); // return success } // end Draw_Text_GDI