我在纯Win32项目中有一个HBITMAP(没有使用外部库)。我可以只使用Winapi和/或CRT函数将其导出到*.bmp文件,这样我就不必为项目添加依赖关系了吗?
没有API可以直接保存到文件中,因为通常,拥有位图句柄并不意味着您可以直接访问位图数据。您的解决方案是通过数据访问(DIB)将位图复制到另一个位图中,然后使用它将数据写入文件。
您通常要么使用CreateDIBSection
创建另一个位图,要么使用GetDIBits
获得位图数据。
CreateFile
, WriteFile
写入文件
你写:BITMAPFILEHEADER
,然后BITMAPINFOHEADER
,然后调色板(当比特/像素超过8时你通常没有),然后是数据本身。
参见:
- c++: Hbitmap/BITMAP into .bmp文件(这个答案)
- 使用hBitmap = CreateDIBSection()在C Win32中保存。bmp文件
- 在MSDN上存储图像
这是来自MSDN文章的代码(注意您需要定义errhandler()
函数):
PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))
errhandler("GetObject", hwnd);
// Convert the color format to a count of bits.
cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24)
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * (1<< cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel
else
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24)
pbmi->bmiHeader.biClrUsed = (1<<cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
// The width must be DWORD aligned unless the bitmap is RLE
// compressed.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
* pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
void CreateBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi,
HBITMAP hBMP, HDC hDC)
{
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
pbih = (PBITMAPINFOHEADER) pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits)
errhandler("GlobalAlloc", hwnd);
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS))
{
errhandler("GetDIBits", hwnd);
}
// Create the .BMP file.
hf = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
(DWORD) 0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hf == INVALID_HANDLE_VALUE)
errhandler("CreateFile", hwnd);
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
(LPDWORD) &dwTmp, NULL))
{
errhandler("WriteFile", hwnd);
}
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, ( NULL)))
errhandler("WriteFile", hwnd);
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL))
errhandler("WriteFile", hwnd);
// Close the .BMP file.
if (!CloseHandle(hf))
errhandler("CloseHandle", hwnd);
// Free memory.
GlobalFree((HGLOBAL)lpBits);
}
HBITMAP
到*.bmp
文件的一个函数代码
BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName)
{
HDC hDC;
int iBits;
WORD wBitCount;
DWORD dwPaletteSize = 0, dwBmBitsSize = 0, dwDIBSize = 0, dwWritten = 0;
BITMAP Bitmap0;
BITMAPFILEHEADER bmfHdr;
BITMAPINFOHEADER bi;
LPBITMAPINFOHEADER lpbi;
HANDLE fh, hDib, hPal, hOldPal2 = NULL;
hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
DeleteDC(hDC);
if (iBits <= 1)
wBitCount = 1;
else if (iBits <= 4)
wBitCount = 4;
else if (iBits <= 8)
wBitCount = 8;
else
wBitCount = 24;
GetObject(hBitmap, sizeof(Bitmap0), (LPSTR)&Bitmap0);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = Bitmap0.bmWidth;
bi.biHeight = -Bitmap0.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = wBitCount;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrImportant = 0;
bi.biClrUsed = 256;
dwBmBitsSize = ((Bitmap0.bmWidth * wBitCount + 31) & ~31) / 8
* Bitmap0.bmHeight;
hDib = GlobalAlloc(GHND, dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;
hPal = GetStockObject(DEFAULT_PALETTE);
if (hPal)
{
hDC = GetDC(NULL);
hOldPal2 = SelectPalette(hDC, (HPALETTE)hPal, FALSE);
RealizePalette(hDC);
}
GetDIBits(hDC, hBitmap, 0, (UINT)Bitmap0.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)
+ dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS);
if (hOldPal2)
{
SelectPalette(hDC, (HPALETTE)hOldPal2, TRUE);
RealizePalette(hDC);
ReleaseDC(NULL, hDC);
}
fh = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (fh == INVALID_HANDLE_VALUE)
return FALSE;
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
GlobalUnlock(hDib);
GlobalFree(hDib);
CloseHandle(fh);
return TRUE;
}
另一个极简的选择是使用OLE的IPicture
。它一直存在,仍然是Win32 API的一部分:
#define _S(exp) (([](HRESULT hr) { if (FAILED(hr)) _com_raise_error(hr); return hr; })(exp));
PICTDESC pictdesc = {};
pictdesc.cbSizeofstruct = sizeof(pictdesc);
pictdesc.picType = PICTYPE_BITMAP;
pictdesc.bmp.hbitmap = hBitmap;
CComPtr<IPicture> picture;
_S( OleCreatePictureIndirect(&pictdesc, __uuidof(IPicture), FALSE, (LPVOID*)&picture) );
// Save to a stream
CComPtr<IStream> stream;
_S( CreateStreamOnHGlobal(NULL, TRUE, &stream) );
LONG cbSize = 0;
_S( picture->SaveAsFile(stream, TRUE, &cbSize) );
// Or save to a file
CComPtr<IPictureDisp> disp;
_S( picture->QueryInterface(&disp) );
_S( OleSavePictureFile(disp, CComBSTR("C:\Temp\File.bmp")) );
我将把这个自包含的概念证明留在这里,因为我可能需要稍后查找它,因为它不是很明显。它截取桌面窗口的截图,并将其保存为bitmap.bmp:
#include <Windows.h>
#include <stdio.h>
#include <assert.h>
/* forward declarations */
PBITMAPINFO CreateBitmapInfoStruct(HBITMAP);
void CreateBMPFile(LPTSTR pszFile, HBITMAP hBMP);
int main(int argc, char **argv);
PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
assert(GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp));
// Convert the color format to a count of bits.
cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24)
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * (1<< cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel
else
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24)
pbmi->bmiHeader.biClrUsed = (1<<cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
// The width must be DWORD aligned unless the bitmap is RLE
// compressed.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
* pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
void CreateBMPFile(LPTSTR pszFile, HBITMAP hBMP)
{
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
PBITMAPINFO pbi;
HDC hDC;
hDC = CreateCompatibleDC(GetWindowDC(GetDesktopWindow()));
SelectObject(hDC, hBMP);
pbi = CreateBitmapInfoStruct(hBMP);
pbih = (PBITMAPINFOHEADER) pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
assert(lpBits) ;
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
assert(GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS));
// Create the .BMP file.
hf = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
(DWORD) 0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
assert(hf != INVALID_HANDLE_VALUE) ;
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
assert(WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
(LPDWORD) &dwTmp, NULL));
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
assert(WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, ( NULL)));
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
assert(WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL));
// Close the .BMP file.
assert(CloseHandle(hf));
// Free memory.
GlobalFree((HGLOBAL)lpBits);
}
int main(int argc, char **argv)
{
HWND hwnd;
HDC hdc[2];
HBITMAP hbitmap;
RECT rect;
hwnd = GetDesktopWindow();
GetClientRect(hwnd, &rect);
hdc[0] = GetWindowDC(hwnd);
hbitmap = CreateCompatibleBitmap(hdc[0], rect.right, rect.bottom);
hdc[1] = CreateCompatibleDC(hdc[0]);
SelectObject(hdc[1], hbitmap);
BitBlt (
hdc[1],
0,
0,
rect.right,
rect.bottom,
hdc[0],
0,
0,
SRCCOPY
);
CreateBMPFile("bitmap.bmp", hbitmap);
return 0;
}
是的,使用Windows成像组件(WIC)这是可能的。WIC提供内置编码器,因此您不必手动编写位图头和数据。它还允许您选择不同的编码器(例如PNG),只需更改一行代码。
这个过程相当直接。它由以下步骤组成:
- 使用
GetObject
从源HBITMAP
检索属性(尺寸,位深度)。 - 创建
IWICImagingFactory
实例 - 从
HBITMAP
(IWICImagingFactory::CreateBitmapFromHBITMAP
)创建IWICBitmap
实例 - 创建
IWICStream
实例(IWICImagingFactory::CreateStream
),并将其附加到文件名(IWICStream::InitializeFromFilename
)。 - 创建
IWICBitmapEncoder
实例(IWICImagingFactory::CreateEncoder
),并将其与流(IWICBitmapEncoder::Initialize
)关联。 - 创建
IWICBitmapFrameEncode
实例(IWICBitmapEncoder::CreateNewFrame
),并按照源HBITMAP
(IWICBitmapFrameEncode::Initialize
,IWICBitmapFrameEncode::SetSize
,IWICBitmapFrameEncode::SetPixelFormat
)初始化。 - 将位图数据写入帧(
IWICBitmapFrameEncode::WriteSource
)。 - 提交帧和数据到流(
IWICBitmapFrameEncode::Commit
,IWICBitmapEncoder::Commit
)。
翻译成代码:
#define COBJMACROS
#include <Objbase.h>
#include <wincodec.h>
#include <Windows.h>
#include <Winerror.h>
#pragma comment(lib, "Windowscodecs.lib")
HRESULT WriteBitmap(HBITMAP bitmap, const wchar_t* pathname) {
HRESULT hr = S_OK;
// (1) Retrieve properties from the source HBITMAP.
BITMAP bm_info = { 0 };
if (!GetObject(bitmap, sizeof(bm_info), &bm_info))
hr = E_FAIL;
// (2) Create an IWICImagingFactory instance.
IWICImagingFactory* factory = NULL;
if (SUCCEEDED(hr))
hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
&IID_IWICImagingFactory, &factory);
// (3) Create an IWICBitmap instance from the HBITMAP.
IWICBitmap* wic_bitmap = NULL;
if (SUCCEEDED(hr))
hr = IWICImagingFactory_CreateBitmapFromHBITMAP(factory, bitmap, NULL,
WICBitmapIgnoreAlpha,
&wic_bitmap);
// (4) Create an IWICStream instance, and attach it to a filename.
IWICStream* stream = NULL;
if (SUCCEEDED(hr))
hr = IWICImagingFactory_CreateStream(factory, &stream);
if (SUCCEEDED(hr))
hr = IWICStream_InitializeFromFilename(stream, pathname, GENERIC_WRITE);
// (5) Create an IWICBitmapEncoder instance, and associate it with the stream.
IWICBitmapEncoder* encoder = NULL;
if (SUCCEEDED(hr))
hr = IWICImagingFactory_CreateEncoder(factory, &GUID_ContainerFormatBmp, NULL,
&encoder);
if (SUCCEEDED(hr))
hr = IWICBitmapEncoder_Initialize(encoder, (IStream*)stream,
WICBitmapEncoderNoCache);
// (6) Create an IWICBitmapFrameEncode instance, and initialize it
// in compliance with the source HBITMAP.
IWICBitmapFrameEncode* frame = NULL;
if (SUCCEEDED(hr))
hr = IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL);
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_Initialize(frame, NULL);
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_SetSize(frame, bm_info.bmWidth, bm_info.bmHeight);
if (SUCCEEDED(hr)) {
GUID pixel_format = GUID_WICPixelFormat24bppBGR;
hr = IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format);
}
// (7) Write bitmap data to the frame.
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_WriteSource(frame, (IWICBitmapSource*)wic_bitmap,
NULL);
// (8) Commit frame and data to stream.
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_Commit(frame);
if (SUCCEEDED(hr))
hr = IWICBitmapEncoder_Commit(encoder);
// Cleanup
if (frame)
IWICBitmapFrameEncode_Release(frame);
if (encoder)
IWICBitmapEncoder_Release(encoder);
if (stream)
IWICStream_Release(stream);
if (wic_bitmap)
IWICBitmap_Release(wic_bitmap);
if (factory)
IWICImagingFactory_Release(factory);
return hr;
}
下面是一个配套的测试应用程序来展示其用法。在包含任何系统头文件之前,请确保#define OEMRESOURCE
允许使用OBM_
图像。
int wmain(int argc, wchar_t** argv) {
HRESULT hr = S_OK;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr))
return -1;
HBITMAP bitmap = LoadImage(NULL, MAKEINTRESOURCE(OBM_ZOOM), IMAGE_BITMAP, 0, 0,
LR_DEFAULTCOLOR);
hr = WriteBitmap(bitmap, argv[1]);
// Cleanup
if (bitmap)
DeleteObject(bitmap);
CoUninitialize();
return 0;
}
这将加载系统提供的位图,并将其保存到命令行中作为参数指定的路径名中。
<标题>限制:- 不支持alpha通道。虽然位图版本5支持alpha通道,但我不知道有什么方法可以找出,
HBITMAP
是否指的是具有alpha通道的位图,我也不知道如何确定它是否预乘。如果你想支持alpha通道,请确保将EnableV5Header32bppBGRA
属性设置为VARIANT_TRUE
(参见BMP格式:编码)。 - 不支持码垛位图(bpp <= 8)。如果您正在处理码垛位图,请确保在调用
IWICImagingFactory::CreateBitmapFromHBITMAP
时提供适当的HPALETTE
。 - 编码器初始化为
GUID_WICPixelFormat24bppBGR
像素格式常量。更通用的实现将从源HBITMAP
推导出兼容的像素格式。