C++: Hbitmap/BITMAP в файл.bmp
Хорошо, вся история в том, что я пытаюсь использовать Leptonica+Tesseract OCR в C++, чтобы сделать скриншот, сохранить его в файл *.bmp, а затем загрузить его обратно в OCR. Мне не нужно будет делать это часто, но так как я не могу скопировать данные скриншота непосредственно в структуру Leptonica PIX, мне нужно сначала сохранить их в файл... на самом деле решение этого вопроса было бы предпочтительным.
Вот некоторый код, который я нашел в Интернете, пытаясь помочь мне.
Крышка экрана:
HBITMAP ScreenCapture(){
int width=100;
int height=100;
// get the device context of the screen
HDC hScreenDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
// and a device context to put it in
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
int x = GetDeviceCaps(hScreenDC, HORZRES);
int y = GetDeviceCaps(hScreenDC, VERTRES);
// maybe worth checking these are positive values
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, x, y);
// get a new bitmap
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
BitBlt(hMemoryDC, 0, 0, width, height, hScreenDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
//GlobalAlloc(GPTR, hBitmap)
WriteDIB(L"test.bmp", (HGLOBAL)hBitmap);
// clean up
DeleteDC(hMemoryDC);
DeleteDC(hScreenDC);
return hBitmap;
// now your image is held in hBitmap. You can save it or do whatever with it
}
Попытка написать функцию:
BOOL WriteDIB( LPTSTR szFile, HANDLE hDIB)
{
cout<<endl<<"Running save function";
/*HANDLE hDIB=GlobalAlloc(GPTR, sizeof(hDIBtochange));//this doesn't work, the result is four. Also the HANDLE parameter's name would be changed to hDIBtochange, so that the rest of the function uses the old 'hDIB' throughout
cout<<endl<<sizeof(hDIBtochange);*/
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 = 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, GlobalSize(hDIB) );
return TRUE;
}
Бесстыдно переписывался с постов людей годами. Хорошо! Проблема, с которой я сталкиваюсь, заключается в том, что я не могу понять, как GlobalAlloc Hbitmap превращается в глобально доступный дескриптор, который можно конвертировать или использовать с LPBITMAPINFOHEADER. Как только создается lpbi, каждое поле внутри него вызывает ошибку "Не удается прочитать память" в отладке Visual Studio 2012. Это недоступно, несмотря на то, что создано.
Решения.. Перейдите прямо из screencap в PIX, внутри памяти.. Найдите способ сохранить в растровое изображение и периодически создавайте их для чтения.. Найдите другой способ полностью, который имеет больше смысла..
Предпочитаю сначала, но, я прошу решение в этом, второму или третьему.
Если вам нужна дополнительная информация, я могу попытаться предоставить ее. В основном это сводится к тому, что "я никогда раньше не писал подобный код, и его не учили на уроках, поэтому я стараюсь учиться на ходу".
3 ответа
Гораздо более простой способ сохранить HBITMAP в файл - использовать GDI+. Это дает вам преимущество в том, что вы можете сохранять в любом формате, который изначально поддерживает Windows, и в то же время освобождает вас от необходимости играть с различными форматами изображений или даже разбираться в них.
В приведенном ниже примере я только что использовал LoadImage как быстрый и грязный способ загрузки уже существующего изображения - вы можете просто использовать HBITMAP, который вы уже захватили.
Вот пример, который загружает растровое изображение и сохраняет его снова. (Первоначально я использовал "image/png" в качестве типа вывода вместе с соответствующим именем файла вывода)
#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
UINT num = 0; // number of image encoders
UINT size = 0; // size of the image encoder array in bytes
ImageCodecInfo* pImageCodecInfo = NULL;
GetImageEncodersSize(&num, &size);
if(size == 0)
return -1; // Failure
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
if(pImageCodecInfo == NULL)
return -1; // Failure
GetImageEncoders(num, size, pImageCodecInfo);
for(UINT j = 0; j < num; ++j)
{
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
{
*pClsid = pImageCodecInfo[j].Clsid;
free(pImageCodecInfo);
return j; // Success
}
}
free(pImageCodecInfo);
return -1; // Failure
}
int main()
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
HBITMAP hBitmap = (HBITMAP)LoadImage(GetModuleHandle(NULL), "babe.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
Bitmap *image = new Bitmap(hBitmap, NULL);
CLSID myClsId;
int retVal = GetEncoderClsid(L"image/bmp", &myClsId);
image->Save(L"output.bmp", &myClsId, NULL);
delete image;
GdiplusShutdown(gdiplusToken);
return 0;
}
Недавно мне пришлось сделать то же самое, что вы делаете, и успешно использовать GlobalAlloc.
Основой этого кода является статья MSDN.
Похоже, вы получили свой пример кода отсюда.
MSDN действительно надежен для работы с win32, определенно предпочитаю его другим сайтам в моей работе.
Кажется, что происходит то, что sizeof(hDIBtochange)
возвращает 4, так что вы выделяете только 4 байта памяти. что было бы недостаточно, чтобы держать pbi
состав.
Вот мой код с GlobalAlloc
который, надеюсь, покажет правильное использование.
void
WriteBmpTofile(const bool remote, LPSTR pszFile, PBITMAPINFO pbi, HBITMAP hBmp, HDC hDC)
{
HANDLE hFile;
BITMAPFILEHEADER hdr;
PBITMAPINFOHEADER pbih;
LPBYTE lpBits;
DWORD dwTemp;
pbih = (PBITMAPINFOHEADER)pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if(!lpBits)
{
return; // could not allocate bitmap
}
GetDIBits(hDC, hBmp, 0, (WORD)pbih->biHeight, lpBits, pbi, DIB_RGB_COLORS);
hFile = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(hFile == INVALID_HANDLE_VALUE)
{
return; // Could not open screenshot file
}
// type == BM
hdr.bfType = 0x4d42;
hdr.bfSize = (sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
hdr.bfOffBits = sizeof(BITMAPFILEHEADER) + pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
// write the bitmap file header to file
WriteFile(hFile, (LPVOID)&hdr, sizeof(BITMAPFILEHEADER), &dwTemp, NULL);
// write the bitmap header to file
WriteFile(hFile, (LPVOID)pbih, sizeof(BITMAPINFOHEADER) + pbih->biClrUsed * sizeof(RGBQUAD), &dwTemp, NULL);
// copy the bitmap colour data into the file
WriteFile(hFile, (LPSTR)lpBits, pbih->biSizeImage, &dwTemp, NULL);
CloseHandle(hFile);
GlobalFree((HGLOBAL)lpBits);
}
Вот верхняя функция в той статье MSDN, если она вам нужна (снова изменена мной).
PBITMAPINFO
Print::CreateBitmapInfo(HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
GetObject(hBmp, sizeof(BITMAP), &bmp);
pbmi = static_cast<PBITMAPINFO>(LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER)));
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes; // we are assuming that there is only one plane
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
// no compression this is an rgb bitmap
pbmi->bmiHeader.biCompression = BI_RGB;
// calculate size and align to a DWORD (8bit), we are assuming there is only one plane.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * bmp.bmBitsPixel +31) & -31) * pbmi->bmiHeader.biHeight;
// all device colours are important
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
Я предполагаю, что вы получили свой код отсюда. Хранение изображения. Некоторое время назад мне пришлось изменить код для работы с WinCE 5.0 и WinCE 6.0. Вот бета-образец, хотя он немного грязный. Это делает это без GlobalAlloc. Вместо этого он использует CreateDibSection.
int CreateBMPFile(HWND hwnd, LPCTSTR 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;
int ret = 0;
pbi = CreateBitmapInfoStruct(NULL, hBMP);
if(pbi == NULL)
{
return ret;
}
pbih = (PBITMAPINFOHEADER) pbi;
/*
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits)
{
//errhandler("GlobalAlloc", hwnd);
return;
}
*/
RGBQUAD *rgbq;
rgbq = pbi->bmiColors;
PALETTEENTRY pe[256];
GetSystemPaletteEntries(hDC, 0, pbih->biClrUsed, pe);
for(DWORD i = 0; i < pbih->biClrUsed; i++)
{
rgbq[i].rgbRed = pe[i].peRed;
rgbq[i].rgbBlue = pe[i].peBlue;
rgbq[i].rgbGreen = pe[i].peGreen;
rgbq[i].rgbReserved = 0;
}
// CE5.0 + CE6.0
HDC tHDC;
tHDC = CreateCompatibleDC(hDC);
HBITMAP h = CreateDIBSection(hDC, pbi, DIB_PAL_COLORS, (void **)&hp, NULL, 0);
if(h == NULL)
{
goto close_bmp;
}
SelectObject(tHDC, h);
BitBlt(tHDC, 0, 0, SCREEN_W, SCREEN_H, hDC, 0, 0, SRCCOPY);
/*
// 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);
return;
}
*/
// 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);
goto close_bmp;
}
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);
goto close_bmp;
}
// 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);
goto close_bmp;
}
close_bmp:
// Close the .BMP file.
if(hf != INVALID_HANDLE_VALUE)
{
if (!CloseHandle(hf))
{
//errhandler("CloseHandle", hwnd);
}
else
{
ret = 1;
}
}
// Free memory.
// GlobalFree((HGLOBAL)lpBits);
if(tHDC != NULL)
DeleteObject(tHDC);
if(h != NULL)
DeleteObject(h);
if(pbi != NULL)
{
//LocalFree(pbi);
free(pbi);
}
return ret;
}