(为了"坑"不跨年,誓要把它填满 -_-" )

这个DIY类的关键核心就在那三个重载的CreateBmp()函数,它们使用BMP文件load出来的数据来创建内存位图
用于显示、绘图等,当然,也可以自定义一个空白的位图来操作,随你的便~~~
先看第一个简单的:
(BTW:以下为了简便,基本说明大部分直接插在代码里)

/* * 参数: * long lWidth - 指定的宽度 * long lHeight - 指定的高度 * * 返回值: * 成功就返回TRUE */ BOOL IBmp::CreateBmp( long lWidth, long lHeight ) { // get屏幕的DC HDC hDC = GetDC( NULL ); if( !hDC ){ // 失败了... TRACE0( "GetDC()居然会失败?饶了我吧!~~~ >_< ~~~ " ); return FALSE; } // 取系统色深 int nBPP = GetDeviceCaps( hDC, BITSPIXEL ); // 释放.. ReleaseDC( NULL, hDC ); // create ~ return CreateBmp( lWidth, lHeight, ( WORD )nBPP ); }

是的,这实际上只不过是获取了系统色深后,调用另一个重载函数来Create指定宽、高与系统色深一样的空白BMP。
现在就来看看这个被调用的同名函数:

/* * 参数: * long lWidth - 指定的宽度 * long lHeight - 指定的高度 * WORD wBPP - 指定的色深 * DWORD dwCompression - 指定的压缩标志,带默认值 = BI_RGB (注1) * LPRGBQUAD lpRGB - 指定的色彩表(colors table ),带默认值 = NULL * * 返回值: * 成功就返回TRUE * * 说明: * 注1:这个参数只是为了用来创建16Bit的5/6/5格式空白BMP,其它情况下全为BI_RGB * 请不要乱填什么BI_RLE4 & BI_RLE8之类的,那是无效的. ^_^ */ BOOL IBmp::CreateBmp( long lWidth, long lHeight, WORD wBPP, DWORD dwCompression/* = BI_RGB*/, LPRGBQUAD lpRGB/* = NULL*/ ) { switch( dwCompression ){ case BI_RGB : // 支持 break; case BI_BITFIELDS : // 支持(only 16bit) if( wBPP == 16 ) break; default : // 乱填 { TRACE0( "乱填?改回!" ); dwCompression = BI_RGB; } break; } // 先清空占用资源,当然了,没占用它就啥都不做的。 // (不清空占用资源的话,要么就泄漏,要么就崩溃!) CleanUp(); // 纠正这个值,防止填错...... // (也许有人可能会异想天开要create个:"2BPP"?) if( wBPP <= 1 ) wBPP = 1; else if( wBPP <= 4 ) wBPP = 4; else if( wBPP <= 8 ) wBPP = 8; else if( wBPP <= 16 ) wBPP = 16; else if( wBPP <= 24 ) wBPP = 24; else wBPP = 32; // 获取屏幕DC HDC hDC = GetDC( NULL ); if( !hDC ){ // 形式上要鉴定一下的. TRACE0( "不是吧?又来?放过我吧!!!" ); return FALSE; } DWORD dwNumColors = 0, dwImageSize, dwInfoSize; // 先计算发色数,从而得出整个BITMAPINFO的实际大小 if( wBPP <= 8 ){ // 假如是8Bit或以下的 // 使用默认发色数(全部) dwNumColors = 1 << wBPP; } else if( ( wBPP == 16 ) && ( dwCompression == BI_BITFIELDS ) ){ // 16bit 5-6-5 // 需要3个掩码 dwNumColors = 3; } // else = 0..上面早已经设定了 // 得到BITMAPINFO的实际字节数 dwInfoSize = sizeof( BITMAPINFOHEADER ) + dwNumColors * sizeof( RGBQUAD ); // BMP位数据的实际字节数 dwImageSize = WIDTHBYTES( abs(lWidth), wBPP ) * abs(lHeight); // 现在就需要根据实际所得创建一个BITMAPINFO,从而用 // 它来调用CreateDIBSection()函数来创建DIB句柄。 // 第一步:先构造一个BITMAPINFO然后填表 // 分配内存 lpBminfo = ( LPBITMAPINFO )new BYTE[dwInfoSize]; if( lpBminfo == NULL ){ // 内存分配失败 // 失败必须清空 TRACE0( "lpBminfo 内存分配失败!" ); ReleaseDC( NULL, hDC ); return FALSE; } // 成功后就需要正确地.."填表" lpBminfo->bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); lpBminfo->bmiHeader.biBitCount = wBPP; lpBminfo->bmiHeader.biWidth = lWidth; lpBminfo->bmiHeader.biHeight = lHeight; lpBminfo->bmiHeader.biCompression = dwCompression; lpBminfo->bmiHeader.biPlanes = 1; lpBminfo->bmiHeader.biClrUsed = 0; lpBminfo->bmiHeader.biClrImportant = 0; lpBminfo->bmiHeader.biXPelsPerMeter = 0; lpBminfo->bmiHeader.biYPelsPerMeter = 0; lpBminfo->bmiHeader.biSizeImage = dwImageSize; // 第二步:"填"完之后,还需要copy bmp的色彩表到BITMAPINFO(假如有色彩表), // 当然啦,那些不需要的自然会跳过这步。 if( ( wBPP == 16 ) && ( dwCompression == BI_BITFIELDS ) ){ // 16Bit : 5-6-5 // 这个不是有colors table,而是有bits mask,必须copy掩码才能正确显示 memcpy( ( LPDWORD )( ( LPBYTE )lpBminfo + sizeof( BITMAPINFOHEADER ) ), dwMask, sizeof( DWORD ) * 3 ); } else if( wBPP <= 8 ){ // 8Bit或以下,必须有色彩表(colors table) // 这个参数默认是"NULL",非NULL则说明给定了一个CT。 if( lpRGB != NULL ){ // copy memcpy( lpBminfo->bmiColors, lpRGB, dwNumColors * sizeof( RGBQUAD ) ); } else { // 没有指定 ( lpRGB = 默认:NULL ) // 假如没有给定的话,那就照旧...取系统的 PALETTEENTRY pe[256]; // 取系统调色板项 GetSystemPaletteEntries( hDC, 0, dwNumColors, pe ); // 然后用取得的调色板项来构造色彩表 for( DWORD i = 0; i < dwNumColors; ++i ){ lpBminfo->bmiColors[i].rgbBlue = pe[i].peBlue; lpBminfo->bmiColors[i].rgbGreen = pe[i].peGreen; lpBminfo->bmiColors[i].rgbRed = pe[i].peRed; lpBminfo->bmiColors[i].rgbReserved = 0; } } } // 正确完成前两步之后,现在就可以来创建我们的DIB句柄 hBitmap = CreateDIBSection( hDC, lpBminfo, DIB_RGB_COLORS, ( VOID ** )&lpBits, NULL, 0 ); if( !hBitmap ){ // 失败. // 失败当然就要清空 TRACE0( "错误:CreateBmp:CreateDIBSection()." ); CleanUp(); ReleaseDC( NULL, hDC ); return FALSE; } // 把dib句柄的位数据全置为0xFF( 默认白色 ) memset( lpBits, 0xFF, dwImageSize ); // Create完后,屏幕DC就要释放。 ReleaseDC( NULL, hDC ); // 当然,last : 还要create可有可无的那个~ if( !CreatePal() ){ // 失败 // 失败..照旧 TRACE0( "错误:CreateBmp:CreatePal()." ); CleanUp(); return FALSE; } // now all OK! return TRUE; }

貌似是个很长的函数,实质上流程简单:
鉴定输入参数-->根据所得创建BITMAPINFO-->调用CreateDIBSection()创建位图句柄,最后
Create Palette,就是那样!而其中有两点需要特别说明:

1: WIDTHBYTES()
WIDTHBYTES实际上是一个宏而不是个函数,可以在IBmp.h头部声明如下:
#define WIDTHBYTES(w,b) ((((w)*(b)+31)/32)*4)
如上,这个宏是用来计算BMP每行扫描线所占的字节数目,而行字节数*高度则得到位数据的大小。

2: dwMask
这个是16Bit(5-6-5)位图所需的3个DWORD掩码,可以在IBmp.h头部声明如下:
const DWORD dwMask[] = { 0xF800, 0x07E0, 0x001F };
由于5-6-5的掩码千篇一律,所以声明为常量。

接下来看看最后一个CreateBmp:

/* * 参数: * LPBYTE lpDib - 打包的DIB数据(BITMAPINFO+Bits data) * DWORD dwBitsOffset - 位数据的偏移量 * * 返回值: * 成功就返回TRUE */ BOOL IBmp::CreateBmp( LPBYTE lpDib, DWORD dwBitsOffset ) { if( !lpDib || !dwBitsOffset ){ // 稍作鉴定 TRACE0( "CreateBmp:输入参数为空." ); return FALSE; } // 一样... CleanUp(); // 先获取某些值域 LPBITMAPINFOHEADER lpBmih = ( LPBITMAPINFOHEADER )lpDib; // 计算发色数 DWORD dwNumColors = NumColorsEntry( lpBmih ); // 计算BITMAPINFO大小 DWORD dwInfoSize = sizeof( BITMAPINFOHEADER ) + dwNumColors * sizeof( RGBQUAD ); // 分配空间 lpBminfo = ( LPBITMAPINFO )new BYTE[dwInfoSize]; if( lpBminfo == NULL ){ // 失败了 TRACE0( "CreateBmp:lpBminfo分配失败." ); return FALSE; } // 计算好以后,就把该copy的copy到我们的BITMAPINFO memcpy( ( LPVOID )lpBminfo, ( LPVOID )lpDib, dwInfoSize ); // 取位数据的大小 DWORD dwImageSize = lpBminfo->bmiHeader.biSizeImage; if( !dwImageSize ){ // 没"填" // 自己计算自己填.. dwImageSize = WIDTHBYTES(abs(lpBminfo->bmiHeader.biWidth), lpBminfo->bmiHeader.biBitCount ) * abs(lpBminfo->bmiHeader.biHeight); lpBminfo->bmiHeader.biSizeImage = dwImageSize; } // now ok!,我们来create位图句柄 // 当然要先GetDC HDC hDC = GetDC( NULL ); if( !hDC ){ // 失败 // 照旧 TRACE0( "错误:CreateBmp:GetDC() fail." ); CleanUp(); return FALSE; } // create~ hBitmap = CreateDIBSection( hDC, lpBminfo, DIB_RGB_COLORS, ( VOID ** )&lpBits, NULL, 0 ); if( !hBitmap ){ // 失败. // 如常 TRACE0( "错误:CreateBmp:CreateDIBSection()." ); CleanUp(); ReleaseDC( NULL, hDC ); return FALSE; } // 最重要的一步:把位数据copy到位图句柄数据指针 memcpy( lpBits, lpDib + dwBitsOffset, dwImageSize ); // release ReleaseDC( NULL, hDC ); // last. if( !CreatePal() ){ // 失败 // 失败..照旧 TRACE0( "错误:CreateBmp:CreatePal()." ); CleanUp(); return FALSE; } // all ok! return TRUE; }

看完上一个再看这个的话,真的简单很多,这个只是利用输入的参数创建一个位图句柄与调色板,

而假如参数有问题的话,就可能失败或者搞个古怪的出来;因为它是在假设已经完全懂得使用的情

况下而设计的,所以并没作过多的鉴定。

看了那三个东西兼且了解了以后,就要利用所有的资源物力来打开/保存/显示BMP,现在就来看看打开的方法:

/* * 参数: * LPCTSTR lpszFileName - BMP的文件名(字符串) * * 返回值: * 成功就返回TRUE */ BOOL IBmp::Load( LPCTSTR lpszFileName ) { HANDLE hFile; LPBYTE lpDib = NULL; DWORD dwBitsOffset, dwFileSize ,dwRead; BITMAPFILEHEADER bmfh; // 打开文件 if( ( hFile = CreateFile( lpszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL ) ) == INVALID_HANDLE_VALUE ){ TRACE0( "打开BMP文件失败。" ); return FALSE; } // 以下略作鉴定 dwFileSize = GetFileSize( hFile, NULL ); if( !dwFileSize ){ TRACE0( "是个空文件?" ); CloseHandle( hFile ); return FALSE; } if( !ReadFile( hFile, ( LPBYTE )&bmfh, sizeof( BITMAPFILEHEADER ), &dwRead, NULL ) ){ TRACE0( "read file head fail." ); CloseHandle( hFile ); return FALSE; } if( dwRead != sizeof( BITMAPFILEHEADER ) || bmfh.bfType != 0x4d42 ){ TRACE0( "It's not a bmp file." ); CloseHandle( hFile ); return FALSE; } // 分配内存,"切"去位图文件头后全部load进 DWORD dwSize = bmfh.bfSize - sizeof( BITMAPFILEHEADER ); dwBitsOffset = bmfh.bfOffBits - sizeof( BITMAPFILEHEADER ); lpDib = ( LPBYTE )new BYTE[dwSize]; if( !lpDib ){ TRACE0( "内存分配失败." ); CloseHandle( hFile ); return FALSE; } if( !ReadFile( hFile, lpDib, dwSize, &dwRead, NULL ) ){ TRACE0( "Read file error!" ); delete [] lpDib; CloseHandle( hFile ); return FALSE; } LPBITMAPINFOHEADER lpBmih = ( LPBITMAPINFOHEADER )lpDib; if( lpBmih->biSize != sizeof( BITMAPINFOHEADER ) ){ TRACE0( "It's not a window's bmp!" ); delete [] lpDib; CloseHandle( hFile ); return FALSE; } // 调用CreateBmp来构造 BOOL bOK = CreateBmp( lpDib, dwBitsOffset ); // 当然要释放 delete [] lpDib; CloseHandle( hFile ); // 搞定 (指的是这个函数结束) return bOK; }

实际流程:用API CreateFile()打开文件后,稍作鉴定,便把BMP有用的部分load进内存,调用
上面那三个CreateBmp()的其中之一去构造我们需要的位图句柄。再看看保存的方法:

BOOL IBmp::Save( LPCTSTR lpszFileName ) { // 啥都没有的话,怎么保存. if( IsEmpty() ) return FALSE; HANDLE hFile; BITMAPFILEHEADER bmfh = { 0 }; DWORD dwWritten; // 计算某些数据 DWORD dwImageSize = lpBminfo->bmiHeader.biSizeImage; DWORD dwNumColors = NumColorsEntry( &lpBminfo->bmiHeader ); DWORD dwInfoSize = sizeof( BITMAPINFOHEADER ) + dwNumColors * sizeof( RGBQUAD ); // 打开文件 if( ( hFile = CreateFile( lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL ) ) == INVALID_HANDLE_VALUE ){ TRACE0( "Create BMP文件失败。" ); return FALSE; } // 正确填写位图文件头(BITMAPFILEHEADER) // bmp标记 bmfh.bfType = 0x4d42; // 恒为 : 0x4d42 ("BM") // bmp文件大小 bmfh.bfSize = sizeof( BITMAPFILEHEADER ) + dwInfoSize + dwImageSize; // bmp位数据偏移量 bmfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + dwInfoSize; // 写入! // 先写文件头 WriteFile( hFile, ( LPBYTE )&bmfh, sizeof( BITMAPFILEHEADER ), &dwWritten, NULL ); // 然后写位图信息 WriteFile( hFile, ( LPBYTE )lpBminfo, dwInfoSize, &dwWritten, NULL ); // 最后当然是写位数据 WriteFile( hFile, lpBits, dwImageSize, &dwWritten, NULL ); CloseHandle( hFile ); // 搞定 (指的是这个函数结束) return ( dwWritten != 0 ); }

超级简单:构造一个正确的文件头后,就连带BITMAPINFO和位数据一起写进个后缀名为bmp的文件中
就成为位图文件了;而要显示的话,就要使用Display函数咯:

void IBmp::Display( HDC hDC ) { HPALETTE hOldPal = SelectPalette( hDC, hPalette, FALSE ); RealizePalette( hDC ); HDC hDisplay = CreateCompatibleDC( hDC ); HBITMAP hOldBmp = ( HBITMAP )SelectObject( hDisplay, hBitmap ); BitBlt( hDC, 0, 0, lpBminfo->bmiHeader.biWidth, lpBminfo->bmiHeader.biHeight, hDisplay, 0, 0, SRCCOPY ); SelectObject( hDisplay, hOldBmp ); DeleteDC( hDisplay ); SelectPalette( hDC, hOldPal, FALSE ); }

只不过是实现调色板(for 8bits以下system)然后双缓冲来显示,最后释放资源,不多解释了。

为了争取一次填满这个"坑",继续看看最后两个函数,同时也是颇重要的两个函数:

HDC IBmp::BeginDraw() { HDC hDC = GetDC( NULL ); if( !hDC ) return NULL; // 内存DC hMemDC = CreateCompatibleDC( hDC ); // 选入DIBSECTION句柄 hTempBmp = ( HBITMAP )SelectObject( hMemDC, hBitmap ); // 实现调色板 hTempPal = SelectPalette( hMemDC, hPalette, FALSE ); RealizePalette( hMemDC ); // 释放 ReleaseDC( NULL, hDC ); // ok~ return hMemDC; }

另一个:

void IBmp::EndDraw() { if( hTempBmp != NULL ){ // 这个只会在作图时使用 SelectObject( hMemDC, hTempBmp ); // 选回 hTempBmp = NULL; } if( hTempPal != NULL ){ // 同上 SelectPalette( hMemDC, hTempPal, FALSE ); hTempPal = NULL; } if( hMemDC != NULL ){ // 同上 DeleteDC( hMemDC ); hMemDC = NULL; } GdiFlush(); }

BeginDraw()函数用私有成员Create个内存DC后,把位图句柄选入并实现调色板,然后return
给调用者来画图,而所有在上面画出来的DD都会直接反映到DIBSECTION的位数据指针:lpBits上,
也就是所谓的"所见即所得".

EndDraw()函数和BeginDraw()函数是一对"孖宝"函数,需要同时使用,例如:
===================================
IBmp bmp;
xxxxxxxxx;
// 开始gdi作图
HDC hDrawDC = bmp.BeginDraw();
SetText( hDrawDC, xxxxxx );
// 画完后必须要调用它释放
bmp.EndDraw();
===================================

最后,少不了帖上这个类完整的代码:

// IBmp.h: 头文件. #ifndef __H_IBMP_INCLUDE__ #define __H_IBMP_INCLUDE__ #define WIDTHBYTES(w,b) ((((w)*(b)+31)/32)*4) const DWORD dwMask[] = { 0xF800, 0x07E0, 0x001F }; class IBmp { public: // 装载BMP BOOL Load( LPCTSTR lpszFileName ); // 保存BMP BOOL Save( LPCTSTR lpszFileName ); // 显示BMP void Display( HDC hDC ); // 构造函数 IBmp(); // 析构函数 virtual ~IBmp(); // 顾名思义,就是create bmp咯. // 有这3个DD,create空白的行,create指定的也行,create from bmp buffer一样行. BOOL CreateBmp( long lWidth, long lHeight ); BOOL CreateBmp( long lWidth, long lHeight, WORD wBPP, DWORD dwCompression = BI_RGB, LPRGBQUAD lpRGB = NULL ); BOOL CreateBmp( LPBYTE lpDib, DWORD dwBitsOffset ); // 用来鉴定位图句柄是否为空,空的就不能显示、作图等. BOOL IsEmpty() { return ( BOOL )( hBitmap == NULL ); } // 初始化 void StartUp(); // 清空占用资源 void CleanUp(); // 计算发色数 DWORD NumColorsEntry( LPBITMAPINFOHEADER lpBmih ); // 开始gdi作图 HDC BeginDraw(); // 结束gdi作图 void EndDraw(); // 建调色板 BOOL CreatePal(); public: // 用来GDI画图&显示的位图句柄 HBITMAP hBitmap; // BMP信息部分 LPBITMAPINFO lpBminfo; // BMP的位数据指针 LPBYTE lpBits; // 调色板句柄 HPALETTE hPalette; private: // 用来作图的内存DC HDC hMemDC; // 用来保存临时句柄防止GDI资源泄漏 HBITMAP hTempBmp; // 用来保存临时句柄防止GDI资源泄漏 HPALETTE hTempPal; }; #endif // #ifndef __H_IBMP_INCLUDE__ ======================================================== // IBmp.cpp: 实现文件. #include "stdafx.h" #include "IBmp.h" IBmp::IBmp() { // 构造函数初始化变量 StartUp(); } IBmp::~IBmp() { // 调用CleanUp()来清空占用资源 CleanUp(); } void IBmp::StartUp() { hBitmap = hTempBmp = NULL; hPalette = hTempPal = NULL; lpBminfo = NULL; lpBits = NULL; hMemDC = NULL; } void IBmp::CleanUp() { // 函数释放所有占用资源 if( hTempBmp != NULL ){ // 这个只会在作图时使用 SelectObject( hMemDC, hTempBmp ); // 选回 hTempBmp = NULL; } if( hTempPal != NULL ){ // 同上 SelectPalette( hMemDC, hTempPal, FALSE ); hTempPal = NULL; } if( hMemDC != NULL ){ // 同上 DeleteDC( hMemDC ); hMemDC = NULL; } if( hBitmap != NULL ){ // bmp句柄 DeleteObject( hBitmap ); hBitmap = NULL; lpBits = NULL; } if( lpBminfo != NULL ){ delete [] ( LPBYTE )lpBminfo; lpBminfo = NULL; } if( hPalette != NULL ){ DeleteObject( hPalette ); hPalette = NULL; } } DWORD IBmp::NumColorsEntry( LPBITMAPINFOHEADER lpBmih ) { if( lpBmih->biBitCount <= 8 ){ // 8 bit or under // 当色深等于或低于8位时,这个值域有意义, // 决定位图的部分发色数。( = 0则是默认:全部 ) if( lpBmih->biClrUsed ) return ( DWORD )lpBmih->biClrUsed; } switch( lpBmih->biBitCount ){ case 1 : // 1 BPP return 2; case 4 : // 4 BPP return 16; case 8 : // 8 BPP return 256; case 16 : // 16 BPP case 32 : // 32 BPP if( lpBmih->biCompression == BI_BITFIELDS ) // need mask. return 3; default : return 0; } // note : 很多人都会无聊地把16bit或以上的biClrUsed值域 // 填上( = 1 << biBitCount; ),实际上是错误的, // 你永远不会知道,因为乱填会发生什么bug。 // 这个值域实际上在大于8bit时应该为0! } BOOL IBmp::CreatePal() { // 首先要看看是否Create了 if( hPalette != NULL ){ // 把它del掉 DeleteObject( hPalette ); hPalette = NULL; } // 开始Create palette,这里不管如何都弄个palette出来.. if( lpBminfo->bmiHeader.biBitCount > 8 ){ // 8Bit以上 // 8bit以上不需要(也没有)调色板,取系统缺省的 hPalette = ( HPALETTE )GetStockObject( DEFAULT_PALETTE ); } else { // 8Bit or lower // 先计算发色数 DWORD dwNumColors = NumColorsEntry( &lpBminfo->bmiHeader ); // 分配内存空间 LPLOGPALETTE lpPal = ( LPLOGPALETTE )new BYTE[sizeof( WORD ) * 2 + sizeof( PALETTEENTRY ) * dwNumColors]; // 版本 : 恒为0x300 lpPal->palVersion = 0x300; // 发色数 : 计算好的值 lpPal->palNumEntries = ( WORD )dwNumColors; // 用BMP的色彩表( colors table )来构造调色板各项颜色 for( DWORD i = 0; i < dwNumColors; ++i ){ lpPal->palPalEntry[i].peBlue = lpBminfo->bmiColors[i].rgbBlue; lpPal->palPalEntry[i].peGreen = lpBminfo->bmiColors[i].rgbGreen; lpPal->palPalEntry[i].peRed = lpBminfo->bmiColors[i].rgbRed; lpPal->palPalEntry[i].peFlags = 0; } // Create~~ hPalette = CreatePalette( ( CONST LOGPALETTE * )lpPal ); delete lpPal; } return ( hPalette != NULL ); } /* * 参数: * long lWidth - 指定的宽度 * long lHeight - 指定的高度 * * 返回值: * 成功就返回TRUE */ BOOL IBmp::CreateBmp( long lWidth, long lHeight ) { // get屏幕的DC HDC hDC = GetDC( NULL ); if( !hDC ){ // 失败了... TRACE0( "GetDC()居然会失败?饶了我吧!~~~ >_< ~~~ " ); return FALSE; } // 取系统色深 int nBPP = GetDeviceCaps( hDC, BITSPIXEL ); // 释放.. ReleaseDC( NULL, hDC ); // create ~ return CreateBmp( lWidth, lHeight, ( WORD )nBPP ); } /* * 参数: * long lWidth - 指定的宽度 * long lHeight - 指定的高度 * WORD wBPP - 指定的色深 * DWORD dwCompression - 指定的压缩标志,带默认值 = BI_RGB (注1) * LPRGBQUAD lpRGB - 指定的色彩表(colors table ),带默认值 = NULL * * 返回值: * 成功就返回TRUE * * 说明: * 注1:这个参数只是为了用来创建16Bit的5/6/5格式空白BMP,其它情况下全为BI_RGB * 请不要乱填什么BI_RLE4 & BI_RLE8之类的,那是无效的. ^_^ */ BOOL IBmp::CreateBmp( long lWidth, long lHeight, WORD wBPP, DWORD dwCompression/* = BI_RGB*/, LPRGBQUAD lpRGB/* = NULL*/ ) { switch( dwCompression ){ case BI_RGB : // 支持 break; case BI_BITFIELDS : // 支持(only 16bit) if( wBPP == 16 ) break; default : // 乱填 { TRACE0( "乱填?改回!" ); dwCompression = BI_RGB; } break; } // 先清空占用资源,当然了,没占用它就啥都不做的。 // (不清空占用资源的话,要么就泄漏,要么就崩溃!) CleanUp(); // 纠正这个值,防止填错...... // (也许有人可能会异想天开要create个:"2BPP"?) if( wBPP <= 1 ) wBPP = 1; else if( wBPP <= 4 ) wBPP = 4; else if( wBPP <= 8 ) wBPP = 8; else if( wBPP <= 16 ) wBPP = 16; else if( wBPP <= 24 ) wBPP = 24; else wBPP = 32; // 获取屏幕DC HDC hDC = GetDC( NULL ); if( !hDC ){ // 形式上要鉴定一下的. TRACE0( "不是吧?又来?放过我吧!!!" ); return FALSE; } DWORD dwNumColors = 0, dwImageSize, dwInfoSize; // 先计算发色数,从而得出整个BITMAPINFO的实际大小 if( wBPP <= 8 ){ // 假如是8Bit或以下的 // 使用默认发色数(全部) dwNumColors = 1 << wBPP; } else if( ( wBPP == 16 ) && ( dwCompression == BI_BITFIELDS ) ){ // 16bit 5-6-5 // 需要3个掩码 dwNumColors = 3; } // else = 0..上面早已经设定了 // 得到BITMAPINFO的实际字节数 dwInfoSize = sizeof( BITMAPINFOHEADER ) + dwNumColors * sizeof( RGBQUAD ); // BMP位数据的实际字节数 dwImageSize = WIDTHBYTES( abs(lWidth), wBPP ) * abs(lHeight); // 现在就需要根据实际所得创建一个BITMAPINFO,从而用 // 它来调用CreateDIBSection()函数来创建DIB句柄。 // 第一步:先构造一个BITMAPINFO然后填表 // 分配内存 lpBminfo = ( LPBITMAPINFO )new BYTE[dwInfoSize]; if( lpBminfo == NULL ){ // 内存分配失败 // 失败必须清空 TRACE0( "lpBminfo 内存分配失败!" ); ReleaseDC( NULL, hDC ); return FALSE; } // 成功后就需要正确地.."填表" lpBminfo->bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); lpBminfo->bmiHeader.biBitCount = wBPP; lpBminfo->bmiHeader.biWidth = lWidth; lpBminfo->bmiHeader.biHeight = lHeight; lpBminfo->bmiHeader.biCompression = dwCompression; lpBminfo->bmiHeader.biPlanes = 1; lpBminfo->bmiHeader.biClrUsed = 0; lpBminfo->bmiHeader.biClrImportant = 0; lpBminfo->bmiHeader.biXPelsPerMeter = 0; lpBminfo->bmiHeader.biYPelsPerMeter = 0; lpBminfo->bmiHeader.biSizeImage = dwImageSize; // 第二步:"填"完之后,还需要copy bmp的色彩表到BITMAPINFO(假如有色彩表), // 当然啦,那些不需要的自然会跳过这步。 if( ( wBPP == 16 ) && ( dwCompression == BI_BITFIELDS ) ){ // 16Bit : 5-6-5 // 这个不是有colors table,而是有bits mask,必须copy掩码才能正确显示 memcpy( ( LPDWORD )( ( LPBYTE )lpBminfo + sizeof( BITMAPINFOHEADER ) ), dwMask, sizeof( DWORD ) * 3 ); } else if( wBPP <= 8 ){ // 8Bit或以下,必须有色彩表(colors table) // 这个参数默认是"NULL",非NULL则说明给定了一个CT。 if( lpRGB != NULL ){ // copy memcpy( lpBminfo->bmiColors, lpRGB, dwNumColors * sizeof( RGBQUAD ) ); } else { // 没有指定 ( lpRGB = 默认:NULL ) // 假如没有给定的话,那就照旧...取系统的 PALETTEENTRY pe[256]; // 取系统调色板项 GetSystemPaletteEntries( hDC, 0, dwNumColors, pe ); // 然后用取得的调色板项来构造色彩表 for( DWORD i = 0; i < dwNumColors; ++i ){ lpBminfo->bmiColors[i].rgbBlue = pe[i].peBlue; lpBminfo->bmiColors[i].rgbGreen = pe[i].peGreen; lpBminfo->bmiColors[i].rgbRed = pe[i].peRed; lpBminfo->bmiColors[i].rgbReserved = 0; } } } // 正确完成前两步之后,现在就可以来创建我们的DIB句柄 hBitmap = CreateDIBSection( hDC, lpBminfo, DIB_RGB_COLORS, ( VOID ** )&lpBits, NULL, 0 ); if( !hBitmap ){ // 失败. // 失败当然就要清空 TRACE0( "错误:CreateBmp:CreateDIBSection()." ); CleanUp(); ReleaseDC( NULL, hDC ); return FALSE; } // 把dib句柄的位数据全置为0xFF( 默认白色 ) memset( lpBits, 0xFF, dwImageSize ); // Create完后,屏幕DC就要释放。 ReleaseDC( NULL, hDC ); // 当然,last : 还要create可有可无的那个~ if( !CreatePal() ){ // 失败 // 失败..照旧 TRACE0( "错误:CreateBmp:CreatePal()." ); CleanUp(); return FALSE; } // now all OK! return TRUE; } /* * 参数: * LPBYTE lpDib - 打包的DIB数据(BITMAPINFO+Bits data) * DWORD dwBitsOffset - 位数据的偏移量 * * 返回值: * 成功就返回TRUE */ BOOL IBmp::CreateBmp( LPBYTE lpDib, DWORD dwBitsOffset ) { if( !lpDib || !dwBitsOffset ){ // 稍作鉴定 TRACE0( "CreateBmp:输入参数为空." ); return FALSE; } // 一样... CleanUp(); // 先获取某些值域 LPBITMAPINFOHEADER lpBmih = ( LPBITMAPINFOHEADER )lpDib; // 计算发色数 DWORD dwNumColors = NumColorsEntry( lpBmih ); // 计算BITMAPINFO大小 DWORD dwInfoSize = sizeof( BITMAPINFOHEADER ) + dwNumColors * sizeof( RGBQUAD ); // 分配空间 lpBminfo = ( LPBITMAPINFO )new BYTE[dwInfoSize]; if( lpBminfo == NULL ){ // 失败了 TRACE0( "CreateBmp:lpBminfo分配失败." ); return FALSE; } // 计算好以后,就把该copy的copy到我们的BITMAPINFO memcpy( ( LPVOID )lpBminfo, ( LPVOID )lpDib, dwInfoSize ); // 取位数据的大小 DWORD dwImageSize = lpBminfo->bmiHeader.biSizeImage; if( !dwImageSize ){ // 没"填" // 自己计算自己填.. dwImageSize = WIDTHBYTES(abs(lpBminfo->bmiHeader.biWidth),lpBminfo->bmiHeader.biBitCount) * abs(lpBminfo->bmiHeader.biHeight); lpBminfo->bmiHeader.biSizeImage = dwImageSize; } // now ok!,我们来create位图句柄 // 当然要先GetDC HDC hDC = GetDC( NULL ); if( !hDC ){ // 失败 // 照旧 TRACE0( "错误:CreateBmp:GetDC() fail." ); CleanUp(); return FALSE; } // create~ hBitmap = CreateDIBSection( hDC, lpBminfo, DIB_RGB_COLORS, ( VOID ** )&lpBits, NULL, 0 ); if( !hBitmap ){ // 失败. // 如常 TRACE0( "错误:CreateBmp:CreateDIBSection()." ); CleanUp(); ReleaseDC( NULL, hDC ); return FALSE; } // 最重要的一步:把位数据copy到位图句柄数据指针 memcpy( lpBits, lpDib + dwBitsOffset, dwImageSize ); // release ReleaseDC( NULL, hDC ); // last. if( !CreatePal() ){ // 失败 // 失败..照旧 TRACE0( "错误:CreateBmp:CreatePal()." ); CleanUp(); return FALSE; } // all ok! return TRUE; } /* * 参数: * LPCTSTR lpszFileName - BMP的文件名(字符串) * * 返回值: * 成功就返回TRUE */ BOOL IBmp::Load( LPCTSTR lpszFileName ) { HANDLE hFile; LPBYTE lpDib = NULL; DWORD dwBitsOffset, dwFileSize ,dwRead; BITMAPFILEHEADER bmfh; // 打开文件 if( ( hFile = CreateFile( lpszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL ) ) == INVALID_HANDLE_VALUE ){ TRACE0( "打开BMP文件失败。" ); return FALSE; } // 以下略作鉴定 dwFileSize = GetFileSize( hFile, NULL ); if( !dwFileSize ){ TRACE0( "是个空文件?" ); CloseHandle( hFile ); return FALSE; } if( !ReadFile( hFile, ( LPBYTE )&bmfh, sizeof( BITMAPFILEHEADER ), &dwRead, NULL ) ){ TRACE0( "read file head fail." ); CloseHandle( hFile ); return FALSE; } if( dwRead != sizeof( BITMAPFILEHEADER ) || bmfh.bfType != 0x4d42 ){ TRACE0( "It's not a bmp file." ); CloseHandle( hFile ); return FALSE; } // 分配内存,"切"去位图文件头后全部load进 DWORD dwSize = bmfh.bfSize - sizeof( BITMAPFILEHEADER ); dwBitsOffset = bmfh.bfOffBits - sizeof( BITMAPFILEHEADER ); lpDib = ( LPBYTE )new BYTE[dwSize]; if( !lpDib ){ TRACE0( "内存分配失败." ); CloseHandle( hFile ); return FALSE; } if( !ReadFile( hFile, lpDib, dwSize, &dwRead, NULL ) ){ TRACE0( "Read file error!" ); delete [] lpDib; CloseHandle( hFile ); return FALSE; } LPBITMAPINFOHEADER lpBmih = ( LPBITMAPINFOHEADER )lpDib; if( lpBmih->biSize != sizeof( BITMAPINFOHEADER ) ){ TRACE0( "It's not a window's bmp!" ); delete [] lpDib; CloseHandle( hFile ); return FALSE; } // 调用CreateBmp来构造 BOOL bOK = CreateBmp( lpDib, dwBitsOffset ); // 当然要释放 delete [] lpDib; CloseHandle( hFile ); // 搞定 (指的是这个函数结束) return bOK; } BOOL IBmp::Save( LPCTSTR lpszFileName ) { // 啥都没有的话,怎么保存. if( IsEmpty() ) return FALSE; HANDLE hFile; BITMAPFILEHEADER bmfh = { 0 }; DWORD dwWritten; // 计算某些数据 DWORD dwImageSize = lpBminfo->bmiHeader.biSizeImage; DWORD dwNumColors = NumColorsEntry( &lpBminfo->bmiHeader ); DWORD dwInfoSize = sizeof( BITMAPINFOHEADER ) + dwNumColors * sizeof( RGBQUAD ); // 打开文件 if( ( hFile = CreateFile( lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL ) ) == INVALID_HANDLE_VALUE ){ TRACE0( "Create BMP文件失败。" ); return FALSE; } // 正确填写位图文件头(BITMAPFILEHEADER) // bmp标记 bmfh.bfType = 0x4d42; // 恒为 : 0x4d42 ("BM") // bmp文件大小 bmfh.bfSize = sizeof( BITMAPFILEHEADER ) + dwInfoSize + dwImageSize; // bmp位数据偏移量 bmfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + dwInfoSize; // 写入! // 先写文件头 WriteFile( hFile, ( LPBYTE )&bmfh, sizeof( BITMAPFILEHEADER ), &dwWritten, NULL ); // 然后写位图信息 WriteFile( hFile, ( LPBYTE )lpBminfo, dwInfoSize, &dwWritten, NULL ); // 最后当然是写位数据 WriteFile( hFile, lpBits, dwImageSize, &dwWritten, NULL ); CloseHandle( hFile ); // 搞定 (指的是这个函数结束) return ( dwWritten != 0 ); } void IBmp::Display( HDC hDC ) { HPALETTE hOldPal = SelectPalette( hDC, hPalette, FALSE ); RealizePalette( hDC ); HDC hDisplay = CreateCompatibleDC( hDC ); HBITMAP hOldBmp = ( HBITMAP )SelectObject( hDisplay, hBitmap ); BitBlt( hDC, 0, 0, lpBminfo->bmiHeader.biWidth, lpBminfo->bmiHeader.biHeight, hDisplay, 0, 0, SRCCOPY ); SelectObject( hDisplay, hOldBmp ); DeleteDC( hDisplay ); SelectPalette( hDC, hOldPal, FALSE ); } HDC IBmp::BeginDraw() { HDC hDC = GetDC( NULL ); if( !hDC ) return NULL; // 内存DC hMemDC = CreateCompatibleDC( hDC ); // 选入DIBSECTION句柄 hTempBmp = ( HBITMAP )SelectObject( hMemDC, hBitmap ); // 实现调色板 hTempPal = SelectPalette( hMemDC, hPalette, FALSE ); RealizePalette( hMemDC ); // 释放 ReleaseDC( NULL, hDC ); // ok~ return hMemDC; } void IBmp::EndDraw() { if( hTempBmp != NULL ){ // 这个只会在作图时使用 SelectObject( hMemDC, hTempBmp ); // 选回 hTempBmp = NULL; } if( hTempPal != NULL ){ // 同上 SelectPalette( hMemDC, hTempPal, FALSE ); hTempPal = NULL; } if( hMemDC != NULL ){ // 同上 DeleteDC( hMemDC ); hMemDC = NULL; } GdiFlush(); }

以上,就是这个类的大概样子,已经拥有强大的基本功能,你可以用这个打开(保存)BMP,并且可以在上面写写画画,

这里附带一个下载连接: http://blogimg.chinaunix.net/blog/upfile2/100101020020.rar

是使用这个类的简单实现,内里只加了两个功能:

1: 将BMP垂直反转; 展示了如何直接操作这个类的位图句柄(DIBSECTION)的位数据指针来改变图象.

2: 画橡皮筋直线; 展示了如何直接操作这个类来用作GDI作图.

btw: 实际上可以把垂直反转等功能封装在类内的,不过为了不破坏原来的设计,所以在doc内实现.

当然,"地基"已经打好,"万长高楼"由你起,其它什么什么的,就靠你自己去如何如何了,hoho~~

tips: 实际上可以添加一大堆Getxxxx内联函数,就不用麻烦地写xxxxxxx->xxxxxxxx->xxxxxx.xxxxxx 之类的语句了. 例如: clase IBmp { public: long GetWidth(); xxxx xxx xx }; inline long IBmp::GetWidth() { return lpBminfo->bmiHeader.biWidth; } 那么以后只需写: m_pBmp->GetWidth(); 是否简单很多呢.. 当然,还可以重载"="号/构造函数等,可以更方便使用这个类,例如重载构造函数: class IBmp { public IBmp( LPCTSTR lpszFileName ); IBmp( long lWidth, long lHeight ); }; 怎么实现?简单,这里写一个: IBmp::IBmp( long lWidth, long lHeight ) { StartUp(); CreateBmp( lWidth, lHeight ); } 以后只需要直接写 : IBmp *m_pBmp = new IBmp( xxx, xxx ); or IBmp bmp( "hello.bmp" ); N容易是不?

OK, 限于本人的水平,假如有不合理的地方或者有错有bug的话,请多多包涵! 顺祝新年快乐,happy new year, byebye~~

Lambochan 2009/12/31

按:10年前的Blog被辣鸡CSDN和谐成这样了,弄了第一篇后就懒得再弄,由它吧  >T_T<

Lambochan 2019/4/26

DIY BMP类 (接续)相关推荐

  1. DIY定制类产品设计和汇出系统

    <DIY定制类产品设计和汇出系统>(Web版)是一款适用于做DIY定制类产品的团队的多用户在线协作系统,可以管理素材产品模板.素材图片,违禁词,尺寸.颜色等基础数据,根据素材产品模板和素材 ...

  2. DIY时钟类--广州百田笔试之一

    2014.05.30 武汉华科大活 题目:(原题不记得,大概回忆)用户输入一个时间,输出下一个时间 这个小题看似不难,实际处理起来对date的处理稍微繁琐,每月有30,31,28,29(闰年的判断)天 ...

  3. 10行代码DIY一个类USB Rubber Ducky来遥控自己电脑

    文章目录 一.前言 二.完整代码 三.制作方法 四.实验结果 五.结语 一.前言 shineblink core 开发板(简称Core)能够很方便的变成USB Slave设备,并且内部集成HID协议, ...

  4. 纯C++实现24位bmp格式图片的读取和修饰

    问题:现有一张bmp图片,要求将它读取到程序中并进行灰度化.水平翻转.模糊.茶色滤镜四种效果的一种,并输出新图片,如下所示: 命令行输入: 其中: 参数1:-b/g/s/r,先后表示blur(模糊), ...

  5. C# GDAL 数字图像处理Part2 Band合成BMP

    在成功读取dataset之后,我们应该进行图像的显示,究竟如何把三个Band读取出来合成为标准的BMP图像呢?(读取dataset见:C# GDAL 数字图像处理Part1 安装以及读取Dataset ...

  6. c++ 24位bmp格式分析

    问题:现有一张bmp图片,要求将它读取到程序中并进行灰度化.水平翻转.模糊.茶色滤镜四种效果的一种,并输出新图片,如下所示: 命令行输入: 其中: 参数1:-b/g/s/r,先后表示blur(模糊), ...

  7. java osgi web开发_基于 OSGi 和 Spring 开发 Web 应用

    开发一个简单的OSGi Web应用实例 一个简单的Web应用 我们写一个简单的 Web 应用 compute.html :计算两个数字的和或乘积.如下图所示: 图1.一个简单例子 一个简单例子.bmp ...

  8. tomcat(15)Digester库

    [0]README 0.1)本文部分文字描述转自 "how tomcat works",旨在学习 "tomcat(15)Digester库" 的基础知识: 2) ...

  9. EJB(RMI学习)

    RMI 1. RMI-IIOP 2. RMI 是在java中使用remote method invocation的最初的方法,RMI使用java.rmi包 RMI-IIOP 是RMI的一个特殊版本,R ...

最新文章

  1. PHP极其强大的图片处理库Grafika详细教程(3):图像属性处理
  2. UA MATH571A ANCOVA简介
  3. MySQL STR_TO_DATE函数
  4. 云计算将为移动医疗带来哪些新机遇?
  5. sql profiler_这是SQL Profiler的结尾吗?
  6. python控制浏览器模拟鼠标点击网页标题_如何使用python来模拟鼠标点击(将经过实例自动化模拟在360浏览器中自动搜索python)...
  7. 两种改变 Windows Vista UI语言的途径
  8. 58java面试题_58到家,java开发实际面试题
  9. 虚拟机架设 ftp 服务器 pureadmin,使用pure-ftpd搭建ftp服务器(简单实现被动模式)...
  10. 个人网页(项目)源码解析「HTML+CSS+JS」
  11. 高等教育中的人工智能市场现状研究分析报告-
  12. [已解决]VitrualBox 启动linux虚拟机后,无法访问网络解决方法
  13. 纯前端实现验证码功能
  14. 关于fastapi框架的异步
  15. mcnpf5输出结果_MCNP入门教程
  16. 悼念贝娜齐尔#183;布托
  17. 【English】Day 2 大英读写2 U1 A 7.11.12 翻译
  18. 以手机为基础的移动互联网对我们生活的影响
  19. 技术人员谈管理之企业组织文化漫谈
  20. 【OKR案例合集】财务部门及公司级 OKR 案例合集

热门文章

  1. {ResponseInfo:com.qiniu.http.Response@62bd765,status:400, reqId:d4kAAACMt2hWMSEW, xlog:X-Log, xvia:,
  2. 华为ENSP实验--基础
  3. 【Go】基于telegraf进行自定义插件开发(一)
  4. 本地html播放器代码,HTML播放器代码集
  5. Adobe登陆出现Access denied解决方法
  6. 介绍两种Revit绘制斜墙的方法及快速【梁随斜板】
  7. php逐个汉字遍历字符串
  8. Unity 实现游戏中多相机屏幕CutIn效果
  9. 基于BP神经网络的PID控制,神经网络算法pid控制
  10. 在线时间戳格式化转换工具