The task to read a bitmap into a data array, show this bitmap and save an array or dc to a file sound like a simple task but is rather difficult in reality if one uses MFC. Maybe I am totaly wrong when stating this, but that is my result of one week searching for solutions and implementing my own ones.
What I have achieved can be as an example project.
bitmapopenandsave.zip (61,0 KiB)The idea of these classes is to open a bitmap into a dc and to save the content of a dc into a bitmap.
CBitmapOp
#pragma once #include <vector> using namespace std; class CBitmapOp { public: CBitmapOp(void); ~CBitmapOp(void); public: BOOL SaveDcToBitmapFile( LPCTSTR szFile, CPaintDC* pDC, CRect rect); BOOL WriteDIBToFile( LPCTSTR szFile, HANDLE hDIB); HANDLE DDBToDIB( CBitmap& bitmap, DWORD dwCompression, CPalette* pPal ) ; BOOL LoadBitmapFileToCBitmap( LPCTSTR sBMPFile, CBitmap& bitmap, CPalette *pPal ); BOOL GetBitmapPixels(CBitmap& bitmap); void SetBitmapDataSize(const int x, const int y); vector</vector><vector <COLORREF> > GetBitmapArray(); private: int m_PixelNumberX; int m_PixelNumberY; vector</vector><vector <COLORREF> > BitmapData; }; </vector> |
#include "StdAfx.h" #include "BitmapOp.h" CBitmapOp::CBitmapOp(void) { m_PixelNumberX=0; m_PixelNumberY=0; } CBitmapOp::~CBitmapOp(void) { } //////////////////////////////////////////////////////////////////// // www.codeguru.com // Writing a window image to a BMP file // Zafir Anjum //////////////////////////////////////////////////////////////////// // To save the image of a window to a BMP file, you have to draw the image // to a compatible memory device context, convert the bitmap associated with // the memory DC to a device-independent bitmap and then write the DIB to a file. // In previous sections, we have already covered how to get a DIB from a // device-dependent bitmap and how to save a DIB to a file. The WriteWindowToDIB() // function uses the DDBToDIB() and the WriteDIB() functions to accomplish its task. // However, this is just half of the job; the other half is to get the logical // palette that can be used with the bitmap. The bitmap won't display properly if // it needs a logical palette and one is not available. Depending on the display // settings we don't always need a logical palette. We use GetDeviceCaps() to determine // whether a display device supports palette operation. For instance, if the display is // set for true color then it doesn't support a palette. We allocate enough memory for a // LOGPALETTE structure and then use the GetSystemPaletteEntries() to fill the color table. // Now we can create the logical palette. // The function then uses the DDBToDIB() and the WriteDIB() functions to complete the job. // You should note that if the window is partially or completely covered by other windows then // the saved bitmap will show parts of these other windows too. //////////////////////////////////////////////////////////////////// // changed by Matthias Pospiech (2007), so that it works directly with a DC. //////////////////////////////////////////////////////////////////// BOOL CBitmapOp::SaveDcToBitmapFile( LPCTSTR szFile, CPaintDC* pDC, CRect rect) { CBitmap bitmap; //CWindowDC dc(pWnd); CDC memDC; memDC.CreateCompatibleDC(pDC); bitmap.CreateCompatibleBitmap(pDC, rect.Width(),rect.Height() ); CBitmap* pOldBitmap = memDC.SelectObject(&bitmap); memDC.BitBlt(0, 0, rect.Width(),rect.Height(), pDC, 0, 0, SRCCOPY); // Create logical palette if device support a palette CPalette pal; if( pDC->GetDeviceCaps(RASTERCAPS) & RC_PALETTE ) { UINT nSize = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 256); LOGPALETTE *pLP = (LOGPALETTE *) new BYTE[nSize]; pLP->palVersion = 0x300; pLP->palNumEntries = GetSystemPaletteEntries(*pDC, 0, 255, pLP->palPalEntry ); // Create the palette pal.CreatePalette( pLP ); delete[] pLP; } memDC.SelectObject(pOldBitmap); // Convert the bitmap to a DIB HANDLE hDIB = DDBToDIB( bitmap, BI_RGB, &pal ); if( hDIB == NULL ) return FALSE; // Write it to file WriteDIBToFile( szFile, hDIB ); // Free the memory allocated by DDBToDIB for the DIB GlobalFree( hDIB ); return TRUE; } //////////////////////////////////////////////////////////////////// // www.codeguru.com // Writing a bitmap to a BMP file // Zafir Anjum //////////////////////////////////////////////////////////////////// // WriteDIB - Writes a DIB to file // Returns - TRUE on success // szFile - Name of file to write to // hDIB - Handle of the DIB BOOL CBitmapOp::WriteDIBToFile( LPCTSTR szFile, HANDLE hDIB) { BITMAPFILEHEADER hdr; LPBITMAPINFOHEADER lpbi; if (!hDIB) return FALSE; CFile file; if( !file.Open( szFile, CFile::modeWrite|CFile::modeCreate) ) return FALSE; lpbi = (LPBITMAPINFOHEADER)hDIB; int nColors = 1 < < lpbi->biBitCount; // Fill in the fields of the file header hdr.bfType = ((WORD) ('M' < < 8) | 'B'); // is always "BM" hdr.bfSize = (DWORD)(GlobalSize (hDIB) + sizeof( hdr )); hdr.bfReserved1 = 0; hdr.bfReserved2 = 0; hdr.bfOffBits = (DWORD) (sizeof( hdr ) + lpbi->biSize + nColors * sizeof(RGBQUAD)); // Write the file header file.Write( &hdr, sizeof(hdr) ); // Write the DIB header and the bits file.Write( lpbi, (UINT)GlobalSize(hDIB) ); return TRUE; } //////////////////////////////////////////////////////////////////// // www.codeguru.com // Writing a window image to a BMP file // Zafir Anjum //////////////////////////////////////////////////////////////////// // // A device-dependent bitmap (DDB) is structured in such a way that it matches // very closely the form in which the bitmap is finally displayed by the device driver. // For this reason the DDB is unlikely to be compatible with other display devices. // A device-independent bitmap (DIB) on the other hand has a logical layout and can be // decifered by any device driver. The disadvantage with DIB though is that it is much // slower to render onto the device as compared to a DDB. // // One situation where we need to convert a DDB to a DIB is when we want to save the bitmap // to a file. The BMP files are infact just composed of a short header followed by // information in a DIB. // // The steps involved in creating the DIB are: // 1. Initialize a BITMAPINFOHEADER data structure. We use information in the bitmap // to determine the widht, height and the bit count. The compression field is set // based on the argument passed into the function. It's best to use the BI_RGB // compression for greater portability and faster rendering. // 2. Select and realize the logical palette associated with the bitmap. This // is passed in as the last argument to the function. If a palette is not provided // use the default palette. // 3. Determine the number of bytes required for the bitmap bits. First allocate // enough memory for BITMAPINFOHEADER and the color table and then call GetDIBits() // to size. Supplying a NULL for the lpBits parameter instructs GetDIBits() to // biSizeImage field in the bitmapinfoheader. Sometimes, this does not work // (depending on the device driver) in which case we make our own estimate. // We can compute this since we already know the width, height, the number of bits // per pixel and the fact that each row of pixels will occupy a multiple // of 4 bytes (32 bits). // 4. Given the final size of the bitmap bits, reallocate the memory block to hold // the bitmapinfoheader, the color table and the bitmap bits. // 5. Finally call the GetDIBits() again to get the bitmap bits. //////////////////////////////////////////////////////////////////// // DDBToDIB - Creates a DIB from a DDB // bitmap - Device dependent bitmap // dwCompression - Type of compression - see BITMAPINFOHEADER // pPal - Logical palette HANDLE CBitmapOp::DDBToDIB( CBitmap& bitmap, DWORD dwCompression, CPalette* pPal ) { BITMAP bm; BITMAPINFOHEADER bi; LPBITMAPINFOHEADER lpbi; DWORD dwLen; HANDLE hDIB; HANDLE handle; HDC hDC; HPALETTE hPal; ASSERT( bitmap.GetSafeHandle() ); // The function has no arg for bitfields if( dwCompression == BI_BITFIELDS ) return NULL; // If a palette has not been supplied use defaul palette hPal = (HPALETTE) pPal->GetSafeHandle(); if (hPal==NULL) hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE); // Get bitmap information bitmap.GetObject(sizeof(bm),(LPSTR)&bm); // Initialize the bitmapinfoheader bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = bm.bmWidth; bi.biHeight = bm.bmHeight; bi.biPlanes = 1; bi.biBitCount = bm.bmPlanes * bm.bmBitsPixel; bi.biCompression = dwCompression; bi.biSizeImage = 0; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; // Compute the size of the infoheader and the color table int nColors = (1 < < bi.biBitCount); if( nColors > 256 ) nColors = 0; dwLen = bi.biSize + nColors * sizeof(RGBQUAD); // We need a device context to get the DIB from hDC = GetDC(NULL); hPal = SelectPalette(hDC,hPal,FALSE); RealizePalette(hDC); // Allocate enough memory to hold bitmapinfoheader and color table hDIB = GlobalAlloc(GMEM_FIXED,dwLen); if (!hDIB){ SelectPalette(hDC,hPal,FALSE); ReleaseDC(NULL,hDC); return NULL; } lpbi = (LPBITMAPINFOHEADER)hDIB; *lpbi = bi; // Call GetDIBits with a NULL lpBits param, so the device driver // will calculate the biSizeImage field GetDIBits(hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L, (DWORD)bi.biHeight, (LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS); bi = *lpbi; // If the driver did not fill in the biSizeImage field, then compute it // Each scan line of the image is aligned on a DWORD (32bit) boundary if (bi.biSizeImage == 0){ bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight; // If a compression scheme is used the result may infact be larger // Increase the size to account for this. if (dwCompression != BI_RGB) bi.biSizeImage = (bi.biSizeImage * 3) / 2; } // Realloc the buffer so that it can hold all the bits dwLen += bi.biSizeImage; if (handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE)) hDIB = handle; else{ GlobalFree(hDIB); // Reselect the original palette SelectPalette(hDC,hPal,FALSE); ReleaseDC(NULL,hDC); return NULL; } // Get the bitmap bits lpbi = (LPBITMAPINFOHEADER)hDIB; // FINALLY get the DIB BOOL bGotBits = GetDIBits( hDC, (HBITMAP)bitmap.GetSafeHandle(), 0L, // Start scan line (DWORD)bi.biHeight, // # of scan lines (LPBYTE)lpbi // address for bitmap bits + (bi.biSize + nColors * sizeof(RGBQUAD)), (LPBITMAPINFO)lpbi, // address of bitmapinfo (DWORD)DIB_RGB_COLORS); // Use RGB for color table if( !bGotBits ) { GlobalFree(hDIB); SelectPalette(hDC,hPal,FALSE); ReleaseDC(NULL,hDC); return NULL; } SelectPalette(hDC,hPal,FALSE); ReleaseDC(NULL,hDC); return hDIB; } //////////////////////////////////////////////////////////////////// // www.codeguru.com // Creating a bitmap object from a BMP file // Zafir Anjum //////////////////////////////////////////////////////////////////// // Create the bitmap object // The LoadBMPImage() function given below will read in a BMP file, // create a device dependent bitmap of it and attach it to the CBitmap // object passed in as an argument. It can also create a logical palette // if a pointer to a CPalette object is supplied. // This function basically reads in the BMP file, creates a logical palette // and calls CreateDIBitmap() to create the GDI bitmap object. Note that when // you supply a pointer to a CPalette object, the function creates a logical // palette and uses the palette when creating the bitmap object. //////////////////////////////////////////////////////////////////// // LoadBMPImage - Loads a BMP file and creates a bitmap GDI object // also creates logical palette for it. // Returns - TRUE for success // sBMPFile - Full path of the BMP file // bitmap - The bitmap object to initialize // pPal - Will hold the logical palette. Can be NULL BOOL CBitmapOp::LoadBitmapFileToCBitmap( LPCTSTR sBMPFile, CBitmap& bitmap, CPalette *pPal ) { CFile file; if( !file.Open( sBMPFile, CFile::modeRead) ) return FALSE; BITMAPFILEHEADER bmfHeader; // Read file header if (file.Read((LPSTR)&bmfHeader, sizeof(bmfHeader)) != sizeof(bmfHeader)) return FALSE; // File type should be 'BM' if (bmfHeader.bfType != ((WORD) ('M' < < 8) | 'B')) return FALSE; // Get length of the remainder of the file and allocate memory DWORD nPackedDIBLen = (DWORD)(file.GetLength() - sizeof(BITMAPFILEHEADER)); HGLOBAL hDIB = ::GlobalAlloc(GMEM_FIXED, nPackedDIBLen); if (hDIB == 0) return FALSE; // Read the remainder of the bitmap file. if (file.Read((LPSTR)hDIB, nPackedDIBLen) != nPackedDIBLen ) { ::GlobalFree(hDIB); return FALSE; } BITMAPINFOHEADER &bmiHeader = *(LPBITMAPINFOHEADER)hDIB ; BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ; // If bmiHeader.biClrUsed is zero we have to infer the number // of colors from the number of bits used to specify it. int nColors = bmiHeader.biClrUsed ? bmiHeader.biClrUsed : 1 << bmiHeader.biBitCount; LPVOID lpDIBBits; if( bmInfo.bmiHeader.biBitCount > 8 ) lpDIBBits = (LPVOID)((LPDWORD)(bmInfo.bmiColors + bmInfo.bmiHeader.biClrUsed) + ((bmInfo.bmiHeader.biCompression == BI_BITFIELDS) ? 3 : 0)); else lpDIBBits = (LPVOID)(bmInfo.bmiColors + nColors); // Create the logical palette if( pPal != NULL ) { // Create the palette if( nColors < = 256 ) { UINT nSize = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * nColors); LOGPALETTE *pLP = (LOGPALETTE *) new BYTE[nSize]; pLP->palVersion = 0x300; pLP->palNumEntries = nColors; for( int i=0; i < nColors; i++) { pLP->palPalEntry[i].peRed = bmInfo.bmiColors[i].rgbRed; pLP->palPalEntry[i].peGreen = bmInfo.bmiColors[i].rgbGreen; pLP->palPalEntry[i].peBlue = bmInfo.bmiColors[i].rgbBlue; pLP->palPalEntry[i].peFlags = 0; } pPal->CreatePalette( pLP ); delete[] pLP; } } CClientDC dc(NULL); CPalette* pOldPalette = NULL; if( pPal ) { pOldPalette = dc.SelectPalette( pPal, FALSE ); dc.RealizePalette(); } HBITMAP hBmp = CreateDIBitmap( dc.m_hDC, // handle to device context &bmiHeader, // pointer to bitmap size and format data CBM_INIT, // initialization flag lpDIBBits, // pointer to initialization data &bmInfo, // pointer to bitmap color-format data DIB_RGB_COLORS); // color-data usage bitmap.Attach( hBmp ); if( pOldPalette ) dc.SelectPalette( pOldPalette, FALSE ); ::GlobalFree(hDIB); return TRUE; } //////////////////////////////////////////////////////////////////// // www.codeguru.com // Original: Print transparent bitmaps via regions // Wes Rogers //////////////////////////////////////////////////////////////////// //Load pixels into BitmapData array // //Returns true if OK, else false // //This code is based on the BitmapToRegion( //function contributed by Mr. Lachand-Robert // //It is a modification of the original function //which does the following // //1) Given a DC which is being drawn into // (such as a printer), the function // makes sure that the regions draws // will be centered on the X,Y offsets // //2) The function then loops thru the bits // in the bitmap. // //The function assumes a mapping mode other //than MM_TEXT for the passed-in Device //Context. I used it successfully with //MM_ANISOTROPIC, for example, and was able //to "zoom" the bitmap correctly /////////////////////////////////////////// BOOL CBitmapOp::GetBitmapPixels(CBitmap& bitmap) { HBITMAP hBmp; hBmp = HBITMAP(bitmap); //Sanity.. if (hBmp == NULL) return FALSE; // Create a memory DC inside which we will scan the bitmap content HDC hMemDC = CreateCompatibleDC(NULL); ASSERT(hMemDC); if (hMemDC) { // Get bitmap siz BITMAP bm; GetObject(hBmp, sizeof(bm), &bm); // Create a 32 bits depth bitmap and select it into the memory DC BITMAPINFOHEADER RGB32BITSBITMAPINFO = { sizeof(BITMAPINFOHEADER),// biSize bm.bmWidth, // biWidth; -bm.bmHeight, // biHeight 1, // biPlanes; 32, // biBitCount BI_RGB, // biCompression; 0, // biSizeImage; 0, // biXPelsPerMeter; 0, // biYPelsPerMeter; 0, // biClrUsed; 0 // biClrImportant; }; VOID * pbits32; HBITMAP hbm32 = CreateDIBSection( hMemDC, (BITMAPINFO *)&RGB32BITSBITMAPINFO, DIB_RGB_COLORS, &pbits32, NULL, 0); if (hbm32) { HBITMAP holdBmp = (HBITMAP)SelectObject(hMemDC, hbm32); // Create a DC just to copy the bitmap into the memory D HDC hDC = CreateCompatibleDC(hMemDC); if (hDC) { // Get how many bytes per row w //have for the bitmap bit //(rounded up to 32 bits BITMAP bm32; VERIFY(GetObject(hbm32, sizeof(bm32), &bm32)); while (bm32.bmWidthBytes % 4) bm32.bmWidthBytes++; // Copy the bitmap into the memory DC HBITMAP holdBmp = (HBITMAP)SelectObject(hDC, hBmp); VERIFY(BitBlt(hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDC, 0, 0, SRCCOPY)); //Scan each bitmap row from bottom to to //(the bitmap is inverted vertically BYTE *p32 = (BYTE *)bm32.bmBits + (bm32.bmHeight - 1) * bm32.bmWidthBytes; //Scan each bitmap pixel from left to right SetBitmapDataSize(bm.bmWidth, bm.bmHeight); for (int y = 0; y < bm.bmHeight; y++) { for (int x = 0; x < bm.bmWidth; x++) { int x0 = x; //Ptr to start of region to fil LONG *p = (LONG *)p32 + x; BitmapData[x][y] = RGB(GetBValue((DWORD)(*p)), GetGValue((DWORD)(*p)), GetRValue((DWORD)(*p))); } //end for // Go to next row //(remember, the bitmap is inverted vertically p32 -= bm32.bmWidthBytes; } //end for // Clean up SelectObject(hDC, holdBmp); DeleteDC(hDC); } DeleteObject(SelectObject(hMemDC, holdBmp)); } DeleteDC(hMemDC); } return TRUE; } void CBitmapOp::SetBitmapDataSize(const int x, const int y) { m_PixelNumberX = x; m_PixelNumberY = y; if ((x>=0) && (y>=0)) { BitmapData.resize(m_PixelNumberX); for(int x = 0; x < m_PixelNumberX; x++) BitmapData[x].resize(m_PixelNumberY); } } vector<vector<COLORREF> > CBitmapOp::GetBitmapArray() { return BitmapData; } |
CGraphCtrl
the part of the overloaded CStatic class responsible for loading and saving of the bitmap:BOOL CGraphCtrl::SaveBitmap(CString csFileName) { CPaintDC dc(this); PlotToDC(&dc); BOOL Result = BitmapOperations.SaveDcToBitmapFile((LPCTSTR)csFileName, &dc, GetSize()); return Result; } BOOL CGraphCtrl::LoadBitmap(CString csFileName) { BOOL Result = LoadBitmapData(csFileName); if (Result==FALSE) return Result; UpdatePlot(); return Result; } BOOL CGraphCtrl::LoadBitmapData(CString csFileName) { CBitmap DrawMeBmp; BOOL Result = BitmapOperations.LoadBitmapFileToCBitmap((LPCTSTR)csFileName, DrawMeBmp, NULL ); if (Result==FALSE) return Result; Result = BitmapOperations.GetBitmapPixels(DrawMeBmp); if (Result==FALSE) return Result; BITMAP bmpInfo; DrawMeBmp.GetBitmap(&bmpInfo); SetPlotDataSize(bmpInfo.bmWidth, bmpInfo.bmHeight); SetPlotData(BitmapOperations.GetBitmapArray()); return Result; } |
Any ideas, correction or help concerning this classes and problems is appreciated.
I do not know what an MFC CArray is. I would suggest to ask such questions somewhere else since I stopped working with MFC about a year after I had started working with it. It was one year of huge frustration. I recommend to use Qt or C#. Both provide the same functionality that requires in MFC 100 lines of code in a few lines of code.
Can I replace
vector<vector >
with an MFC CArray ? Is it possible ? How ?