// Loads a PCX file bool CBitmap::loadPCX(const char *fileName) { FILE *filePtr = fopen(fileName,"rb"); // Open the file BYTE *scanLine = NULL; // A pointer to contain a row of pixel data in the PCX SPCXHeader header = {0}; // Create a zeroed out SPCXHeader // We want to do robust error checking so we'll wrap the loading code in a // try...catch block so we can deal with errors in only place try { // Error Check if(!filePtr) throw "Couldn't open .pcx file"; // Read in the .pcx file header if(!fread(&header,sizeof(SPCXHeader),1,filePtr)) throw "Couldn't read .pcx header"; // Error Check -- manufacturer better equal 10 otherwise this // for sure isn't a .pcx file if((int)header.manufacturer != 10) throw "Not a valid .pcx file"; // Okay we have the header. Let's determine the width, height, and number of channels // that are in the .pcx file. Notice the +1 for width and height int width = header.xMax - header.xMin + 1; int height = header.yMax - header.yMin + 1; int channels = header.planes; // Okay let's set the size of our CBitmap if(!setSize(width,height,channels)) throw "Couldn't create bitmap for loading .pcx into"; // Now we need to test to see if there is a palette // If the header version equals 5, there is a chance that there is a palette if((int)header.version == 5) { // Since it's a version 5 .pcx it's possible that it has a palette so // now we must test further BYTE test_byte; // byte to check to see if it's a 8-bit .pcx file // Seek to where the test byte is if(fseek(filePtr,-PCX_PALETTE_TEST,SEEK_END)) throw "PCX file is corrupt"; // Read in the test byte if(!fread(&test_byte,sizeof(BYTE),1,filePtr)) throw "PCX file is corrupt"; // Check the test byte -- If it EQUALS '12' we have a palette to read in if(test_byte == 12) { // Load the palette if(!loadPCXPalette(filePtr)) throw "Couldn't load palette"; } } // Next seek past the SPCXHeader if(fseek(filePtr,PCX_HEADER_SIZE,SEEK_SET)) throw "PCX file is corrupt"; // The total number of bytes in a scanline int totalBytes = (int)header.planes * header.bytesPerLine; // Allocate the scanline scanLine = new BYTE[totalBytes]; // Error Check if(scanLine == NULL) throw "Couldn't allocate memory"; // Now loop over the entire .pcx file -- We'll fill each "scanLine" and then // convert that scanLine to our CBitmap depending on what type of .pcx we're dealing with for(int lineCount = 0; lineCount < height; lineCount++) { if(!readScanLine(filePtr, scanLine, totalBytes)) throw "Error reading scanLine"; // If it's an 8-bit .pcx file, simply copy over the scanline if((int)header.planes == 1) memcpy(getLinePtr(lineCount),scanLine,getStride()); else // Converge "scanLine" into the same form as a CBitmap 24-bit scanline { uchar *pixels = getLinePtr(lineCount); // Loop through the entire line of pixels. for(int i = 0; i < width; ++i) { /* What does this mess do? Well lets break down what each part is doing. i * header.planes - This puts "pixels" at the current pixel + RED_BITMAP_OFFSET - Offset from the current pixel to the 'R' color component + GREEN_BITMAP_OFFSET - Offset from the current pixel to the 'G' color component + BLUE_BITMAP_OFFSET - Offset from the current pixel to the 'B' color component PCX stores the scanline as a sequence of 'R', then a sequence of 'G', then a sequence of 'B' so "i + header.bytesPerLine" moves to the sequence containing the 'G' components and "i + header.bytesPerLine * 2" moves to the sequence containing the 'B' components */ pixels[i * header.planes + RED_BITMAP_OFFSET] = scanLine[i]; pixels[i * header.planes + GREEN_BITMAP_OFFSET] = scanLine[i + header.bytesPerLine]; pixels[i * header.planes + BLUE_BITMAP_OFFSET] = scanLine[i + header.bytesPerLine * 2]; } } // end of if...else ((int)header.planes == 1) } // end of for(int lineCount = 0; lineCount < height; lineCount++) } catch (char *str) // If an error happened, catch it here and cleanup { MessageBox(NULL, str, "ERROR", MB_OK | MB_ICONERROR); if(filePtr) fclose(filePtr); if(scanLine) delete[] scanLine; return false; } delete[] scanLine; // Free mem fclose(filePtr); // Close the file return true; // PCX was successfully loaded }
// This is the function that "manually" loads an 8-bit bitmap // It will return true for success, false for failure bool CBitmap::loadBMP(char *file_name, HDC hdc) { // Make sure we got a legal file name passed in if(file_name == NULL) return false; // These structures will hold our bitmap file header (top of bitmap) // and our bitmap info header (directly underneath bitmap file header) BITMAPFILEHEADER bmp_fileheader = {0}; BITMAPINFOHEADER bmp_infoheader = {0}; FILE *bmpFile = fopen(file_name,"rb"); bool status = false; // This the "status" of our attempt at loading the 8-bit bitmap // When we have successful loaded it, we'll set this to true // Make sure we could open the file if(bmpFile == NULL) return false; // Read the file header -- Making sure the read is if(!fread(&bmp_fileheader, sizeof(BITMAPFILEHEADER), 1, bmpFile)) goto CLEAN_UP; // Now normally "goto's" are frowned upon, but in this case // it's a reasonable solution -- There are MANY things that could fail // along the way, so instead of rewriting the code for each // possible failure, we'll just "goto" the "CLEAN_UP:" code and // return our "status" flag of how the loading of the bitmap went :) // Check the type field to make sure we have a .bmp file -- This will always be "BM" for .bmps if(memcmp(&bmp_fileheader.bfType, "BM", 2)) goto CLEAN_UP; // Read the info header -- You'll notice it's positioned directly after the // BITMAPFILEHEADER. That's roughly how a bitmap is laid out: BITMAPFILEHEADER // BITMAPINFOHEADER // Pixel Data if(!fread(&bmp_infoheader, sizeof(BITMAPINFOHEADER), 1, bmpFile)) goto CLEAN_UP; // It's always good to double check and make sure the fread() went as planned :) // Obviously, these two numbers better be equal or there was a problem if(bmp_infoheader.biSize != sizeof(BITMAPINFOHEADER)) goto CLEAN_UP; // This had better equal one (MSDN says this will always equal one) if(bmp_infoheader.biPlanes != 1) goto CLEAN_UP; // If the bitmap isn't a 8-bit bitmap, WE CAN'T LOAD IT // .biBitCount represents the number of bits per pixel our bitmap is if(bmp_infoheader.biBitCount != 8) goto CLEAN_UP; // Now we're creating an "empty" CBitmap for storing the 8-bit bitmap // If this fails we can't load the 8-bit bitmap (darn!) // You'll notice that the BITMAPINFOHEADER contains the width (biWidth) in pixels and // the height (biHeight) in pixels of the bitmap if(!setSize(hdc,bmp_infoheader.biWidth, bmp_infoheader.biHeight)) goto CLEAN_UP; /************* HERE WE'RE GOING TO READ THE COLOR MAP INFORMATION ***************/ { RGBQUAD rgbQuad[256] = {0}; // Let's determine the size of the color table (how many colors are used by the bitmap) uint tableSize = bmp_infoheader.biClrUsed; // If the size equals zero, this means the number of colors used by bitmap is equal // to the maximum number of colors allowed for this type of bitmap (this is dependent on the bits per pixel) // For 8-bit bitmaps, the maximum number of colors is 256 if(tableSize == 0) tableSize = 256; // 8-bit bitmaps can't have more than 256 colors // Also 8-bit bitmaps can't have a negative number of colors // Make sure there isn't an error :) if((tableSize > 256) || (tableSize < 0)) goto CLEAN_UP; // Okay we're ready to read the RGBQUAD (the color table (a.k.a. color map) ) if(!fread(rgbQuad, sizeof(RGBQUAD) * tableSize, 1, bmpFile)) goto CLEAN_UP; // Set the palette in the bitmap if(!setPalette(0,tableSize,rgbQuad)) goto CLEAN_UP; } // If you noticed the {} it's because I wanted to make a "local scope" to separate // the color table reading for clarity sake (also makes the compiler not puke) /*************** DONE READING THE COLOR MAP INFORMATION *************************/ // Jump to the location where the bitmap data is stored // bfOffBits is the number of bytes from the beginning of the file to where the bitmaps bits reside if(fseek(bmpFile, bmp_fileheader.bfOffBits, SEEK_SET)) goto CLEAN_UP; // Read in the bitmap is compressed (has been saved with Run Length Encoding) // We can't load it if(bmp_infoheader.biCompression == BI_RLE8) goto CLEAN_UP; else if(bmp_infoheader.biCompression == BI_RGB) // There is NO compression { /* Okay a little explaining is in order :) The total number of bytes that make a scan line (this would be one line of pixel data) is equal to: (width of bitmap * bitmap channels) Now a bitmap's channels is defined as "the number of bytes used per pixel" So an 8-bit bitmaps channel would equal 1, an 24-bit bitmap channel would equal 3 -- Remember (8 bits == 1 byte) :) Sometime bitmaps have padding bytes, if we're reading in bitmap and it has padding bytes it's our responsibility to skip over 'em You'll recall we calculate the "bmp_stride" in the set_size() method -- This can be thought us as "the actual number of bytes in scan line" The bmp_width would be the "the actual number of bytes in a scan line that are pixel data" This of course follows our formula (bmp_width * 1) */ uint padding = bmp_stride - bmp_width; // Calculate the number of // padding bytes if there is any // Okay now all we have to do is loop over all the pixel data // and store what we read in, in our CBitmap :) for(int y = 0; y < bmp_infoheader.biHeight; y++) { // We have to flip the y-coordinate because our CBitmap // puts line zero at the top of the screen, and the .bmp format // on disk puts the first bytes at the bottom of the screen. uchar *scanLine = getLinePtr(bmp_height - 1 - y); // Read the precise number of bytes that the line requires into the bitmap. // This will be the width of the bitmap, DON'T read the padding bytes. // Remember memory alignment requirements may vary. // We want our 8-bit bitmap loader to be ROBUST %) if(!fread(scanLine, bmp_width, 1, bmpFile)) goto CLEAN_UP; // In case you weren't aware, goto's will break out of for loops // Skip over the padding bytes (if there are any, if padding == 0, no harm done) if(fseek(bmpFile, padding, SEEK_CUR)) goto CLEAN_UP; } // If we get here the bitmap WAS LOADED!!!!!!!!!!!! // So we better set status to true status = true; } // end of else if(bmp_infoheader.biCompression == BI_RGB) // There is NO compression CLEAN_UP: fclose(bmpFile); // Close the file we opened return status; }
bool DIB_BITMAP::loadBMP(const char *file_name) { // If DIB_BITMAP has already been set -- clear it out first FreeDIB_BMP(); // Error Check -- Make sure they passed in a valid file name if(!file_name) return false; FILE *bmp_file = fopen(file_name, "rb"); // Error Check -- Make sure the file could be opened if(!bmp_file) return false; BITMAPFILEHEADER bmp_fileheader; // Read the BITMAPFILEHEADER if(!fread(&bmp_fileheader, sizeof(BITMAPFILEHEADER), 1, bmp_file)) { fclose(bmp_file); return false; } // Check the type field to make sure we have a .bmp file if(memcmp(&bmp_fileheader.bfType, "BM", 2)) { fclose(bmp_file); return false; } BITMAPINFOHEADER bmp_infoheader; // Read the BITMAPINFOHEADER. if(!fread(&bmp_infoheader, sizeof(BITMAPINFOHEADER), 1, bmp_file)) { fclose(bmp_file); return false; } // We only support 24-bit and 32-bit .bmps so make sure that's what we have if((bmp_infoheader.biBitCount != 24) && (bmp_infoheader.biBitCount != 32)) { fclose(bmp_file); return false; } // Set the size of our DIB_BITMAP, once we do this we're ready to store the pixel // data in it if(set_size(bmp_infoheader.biWidth,bmp_infoheader.biHeight,bmp_infoheader.biBitCount / 8) == false) { fclose(bmp_file); return false; } // Jump to the location where the pixel data is stored if(fseek(bmp_file, bmp_fileheader.bfOffBits, SEEK_SET)) { fclose(bmp_file); return false; } unsigned int bytesPerLine = bmp_width * bmp_channels; // Bytes per line (number of bytes // in a scan line) // Calculate how many "padding" bytes there are -- WE DO NOT want to read in the // padding bytes (we will just skip over those) // **Remember** Windows adds padding bytes to ensure ALL .bmps are DWORD aligned // (divisible evenly by 4) unsigned int padding = bmp_stride - bytesPerLine; // Loop over all the scan lines (all the rows of pixels in the image) for(int y = bmp_height-1; y >= 0; y--) { // Get the "current" line pointer uchar *LinePtr = getLinePtr(y); // Read the precise number of bytes that the scan line requires into the bitmap if(!fread(LinePtr, bytesPerLine, 1, bmp_file)) { fclose(bmp_file); return false; } // Skip over any padding bytes. if(fseek(bmp_file, padding, SEEK_CUR)) { fclose(bmp_file); return false; } } // end of for (int y = 0; y < bmp_infoheader.biHeight; y++) fclose(bmp_file); return true; // If we get here .bmp was read in successfully } // end of loadBMP(char *file_name, HDC hdc)