位图和调色板
自然界中的所有颜色都可以由红、绿、蓝(R,G,B)三基色组合而成。针对含有红、绿、蓝色成分的多少,
可以对其分别分成0~255个等级,而红、绿、蓝的不同组合共有256×256×256种,因此约能表示1600万种颜色。
对每个像素进行了(R,G,B)量化的图像就是位图,其在计算机中对应文件的扩展名一般为.bmp
调色板横空的功能在于缓解位图文件存储空间过大的问题, 完全利用(R,G,B)组合来存储一个800×600的位图所需要的空间为
800×600×3 = 1440000(字节)= 1.37M(字节), 假设位图为16色, 只需要用4个bit就可以存储这个位图的每个像素在16
种颜色中所处的等级,然后调色板提供了这16种等级对应的(R,G,B)值,这样,存储这个16色位图只需要:
800×600×4/8 = 240000(字节)= 0.22 M(字节)
额外的存储R,G,B表的开销(即调色板Palette,也称为颜色查找表LUT)仅仅为16×3=48字节
常见的位图有单色、16色、256色、16位及24位真彩色5种,对于前三者(即不大于256色)都可以调色板方式进行存储,
而对16位及24位真彩色以调色板进行存储是不划算的,它们直接按照R,G,B分量进行存储。
DDB位图(Device-dependent bitmap,与设备相关的位图)
它只能存在于内存中(视频内存或系统内存),其颜色模式必须与特定的输出设备相一致,使用系统调色板。
在256色以下的位图中存储的像素值是系统调色板的索引,其颜色依赖于系统调色板。
一般只能载入色彩较简单的DDB位图,对于颜色较丰富的位图,需使用DIB才能长期保存。
DIB位图(Device-independent bitmap,与设备无关的位图)
不依赖于具体设备,可以用来永久性地保存图象。DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.DIB文件中。
DIB位图的特点是将颜色信息储存在位图文件自身的颜色表中,应用程序要根据此颜色表为DIB创建逻辑调色板。
因此,在输出一幅DIB位图之前,程序应该将其逻辑调色板选入到相关的设备上下文并实现到系统调色板中。
DIB位图编程
DIB位图文件的格式。位图文件分为四部分:
(1)位图文件头BITMAPFILEHEADER是一个结构体,长度为14字节,定义为:
typedef struct tagBITMAPFILEHEADER { WORD bfType; //文件类型,必须是0x424D,即字符串"BM" DWORD bfSize; //文件大小,包括BITMAPFILEHEADER的14个字节 WORD bfReserved1; //保留字 WORD bfReserved2; //保留字 DWORD bfOffBits; //从文件头到实际的位图数据的偏移字节数 } BITMAPFILEHEADER;
(2)位图信息头BITMAPINFOHEADER也是一个结构体,长度为40字节,定义为:
typedef struct tagBITMAPINFOHEADER { DWORD biSize; //本结构的长度,为40 LONG biWidth; //图象的宽度,单位是象素 LONG biHeight; //图象的高度,单位是象素 WORD biPlanes; //必须是1 WORD biBitCount; //表示颜色时要用到的位数,1(单色), 4(16色), 8(256色), 24(真彩色) DWORD biCompression; //指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS等,BI_RGB表示不压缩 DWORD biSizeImage; //实际的位图数据占用的字节数,即 biSizeImage=biWidth’ × biHeight,biWidth’是biWidth 按照4的整倍数调整后的结果 LONG biXPelsPerMeter; //目标设备的水平分辨率,单位是每米的象素个数 LONG biYPelsPerMeter; //目标设备的垂直分辨率,单位是每米的象素个数 DWORD biClrUsed; //位图实际用到的颜色数,0表示颜色数为2biBitCount DWORD biClrImportant; //位图中重要的颜色数,0表示所有颜色都重要 } BITMAPINFOHEADER;
当biCompression成员的值是BI_RGB时,它没有调色板。
16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。这种格式也被称作555 16位位图。
当biCompression成员的值是BI_BITFIELDS,那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。
分别用于描述红、绿、蓝分量在16位中所占的位置。在Windows 95(或98)中,系统可接受两种格式的位域:555和565;
在555格式下,红、绿、蓝的掩码分别是:0x7C00、0x03E0、0x001F,
在565格式下,红、绿、蓝的掩码分别为:0xF800、0x07E0、0x001F。
(3)调色板Palette
调色板Palette针对的是需要调色板的位图,即单色、16色和256色位图。对于不以调色板方式存储的位图,则无此项信息。
调色板是一个数组,共有biClrUsed个元素(如果该值为0,则有2biBitCount个元素)。数组中每个元素是一个RGBQUAD结构体,长度为4个字节,定义为:
typedef struct tagRGBQUAD { BYTE rgbBlue; //蓝色分量 BYTE rgbGreen; //绿色分量 BYTE rgbRed; //红色分量 BYTE rgbReserved; //保留值 } RGBQUAD;
(4)实际的位图数据ImageDate
对于用到调色板的位图,实际的图象数据ImageDate为该象素的颜色在调色板中的索引值;对于真彩色图,图象数据则为实际的R、G、B值:
a.单色位图:用1bit就可以表示象素的颜色索引值;
b.16色位图:用4bit可以表示象素的颜色索引值;
c.256色位图:1个字节表示1个象素的颜色索引值;
d.真彩色:3个字节表示1个象素的颜色R,G,B值。
此外,位图数据每一行的字节数必须为4的整倍数,如果不是,则需要补齐。
奇怪的是,位图文件中的数据是从下到上(而不是从上到下)、从左到右方式存储的。
BMP图像数据的存储顺序是由下往上、由左向右(即图像上下颠倒存储),而图像的宽度(以字节为单位)必须是4的倍数,
倘若不到4的倍数则必须补足,并且图像的数据及其调色板数据存储格式为BGRBGR而不是一般习惯的RGBRGB
here are two varieties of DIBs:
HBITMAP与BITMAP的区别
typedef struct tagBITMAP { /* bm */ int bmType; int bmWidth; int bmHeight; int bmWidthBytes; BYTE bmPlanes; BYTE bmBitsPixel; LPVOID bmBits; } BITMAP;
BITMAP 是一个结构, HBITMAP 是一个指向BITMAP的句柄
There are two types of DDBs: discardable and nondiscardable.
A discardable DDB is a bitmap that the system discards if the bitmap is not selected into a DC and if system memory is low.
The CreateDiscardableBitmap function creates discardable bitmaps.
The CreateBitmap, CreateCompatibleBitmap, and CreateBitmapIndirect functions create nondiscardable bitmaps.
An application can create a DDB from a DIB by
initializing the required structures and calling the CreateDIBitmap function.
Specifying CBM_INIT in the call to CreateDIBitmap is equivalent
1) to calling the CreateCompatibleBitmap function to create a DDB in the format of the device and then
2) calling the SetDIBits function to translate the DIB bits to the DDB.
To determine whether a device supports the SetDIBits function,
call the GetDeviceCaps function, specifying RC_DI_BITMAP as the RASTERCAPS flag.
An application can create a DIB from a DDB by
1) initializing the required structures and
2) calling the GetDIBits function.
To determine whether a device supports this function,
call the GetDeviceCaps function, specifying RC_DI_BITMAP as the RASTERCAPS flag.
The following structures are used with bitmaps:
The following functions are used with bitmaps.
Function | Description |
---|---|
AlphaBlend | Displays a bitmap with transparent or semitransparent pixels. |
BitBlt | Performs a bit-block transfer. |
CreateBitmap | Creates a bitmap. |
CreateBitmapIndirect | Creates a bitmap. |
CreateCompatibleBitmap | Creates a bitmap compatible with a device. |
CreateDIBitmap | Creates a device-dependent bitmap (DDB) from a DIB. |
CreateDIBSection | Creates a DIB that applications can write to directly. |
ExtFloodFill | Fills an area of the display surface with the current brush. |
GetBitmapDimensionEx | Gets the dimensions of a bitmap. |
GetDIBColorTable | Retrieves RGB color values from a DIB section bitmap. |
GetDIBits | Copies a bitmap into a buffer. |
GetPixel | Gets the RGB color value of the pixel at a given coordinate. |
GetStretchBltMode | Gets the current stretching mode. |
GradientFill | Fills rectangle and triangle structures. |
LoadBitmap | Loads a bitmap from a module's executable file. |
MaskBlt | Combines the color data in the source and destination bitmaps. |
PlgBlt | Performs a bit-block transfer. |
SetBitmapDimensionEx | Sets the preferred dimensions to a bitmap. |
SetDIBColorTable | Sets RGB values in a DIB. |
SetDIBits | Sets the pixels in a bitmap using color data from a DIB. |
SetDIBitsToDevice | Sets the pixels in a rectangle using color data from a DIB. |
SetPixel | Sets the color for a pixel. |
SetPixelV | Sets a pixel to the best approximation of a color. |
SetStretchBltMode | Sets the bitmap stretching mode. |
StretchBlt | Copies a bitmap and stretches or compresses it. |
StretchDIBits | Copies the color data in a DIB. |
TransparentBlt | Performs a bit-block transfer of color data. |
The following functions are provided only for compatibility with 16-bit versions of Microsoft Windows:
// DDBToDIB - Creates a DIB from a DDB // bitmap - Device dependent bitmap // dwCompression - Type of compression - see BITMAPINFOHEADER // pPal - Logical palette HANDLE 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; }
function DIBToDDB( hDIB : THandle ) : HBitmap; var lpbi : PBitmapInfoHeader; hbm : HBitmap; Pal, OldPal : HPalette; dc : HDC; nSize : Word; pLP : PLogPalette; nColors, i : Integer; lpDIBBits : Pointer; bmInfo : PBitmapInfo; bmicoloraddr : PChar; bmisum : PChar; bmisumncolor : PChar; begin if ( hDIB = 0 ) then begin DIBToDDB := 0; exit; end; dc := GetDC( 0 ); Pal := 0; lpbi := PBitmapInfoHeader( hDIB ); if ( lpbi^.biClrUsed > 0 ) then nColors := lpbi^.biClrUsed else nColors := 1 shl lpbi^.biBitCount; bmicoloraddr := PChar( @( bmInfo^.bmiColors ) ); bmisum := bmicoloraddr + ( bmInfo^.bmiHeader.biClrUsed * sizeof( DWORD ) ); if bmInfo^.bmiHeader.biCompression = BI_BITFIELDS then bmisum := bmisum + ( 3 * sizeof( DWORD ) ); bmisumncolor := bmicoloraddr + ( nColors * sizeof( DWORD ) ); if bmInfo^.bmiHeader.biBitCount > 8 then lpDIBBits := Pointer( bmisum ) else lpDIBBits := Pointer( bmisumncolor ); if ( nColors <= 256 and ( GetDeviceCaps( dc, RASTERCAPS ) and RC_PALETTE ) ) then begin // Create and select a logical palette if needed nSize := sizeof( TLogPalette ) + ( sizeof( TPaletteEntry ) * nColors ); GetMem( pLP, nSize ); pLP^.palVersion := $0300; pLP^.palNumEntries := nColors; for i := 0 to nColors do begin 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; end; Pal := CreatePalette( pLP^ ); FreeMem( pLP ); OldPal := SelectPalette( dc, Pal, False ); // select and realize the palette RealizePalette( dc ); end; hbm := CreateDIBitmap( dc, ( PBitmapInfoHeader( lpbi ) )^, LongInt( CBM_INIT ), lpDIBBits, ( PBitmapInfo( lpbi ) )^, DIB_RGB_COLORS ); if Pal <> 0 then SelectPalette( dc, OldPal, False ); ReleaseDC( 0, dc ); Result := hbm; end;
function DDBToDIB( hbmp : HBitmap; bits : UINT ) : THandle; var hDIB : THandle; dc : HDC; bmp : BITMAP; wLineLen : UINT; dwSize : DWORD; wColSize : DWORD; lpbi : PBitmapInfoHeader; lpBits : ^Byte; begin // Create a DIB that we can write to the AVI stream. GetObject( hbmp, sizeof( BITMAP ), @bmp ); wLineLen := ( bmp.bmWidth * bits + 31 ) div 32 * 4; if bits <= 8 then wColSize := sizeof( RGBQUAD ) * ( 1 shl bits ) else wColSize := 0; dwSize := sizeof( TBitmapInfoHeader ) + wColSize + wLineLen * bmp.bmHeight; hDIB := GlobalAlloc( GHND, dwSize ); if hDIB = 0 then begin Result := hDIB; exit; end; lpbi := GlobalLock( hDIB ); lpbi^.biSize := sizeof( BITMAPINFOHEADER ); lpbi^.biWidth := bmp.bmWidth; lpbi^.biHeight := bmp.bmHeight; lpbi^.biPlanes := 1; lpbi^.biBitCount := bits; lpbi^.biCompression := BI_RGB; lpbi^.biSizeImage := dwSize - sizeof( BITMAPINFOHEADER ) - wColSize; lpbi^.biXPelsPerMeter := 0; lpbi^.biYPelsPerMeter := 0; if bits <= 8 then lpbi^.biClrUsed := 1 shl bits else lpbi^.biClrUsed := 0; lpbi^.biClrImportant := 0; lpBits := Pointer( Integer( lpbi ) + sizeof( TBitmapInfoHeader ) + wColSize ); dc := CreateCompatibleDC( 0 ); GetDIBits( dc, hbmp, 0, bmp.bmHeight, lpBits, PBitmapInfo( lpbi )^, DIB_RGB_COLORS ); if bits <= 8 then lpbi^.biClrUsed := 1 shl bits else lpbi^.biClrUsed := 0; DeleteDC( dc ); GlobalUnlock( hDIB ); Result := hDIB; end; procedure SaveDibToFile( hDIB : THandle; FileName : string ); var bmfh : BITMAPFILEHEADER; lbmpHdr : PBitmapInfoHeader; Afile : HFILE; Aof : TOFSTRUCT; begin lbmpHdr := GlobalLock( hDIB ); bmfh.bfType := $4D42; // 'BM'for bitmap*/ bmfh.bfSize := sizeof( BITMAPINFOHEADER ) + sizeof( BITMAPFILEHEADER ) + lbmpHdr.biClrUsed * 4 + lbmpHdr.biSizeImage; bmfh.bfReserved1 := 0; bmfh.bfReserved2 := 0; bmfh.bfOffBits := sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER ) + lbmpHdr.biClrUsed * 4; Afile := OpenFile( PChar( FileName ), Aof, OF_CREATE or OF_WRITE ); _lwrite( Afile, PChar( @bmfh ), sizeof( BITMAPFILEHEADER ) ); _lwrite( Afile, PChar( lbmpHdr ), GlobalSize( hDIB ) ); _lclose( Afile ); end;