C++ GDI+ SelectPalette - c++

I am playing with GDI+. Trying to use
pDC->SelectPalette(CPalette::FromHandle(hLogPal), FALSE);
pDC->RealizePalette();
instead of
memcpy(newBitmapInfo + sizeof(BITMAPINFO), rgbquad, palettesize);
But it seem that with it's working with memcpy(newBitmapInfo + sizeof(BITMAPINFO), rgbquad, palettesize); but with SelectPalette only black screen.
I thought that information about color can be used from bitmapinfo or from pallet.
All code:
void ConvertTo8BitImage(BYTE** pBitmapInfo, BYTE** imageData)
{
Gdiplus::GdiplusStartupInput tmp;
ULONG_PTR token;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);
Gdiplus::Bitmap *source = Gdiplus::Bitmap::FromFile(L"D:/TestImage.bmp");
Gdiplus::Bitmap *destination = source->Clone(0, 0, source->GetWidth(), source->GetHeight(),
PixelFormat8bppIndexed);
int width = source->GetWidth();
int height = source->GetHeight();
HBITMAP hBitmap;
Gdiplus::Color color;
destination->GetHBITMAP(color, &hBitmap);
int palettesize = 256 * sizeof(RGBQUAD);
CLSID clsid_bmp;
CLSIDFromString(L"{557cf400-1a04-11d3-9a73-0000f81ef32e}", &clsid_bmp);
*pBitmapInfo = new BYTE[(sizeof(BITMAPINFO) + palettesize)];
BITMAPINFO* ptr = (BITMAPINFO*)*pBitmapInfo;
ptr->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
ptr->bmiHeader.biWidth = width;
ptr->bmiHeader.biHeight = height;
ptr->bmiHeader.biPlanes = 1;
ptr->bmiHeader.biBitCount = 8;
ptr->bmiHeader.biCompression = BI_RGB;
ptr->bmiColors[0].rgbRed = 0;
DWORD size = ((width * 8 + 31) / 32) * 4 * height;
*imageData = new BYTE[size];
HDC hdc = GetDC(0);
GetDIBits(hdc, hBitmap, 0, height, *imageData, (BITMAPINFO*)*pBitmapInfo, DIB_PAL_COLORS);
ReleaseDC(0, hdc);
Gdiplus::GdiplusShutdown(token);
}
void CMFCApplicationColorsView::OnDraw(CDC* pDC)
{
CMFCApplicationColorsDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
BYTE *bitmapInfo = NULL;
BYTE *imageData = NULL;
ConvertTo8BitImage(&bitmapInfo, &imageData);
int palettesize = 256 * sizeof(RGBQUAD);
BYTE *newBitmapInfo = new BYTE[(sizeof(BITMAPINFO) + palettesize)];
ZeroMemory(newBitmapInfo, (sizeof(BITMAPINFO) + palettesize));
BITMAPINFO *ptr = (BITMAPINFO*)newBitmapInfo;
ptr->bmiHeader.biBitCount = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biBitCount;
ptr->bmiHeader.biClrImportant = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biClrImportant;
ptr->bmiHeader.biClrUsed = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biClrUsed;
ptr->bmiHeader.biCompression = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biCompression;
ptr->bmiHeader.biHeight = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biHeight;
ptr->bmiHeader.biPlanes = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biPlanes;
ptr->bmiHeader.biSize = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biSize;
ptr->bmiHeader.biSizeImage = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biSizeImage;
ptr->bmiHeader.biWidth = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biWidth;
ptr->bmiHeader.biXPelsPerMeter = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biXPelsPerMeter;
ptr->bmiHeader.biYPelsPerMeter = ((BITMAPINFO*)bitmapInfo)->bmiHeader.biYPelsPerMeter;
ptr->bmiColors[0] = ((BITMAPINFO*)bitmapInfo)->bmiColors[0];
RGBQUAD rgbquad[256];
memcpy(rgbquad, bitmapInfo + sizeof(BITMAPINFO), palettesize);
//memcpy(newBitmapInfo + sizeof(BITMAPINFO), rgbquad, palettesize);
NPLOGPALETTE pPal = (NPLOGPALETTE)LocalAlloc(LMEM_FIXED,
(sizeof(LOGPALETTE) +
(sizeof(PALETTEENTRY) * (palettesize))));
pPal->palVersion = 0x300;
pPal->palNumEntries = 256;
for (int i = 0; i < 256; i++)
{
pPal->palPalEntry[i].peRed = rgbquad[i].rgbRed;
pPal->palPalEntry[i].peGreen = rgbquad[i].rgbGreen;
pPal->palPalEntry[i].peBlue = rgbquad[i].rgbBlue;
pPal->palPalEntry[i].peFlags = 0;
}
HPALETTE hLogPal = CreatePalette((LPLOGPALETTE)pPal);
pDC->SelectPalette(CPalette::FromHandle(hLogPal), FALSE);
pDC->RealizePalette();
StretchDIBits(pDC->GetSafeHdc(), 0, 0, 1920, 1080, 0, 0, 1920, 1080,
imageData, ptr, DIB_PAL_COLORS, SRCCOPY);
delete[] bitmapInfo;
delete[] imageData;
}

HBITMAP hBitmap;
Gdiplus::Color color;
destination->GetHBITMAP(color, &hBitmap);
You did convert to 8-bit bitmap, however GetHBITMAP will return a bitmap handle compatible with your video card, which is probably 32-bit. GDI+ has already processed the palette and returned a bitmap handle which is turned back in to 32-bit. HBITMAP handle can be painted directly, for example using CreateCompatibleDC and BitBlt. So there is no need to obtaining the palette and passing it to GDI, and no need for 8-bit conversion in the first place.
If this is necessary for some reason, you can get the bits and palette from 32-bit bitmap, put that in 8-bit bitmap, and draw with StretchDIBits
The main issue in your code is that it should use DIB_RGB_COLORS flag for GetDIBits/StretchDIBits, because the device context is most likely 32-bit. There is no need for SelectPalette/RealizePalette either (unless it's 8-bit display from 30 years ago)
It makes more sense to get the bits directly from GDI+ using LockBits, and get the palette directly using GetPalette, as seen in the example below.
Aside, source and destination have to be deleted before exit.
void draw(HDC hdc)
{
Gdiplus::Bitmap *source = Gdiplus::Bitmap::FromFile(L"D:/TestImage.bmp");
if(!source)
return;
int width = source->GetWidth();
int height = source->GetHeight();
Gdiplus::Bitmap *destination = source->Clone(0, 0, width, height,
PixelFormat8bppIndexed);
//get bitmap bits from GDI+
Gdiplus::BitmapData data;
Gdiplus::Rect rect(0, 0, width, height);
destination->LockBits(&rect, Gdiplus::ImageLockModeRead,
destination->GetPixelFormat(), &data);
int bufsize = data.Stride * data.Height;
BYTE *buf = new BYTE[bufsize];
memcpy(buf, data.Scan0, bufsize);
destination->UnlockBits(&data);
//setup BITMAPINFO
int bmpinfo_size = sizeof(BITMAPINFO) + 256 * 4;
BITMAPINFO* bmpinfo = (BITMAPINFO*)new BYTE[bmpinfo_size];
memset(bmpinfo, 0, bmpinfo_size);
bmpinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpinfo->bmiHeader.biWidth = width;
bmpinfo->bmiHeader.biHeight = -height;
bmpinfo->bmiHeader.biPlanes = 1;
bmpinfo->bmiHeader.biBitCount = 8;
bmpinfo->bmiHeader.biCompression = BI_RGB;
bmpinfo->bmiHeader.biSizeImage = bufsize;
//get palette from GDI+
int palsize = destination->GetPaletteSize();
Gdiplus::ColorPalette *palette = (Gdiplus::ColorPalette*)new BYTE[palsize];
destination->GetPalette(palette, palsize);
//set palette for BITMAPINFO
memset(&bmpinfo->bmiColors[0], 0, 256 * 4);
for(int i = 0; i < palette->Count; i++)
{
auto clr = Gdiplus::Color(palette->Entries[i]);
bmpinfo->bmiColors[i].rgbRed = clr.GetR();
bmpinfo->bmiColors[i].rgbGreen = clr.GetG();
bmpinfo->bmiColors[i].rgbBlue = clr.GetB();
bmpinfo->bmiColors[i].rgbReserved = 0;
}
StretchDIBits(hdc, 0, 0, width, height, 0, 0, width, height,
buf, bmpinfo, DIB_RGB_COLORS, SRCCOPY);
delete[] buf;
delete[] bmpinfo;
delete[] palette;
delete destination;
delete source;
}
void CMFCApplicationColorsView::OnDraw(CDC* pDC)
{
CMFCApplicationColorsDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
Gdiplus::GdiplusStartupInput tmp;
ULONG_PTR token;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);
draw(pDC->GetSafeHdc());
Gdiplus::GdiplusShutdown(token);
}

Related

Create Device Context with Icon and transparent background

I'm trying to get a image with HICON but the background is not transparent. How can I make it transparent? I need a winapi example because the code is in Dart, but it has all the windows calls/functions.
I've tried different version I've found on the internet but it didn't worked. I can access icon mask if that can help for a solution.
Current code:
var icon = SendMessage(hWnd, WM_GETICON, 2, 0); // ICON_SMALL2 - User Made Apps
if (icon == 0) icon = GetClassLongPtr(hWnd, -14); // GCLP_HICON - Microsoft Win Apps
final int hScreen = GetDC(hWnd);
final int hDC = CreateCompatibleDC(hScreen);
final int hBitmap = CreateCompatibleBitmap(hScreen, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
SelectObject(hDC, hBitmap);
// SetBkMode(hDC, TRANSPARENT); - Works for text only
// PatBlt(hDC, 0, 0, 545, 850, WHITENESS); - only white/black;
DrawIconEx(hDC, 0, 0, icon, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), NULL, NULL, 3);
The icon is transparent:
Even if I don't draw the icon, the output is a black square.
Can you suggest how to remove the background? In basic winapi calls. The code can be in cpp if it doesn't use special classes from libraries, I can use only dllCalls
Here is full working code:
// ignore_for_file: depend_on_referenced_packages, non_constant_identifier_names, avoid_print, unrelated_type_equality_checks
import 'dart:ffi';
import 'dart:io';
import 'package:win32/win32.dart';
import 'package:ffi/ffi.dart';
int enumWindowsProc(int hWnd, int lparam) {
if (IsWindowVisible(hWnd) == FALSE) return TRUE;
final length = GetWindowTextLength(hWnd);
if (length == 0) return TRUE;
var icon = SendMessage(hWnd, WM_GETICON, 2, 0); // ICON_SMALL2 - User Made Apps
if (icon == 0) icon = GetClassLongPtr(hWnd, -14); // GCLP_HICON - Microsoft Win Apps
if (icon == 0) {
icon = 0;
return 1;
}
final int hScreen = GetDC(hWnd);
final int hDC = CreateCompatibleDC(hScreen);
final int hBitmap = CreateCompatibleBitmap(hScreen, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
SelectObject(hDC, hBitmap);
SetBkMode(hDC, TRANSPARENT); //- Works for text only
PatBlt(hDC, 0, 0, GetSystemMetrics(SM_CXICON) ~/ 2, GetSystemMetrics(SM_CYICON), WHITENESS); // test, half white half black.
DrawIconEx(hDC, 0, 0, icon, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), NULL, NULL, 3);
//Turn to bytes
final bmpScreen = calloc<BITMAP>();
GetObject(hBitmap, sizeOf<BITMAP>(), bmpScreen);
final bitmapFileHeader = calloc<BITMAPFILEHEADER>();
final bitmapInfoHeader = calloc<BITMAPINFOHEADER>()
..ref.biSize = sizeOf<BITMAPINFOHEADER>()
..ref.biWidth = bmpScreen.ref.bmWidth
..ref.biHeight = bmpScreen.ref.bmHeight
..ref.biPlanes = 1
..ref.biBitCount = 32
..ref.biCompression = BI_RGB;
final dwBmpSize = ((bmpScreen.ref.bmWidth * bitmapInfoHeader.ref.biBitCount + 31) / 32 * 4 * bmpScreen.ref.bmHeight).toInt();
final lpBitmap = calloc<Uint8>(dwBmpSize);
GetDIBits(hDC, hBitmap, 0, bmpScreen.ref.bmHeight, lpBitmap, bitmapInfoHeader.cast(), DIB_RGB_COLORS);
final dwSizeOfDIB = dwBmpSize + sizeOf<BITMAPFILEHEADER>() + sizeOf<BITMAPINFOHEADER>();
bitmapFileHeader.ref.bfOffBits = sizeOf<BITMAPFILEHEADER>() + sizeOf<BITMAPINFOHEADER>();
bitmapFileHeader.ref.bfSize = dwSizeOfDIB;
bitmapFileHeader.ref.bfType = 0x4D42; // BM
var b = BytesBuilder();
b.add(Pointer<Uint8>.fromAddress(bitmapFileHeader.address).asTypedList(sizeOf<BITMAPFILEHEADER>()));
b.add(Pointer<Uint8>.fromAddress(bitmapInfoHeader.address).asTypedList(sizeOf<BITMAPINFOHEADER>()));
b.add(lpBitmap.asTypedList(dwBmpSize));
// I need the Bitmap in Bytes, I save it to file just for debugging.
//capture?.icon = b.takeBytes();
//
DeleteDC(hDC);
DeleteObject(hBitmap);
free(bmpScreen);
free(bitmapFileHeader);
free(bitmapInfoHeader);
free(lpBitmap);
Directory current = Directory.current;
File("${current.path}/imgs/i_${icon.toString()}.bmp").writeAsBytes(b.takeBytes());
return 1;
}
void main() {
final imgs = "${Directory.current.path}/imgs";
if (Directory(imgs).exists() == true) {
Directory(imgs).deleteSync(recursive: true);
}
final wndProc = Pointer.fromFunction<EnumWindowsProc>(enumWindowsProc, 0);
EnumWindows(wndProc, 0);
}
final _user32 = DynamicLibrary.open('user32.dll');
int DrawIconEx(int hdc, int xLeft, int yTop, int hIcon, int cxWidth, int cyWidth, int istepIfAniCur, int hbrFlickerFreeDraw, int diFlags) =>
_DrawIconEx(hdc, xLeft, yTop, hIcon, cxWidth, cyWidth, istepIfAniCur, hbrFlickerFreeDraw, diFlags);
final _DrawIconEx = _user32.lookupFunction<
Int32 Function(IntPtr hdc, Int32 xLeft, Int32 yTop, IntPtr hIcon, Int32 cxWidth, Int32 cyWidth, Uint32 istepIfAniCur, IntPtr hbrFlickerFreeDraw, Uint32 diFlags),
int Function(int hdc, int xLeft, int yTop, int hIcon, int cxWidth, int cyWidth, int istepIfAniCur, int hbrFlickerFreeDraw, int diFlags)>('DrawIconEx');
final _gdi32 = DynamicLibrary.open('gdi32.dll');
int PatBlt(int hdc, int x, int y, int w, int h, int rop) => _PatBlt(hdc, x, y, w, h, rop);
final _PatBlt =
_gdi32.lookupFunction<Int32 Function(IntPtr hdc, Int32 x, Int32 y, Int32 w, Int32 h, Uint32 rop), int Function(int hdc, int x, int y, int w, int h, int rop)>('PatBlt');
Found a even better solution, works with buffer and write to file, alpha channel and everything :)
https://github.com/pelayomendez/exe-icon-extractor/blob/master/src/module.cc
#include <tchar.h>
#include <iostream>
#include <windows.h>
#include <fstream>
#include <cassert>
using namespace std;
// Check windows
#if _WIN32 || _WIN64
#if _WIN64
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
// Check GCC
#if __GNUC__
#if __x86_64__ || __ppc64__
#define ENV64BIT
#else
#define ENV32BIT
#endif
#endif
typedef struct
{
WORD idReserved; // must be 0
WORD idType; // 1 = ICON, 2 = CURSOR
WORD idCount; // number of images (and ICONDIRs)
// ICONDIR [1...n]
// ICONIMAGE [1...n]
} ICONHEADER;
//
// An array of ICONDIRs immediately follow the ICONHEADER
//
typedef struct
{
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes; // for cursors, this field = wXHotSpot
WORD wBitCount; // for cursors, this field = wYHotSpot
DWORD dwBytesInRes;
DWORD dwImageOffset; // file-offset to the start of ICONIMAGE
} ICONDIR;
//
// After the ICONDIRs follow the ICONIMAGE structures -
// consisting of a BITMAPINFOHEADER, (optional) RGBQUAD array, then
// the color and mask bitmap bits (all packed together
//
typedef struct
{
BITMAPINFOHEADER biHeader; // header for color bitmap (no mask header)
//RGBQUAD rgbColors[1...n];
//BYTE bXOR[1]; // DIB bits for color bitmap
//BYTE bAND[1]; // DIB bits for mask bitmap
} ICONIMAGE;
//
// Return the number of BYTES the bitmap will take ON DISK
//
static UINT NumBitmapBytes(BITMAP* pBitmap)
{
int nWidthBytes = pBitmap->bmWidthBytes;
// bitmap scanlines MUST be a multiple of 4 bytes when stored
// inside a bitmap resource, so round up if necessary
if (nWidthBytes & 3)
nWidthBytes = (nWidthBytes + 4) & ~3;
return nWidthBytes * pBitmap->bmHeight;
}
static BOOL GetIconBitmapInfo(HICON hIcon, ICONINFO* pIconInfo, BITMAP* pbmpColor, BITMAP* pbmpMask)
{
if (!GetIconInfo(hIcon, pIconInfo))
return FALSE;
if (!GetObject(pIconInfo->hbmColor, sizeof(BITMAP), pbmpColor))
return FALSE;
if (!GetObject(pIconInfo->hbmMask, sizeof(BITMAP), pbmpMask))
return FALSE;
return TRUE;
}
//
// Write one icon directory entry - specify the index of the image
//
static void WriteIconDirectoryEntry(BYTE* buffer, int* pBufferOffset, int nIdx, HICON hIcon, UINT nImageOffset)
{
ICONINFO iconInfo;
ICONDIR iconDir;
BITMAP bmpColor;
BITMAP bmpMask;
UINT nColorCount;
UINT nImageBytes;
GetIconBitmapInfo(hIcon, &iconInfo, &bmpColor, &bmpMask);
nImageBytes = NumBitmapBytes(&bmpColor) + NumBitmapBytes(&bmpMask);
if (bmpColor.bmBitsPixel >= 8)
nColorCount = 0;
else
nColorCount = 1 << (bmpColor.bmBitsPixel * bmpColor.bmPlanes);
// Create the ICONDIR structure
iconDir.bWidth = (BYTE)bmpColor.bmWidth;
iconDir.bHeight = (BYTE)bmpColor.bmHeight;
iconDir.bColorCount = nColorCount;
iconDir.bReserved = 0;
iconDir.wPlanes = bmpColor.bmPlanes;
iconDir.wBitCount = bmpColor.bmBitsPixel;
iconDir.dwBytesInRes = sizeof(BITMAPINFOHEADER) + nImageBytes;
iconDir.dwImageOffset = nImageOffset;
// Write to disk
memcpy(&buffer[*pBufferOffset], &iconDir, sizeof(iconDir));
(*pBufferOffset) += sizeof(iconDir);
// Free resources
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
}
static UINT WriteIconData(BYTE* buffer, int* pBufferOffset, HBITMAP hBitmap)
{
BITMAP bmp;
int i;
BYTE* pIconData;
UINT nBitmapBytes;
GetObject(hBitmap, sizeof(BITMAP), &bmp);
nBitmapBytes = NumBitmapBytes(&bmp);
pIconData = (BYTE*)malloc(nBitmapBytes);
GetBitmapBits(hBitmap, nBitmapBytes, pIconData);
// bitmaps are stored inverted (vertically) when on disk..
// so write out each line in turn, starting at the bottom + working
// towards the top of the bitmap. Also, the bitmaps are stored in packed
// in memory - scanlines are NOT 32bit aligned, just 1-after-the-other
for (i = bmp.bmHeight - 1; i >= 0; i--)
{
memcpy(&buffer[*pBufferOffset], pIconData + (i * bmp.bmWidthBytes), bmp.bmWidthBytes);
(*pBufferOffset) += bmp.bmWidthBytes;
// extend to a 32bit boundary (in the file) if necessary
if (bmp.bmWidthBytes & 3)
{
DWORD padding = 0;
memcpy(&buffer[*pBufferOffset], &padding, 4 - bmp.bmWidthBytes);
(*pBufferOffset) += 4 - bmp.bmWidthBytes;
}
}
free(pIconData);
return nBitmapBytes;
}
//
// Create a .ICO file, using the specified array of HICON images
//
BOOL SaveIcon3(HICON hIcon[], int nNumIcons, BYTE* buffer, int* pWritten)
{
int i;
int* pImageOffset = (int*)malloc(nNumIcons * sizeof(int));
int bufferOffset = 0;
if (hIcon == 0 || nNumIcons < 1)
return 0;
//
// Write the iconheader first of all
//
ICONHEADER iconheader;
// Setup the icon header
iconheader.idReserved = 0; // Must be 0
iconheader.idType = 1; // Type 1 = ICON (type 2 = CURSOR)
iconheader.idCount = nNumIcons; // number of ICONDIRs
// Write the header to disk
memcpy(&(buffer[bufferOffset]), &iconheader, sizeof(iconheader));
bufferOffset += sizeof(iconheader);
//
// Leave space for the IconDir entries
//
bufferOffset += sizeof(ICONDIR) * nNumIcons;
//
// Now write the actual icon images!
//
for (i = 0; i < nNumIcons; i++) {
ICONINFO iconInfo;
BITMAP bmpColor, bmpMask;
// GetIconBitmapInfo
GetIconBitmapInfo(hIcon[i], &iconInfo, &bmpColor, &bmpMask);
// record the file-offset of the icon image for when we write the icon directories
pImageOffset[i] = bufferOffset;
// WriteIconImageHeader
BITMAPINFOHEADER biHeader;
UINT nImageBytes;
// calculate how much space the COLOR and MASK bitmaps take
nImageBytes = NumBitmapBytes(&bmpColor) + NumBitmapBytes(&bmpMask);
// write the ICONIMAGE to disk (first the BITMAPINFOHEADER)
ZeroMemory(&biHeader, sizeof(biHeader));
// Fill in only those fields that are necessary
biHeader.biSize = sizeof(biHeader);
biHeader.biWidth = bmpColor.bmWidth;
biHeader.biHeight = bmpColor.bmHeight * 2; // height of color+mono
biHeader.biPlanes = bmpColor.bmPlanes;
biHeader.biBitCount = bmpColor.bmBitsPixel;
biHeader.biSizeImage = nImageBytes;
// write the BITMAPINFOHEADER
memcpy(&(buffer[bufferOffset]), &biHeader, sizeof(biHeader));
bufferOffset += sizeof(biHeader);
// color and mask bitmaps
WriteIconData(buffer, &bufferOffset, iconInfo.hbmColor);
WriteIconData(buffer, &bufferOffset, iconInfo.hbmMask);
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
}
*pWritten = bufferOffset;
//
// Lastly, skip back and write the icon directories.
//
bufferOffset = sizeof(ICONHEADER);
for (i = 0; i < nNumIcons; i++)
{
WriteIconDirectoryEntry(buffer, &bufferOffset, i, hIcon[i], pImageOffset[i]);
}
free(pImageOffset);
return 1;
}
void main()
{
HICON hIconLarge;
HICON hIconSmall;
int extractIcon = ExtractIconExW(L"E:\\Program Files\\Microsoft VS Code Insiders\\Code - Insiders.exe", 0, &hIconLarge, &hIconSmall, 1);
if (extractIcon <= 0) {
std::cout << "No icon";
return;
}
BYTE buffer[(256 * 256) * 4]; // (256x256) Max Windows Icon Size x 4 bytes (32 bits)
int written;
SaveIcon3(&hIconLarge, 1, buffer, &written);
std::ofstream file;
file.open("E:/t.ico", std::ios_base::binary);
assert(file.is_open());
for (int i = 0; i < sizeof(buffer) / sizeof(buffer[0]); ++i)
file.write((char*)(buffer + i * sizeof(buffer[0])), sizeof(buffer[0]));
file.close();
}
Found a solution.
static BITMAP_AND_BYTES createAlphaChannelBitmapFromIcon(HICON hIcon) {
// Get the icon info
ICONINFO iconInfo = {0};
GetIconInfo(hIcon, &iconInfo);
// Get the screen DC
HDC dc = GetDC(NULL);
// Get icon size info
BITMAP bm = {0};
GetObject( iconInfo.hbmColor, sizeof( BITMAP ), &bm );
// Set up BITMAPINFO
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = bm.bmWidth;
bmi.bmiHeader.biHeight = -bm.bmHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
// Extract the color bitmap
int nBits = bm.bmWidth * bm.bmHeight;
int32_t* colorBits = new int32_t[nBits];
GetDIBits(dc, iconInfo.hbmColor, 0, bm.bmHeight, colorBits, &bmi, DIB_RGB_COLORS);
// Check whether the color bitmap has an alpha channel.
// (On my Windows 7, all file icons I tried have an alpha channel.)
BOOL hasAlpha = FALSE;
for (int i = 0; i < nBits; i++) {
if ((colorBits[i] & 0xff000000) != 0) {
hasAlpha = TRUE;
break;
}
}
// If no alpha values available, apply the mask bitmap
if (!hasAlpha) {
// Extract the mask bitmap
int32_t* maskBits = new int32_t[nBits];
GetDIBits(dc, iconInfo.hbmMask, 0, bm.bmHeight, maskBits, &bmi, DIB_RGB_COLORS);
// Copy the mask alphas into the color bits
for (int i = 0; i < nBits; i++) {
if (maskBits[i] == 0) {
colorBits[i] |= 0xff000000;
}
}
delete[] maskBits;
}
// Release DC and GDI bitmaps
ReleaseDC(NULL, dc);
::DeleteObject(iconInfo.hbmColor);
::DeleteObject(iconInfo.hbmMask);
// Create GDI+ Bitmap
Gdiplus::Bitmap* bmp = new Gdiplus::Bitmap(bm.bmWidth, bm.bmHeight, bm.bmWidth*4, PixelFormat32bppARGB, (BYTE*)colorBits);
BITMAP_AND_BYTES ret = {bmp, colorBits};
return ret;
}
Original post: https://stackoverflow.com/a/22885412/1456151
And also from PowerToys source code:
HBITMAP CreateBitmapFromIcon(_In_ HICON hIcon, _In_opt_ UINT width, _In_opt_ UINT height)
{
HBITMAP hBitmapResult = NULL;
// Create compatible DC
HDC hDC = CreateCompatibleDC(NULL);
if (hDC != NULL)
{
// Get bitmap rectangle size
RECT rc = { 0 };
rc.left = 0;
rc.right = (width != 0) ? width : GetSystemMetrics(SM_CXSMICON);
rc.top = 0;
rc.bottom = (height != 0) ? height : GetSystemMetrics(SM_CYSMICON);
// Create bitmap compatible with DC
BITMAPINFO BitmapInfo;
ZeroMemory(&BitmapInfo, sizeof(BITMAPINFO));
BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BitmapInfo.bmiHeader.biWidth = rc.right;
BitmapInfo.bmiHeader.biHeight = rc.bottom;
BitmapInfo.bmiHeader.biPlanes = 1;
BitmapInfo.bmiHeader.biBitCount = 32;
BitmapInfo.bmiHeader.biCompression = BI_RGB;
HDC hDCBitmap = GetDC(NULL);
HBITMAP hBitmap = CreateDIBSection(hDCBitmap, &BitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0);
ReleaseDC(NULL, hDCBitmap);
if (hBitmap != NULL)
{
// Select bitmap into DC
HBITMAP hBitmapOld = (HBITMAP)SelectObject(hDC, hBitmap);
if (hBitmapOld != NULL)
{
// Draw icon into DC
if (DrawIconEx(hDC, 0, 0, hIcon, rc.right, rc.bottom, 0, NULL, DI_NORMAL))
{
// Restore original bitmap in DC
hBitmapResult = (HBITMAP)SelectObject(hDC, hBitmapOld);
hBitmapOld = NULL;
hBitmap = NULL;
}
if (hBitmapOld != NULL)
{
SelectObject(hDC, hBitmapOld);
}
}
if (hBitmap != NULL)
{
DeleteObject(hBitmap);
}
}
DeleteDC(hDC);
}
return hBitmapResult;
}

Windows GetDIBits not returning expected values

I'm currently writing a small program that scans the screen and looks for pixels. My issue is that GetDIBits function doesn't appear to return a proper screenshot of the screen.
Copying the bitmap to the clipboard does put the right screen image in the clipboard.
I decided to print out the function's output to a BMP file to get an idea of what's going on and it clearly isn't what I'm expecting.
I'll also mention that I have 3 monitors, incase that could explain why it's not behaving like expected.
class Test {
int screenWidth;
int screenHeight;
HWND targetWindow;
HDC targetDC;
HDC captureDC;
RGBQUAD *pixels;
HBITMAP captureBitmap;
bool TakeScreenshot() {
ZeroMemory(pixels, screenHeight*screenWidth);
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
targetWindow = GetDesktopWindow();
targetDC = GetDC(NULL);
captureDC = CreateCompatibleDC(targetDC);
captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight);
HGDIOBJ old = SelectObject(captureDC, captureBitmap);
if (!old)
printf("Error selecting object\n");
OpenClipboard(NULL);
EmptyClipboard();
SetClipboardData(CF_BITMAP, captureBitmap);
CloseClipboard();
if (BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC, 0, 0, SRCCOPY)) {
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = screenWidth;
bmi.bmiHeader.biHeight = -screenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
if (!SelectObject(captureDC, old))
printf("Error unselecting object\n");
if (!GetDIBits(captureDC,
captureBitmap,
0,
screenHeight,
pixels,
&bmi,
DIB_RGB_COLORS
)) {
printf("%s: GetDIBits failed\n", __FUNCTION__);
return false;
}
}
else {
printf("%s: BitBlt failed\n", __FUNCTION__);
return false;
}
return true;
}
// This is from somewhere on stackoverflow - can't find where.
void MakePicture() {
typedef struct /**** BMP file header structure ****/
{
unsigned int bfSize; /* Size of file */
unsigned short bfReserved1; /* Reserved */
unsigned short bfReserved2; /* ... */
unsigned int bfOffBits; /* Offset to bitmap data */
} BITMAPFILEHEADER;
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
unsigned short bfType = 0x4d42;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfSize = 2 + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 2560 * 1440 * 3;
bfh.bfOffBits = 0x36;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = screenWidth;
bih.biHeight = screenHeight;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = 0;
bih.biSizeImage = 0;
bih.biXPelsPerMeter = 5000;
bih.biYPelsPerMeter = 5000;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
FILE *file;
fopen_s(&file, "test.bmp", "wb");
if (!file)
{
printf("Could not write file\n");
return;
}
/*Write headers*/
fwrite(&bfType, 1, sizeof(bfType), file);
fwrite(&bfh, 1, sizeof(bfh), file);
fwrite(&bih, 1, sizeof(bih), file);
/*Write bitmap*/
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y].rgbRed;
unsigned char g = pixels[x + y].rgbGreen;
unsigned char b = pixels[x + y].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
}
fclose(file);
}
Test() {
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
pixels = new RGBQUAD[screenWidth * screenHeight];
}
~Test() {
//cleanup
}
};
Here is the result that the code is giving(instead of a screenshot):
It appears like it takes a few pixels from the top of my screen and stretches them into an image. The screenshot is from Visual Studio being open(orange part being the notifications).
If I put a giant red square (255, 0, 0) in my screen, if it's height isn't 0, the pixels array will not contain a single red pixel.
BitBlt performs the actual copying. Clipboard functions should be called after BitBlt
Also note, in mutli-monitor settings, SM_CXSCREEN/Y... give the size for the primary monitor. Use SM_XVIRTUALSCREEN/XV... for the whole screen. SM_XVIRTUALSCREEN/Y will give the X/Y coordinate (it's usually zero)
Be sure to release all the handles and delete the used objects when you are finished. In fact, there is no need to declare targetDC etc. as class members.
If the application is not DPI aware, the bitmap may look smaller depending on DPI settings. Call SetProcessDPIAware() at the start of the program for a quick fix, or set the manifest.
As noted in comment, with SetClipboardData(CF_BITMAP, captureBitmap); the system takes over captureBitmap. Avoid calling this function or make a copy of the bitmap to pass to clipboard.
int main()
{
int screenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int screenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
int screen_x = GetSystemMetrics(SM_XVIRTUALSCREEN);
int screen_y = GetSystemMetrics(SM_YVIRTUALSCREEN);
screenWidth = GetSystemMetrics(SM_CXSCREEN);
screenHeight = GetSystemMetrics(SM_CYSCREEN);
screen_x = 0;
screen_y = 0;
RGBQUAD* pixels = new RGBQUAD[screenWidth * screenHeight];
DWORD size = screenWidth * screenHeight * 4;
ZeroMemory(pixels, size);
HDC targetDC = GetDC(NULL);
HDC captureDC = CreateCompatibleDC(targetDC);
HBITMAP captureBitmap = CreateCompatibleBitmap(targetDC, screenWidth, screenHeight);
HGDIOBJ old = SelectObject(captureDC, captureBitmap);
if(!BitBlt(captureDC, 0, 0, screenWidth, screenHeight, targetDC,
screen_x, screen_y, SRCCOPY))
printf("BitBlt error\n");
SelectObject(captureDC, old);
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = screenWidth;
bmi.bmiHeader.biHeight = -screenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0;
if(OpenClipboard(NULL))
{
EmptyClipboard();
SetClipboardData(CF_BITMAP,
CopyImage(captureBitmap, IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE));
CloseClipboard();
}
if(!GetDIBits(targetDC,
captureBitmap,
0,
screenHeight,
pixels,
&bmi,
DIB_RGB_COLORS
))
printf("%s: GetDIBits failed\n", __FUNCTION__);
BITMAPFILEHEADER filehdr = { 'MB', 54 + size, 0, 0, 54 };
std::ofstream f("test.bmp", std::ios::binary);
f.write((char*)&filehdr, sizeof(filehdr));
f.write((char*)&bmi, sizeof(bmi));
f.write((char*)pixels, size);
//cleanup:
SelectObject(captureDC, old);
DeleteObject(captureBitmap);
DeleteDC(captureDC);
ReleaseDC(0, targetDC);
}
GetDIBits function reference, remarks section:
The bitmap identified by the hbmp parameter must not be selected into
a device context when the application calls this function.
Deselect bitmap before calling GetBIBits.
HBITMAP oldBitmap = SelectObject(captureDC, captureBitmap);
...
// Deselect captureBitmap by selecting oldBitmap.
SelectObject(captureDC, oldBitmap);
Remember to add cleanup code (restore bitmap, destroy bitmap, destroy or release device contexts).
Additional bug:
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y].rgbRed;
unsigned char g = pixels[x + y].rgbGreen;
unsigned char b = pixels[x + y].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
}
I think it should be
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y*screenWidth].rgbRed;
unsigned char g = pixels[x + y*screenWidth].rgbGreen;
unsigned char b = pixels[x + y*screenWidth].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
}
But rows require padding to multiplies of 4 bytes:
// Important: each row has to be padded to multiple of DWORD.
// Valid only for 24 bits per pixel bitmaps.
// Remark: 32 bits per pixel have rows always aligned (padding==0)
int padding = 3 - (screenWidth*3 + 3)%4;
// or
// int padding = 3 - ((screenWidth*3 + 3) & 3);
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
unsigned char r = pixels[x + y*screenWidth].rgbRed;
unsigned char g = pixels[x + y*screenWidth].rgbGreen;
unsigned char b = pixels[x + y*screenWidth].rgbBlue;
fwrite(&b, 1, 1, file);
fwrite(&g, 1, 1, file);
fwrite(&r, 1, 1, file);
}
// Important: each row has to be padded to multiple of DWORD.
fwrite("\0\0\0\0", 1, padding, file);
}
Adjust file size (valid for 24 bits per pixel):
bfh.bfSize =
2
+ sizeof(BITMAPFILEHEADER)
+ sizeof(BITMAPINFOHEADER)
+ ((screenWidth*3 + 3) & ~3) * screenHeight;

How to display image using CreateDIBSection

I want to display a images on window, am giving image data as in buffer &CaptureBuffer to CreateDIBSection, CreateDIBForVideo method is calling from thread on main().
dont know where im going wrong it is showing black window.
void CreateDIBForVideo()
{
// ScreenCaptureProcessorGDI is a class it have initialization for capture window screen
screenObject = new ScreenCaptureProcessorGDI();
screenObject->init();
HDC DisplayDC = CreateDC((LPCWSTR)"DISPLAY", NULL, NULL, NULL);
BITMAPINFO bmpInfo = { 0 };
bmpInfo.bmiHeader.biSize= sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = screenObject->lOutputDuplDesc.ModeDesc.Width;
bmpInfo.bmiHeader.biHeight= screenObject->lOutputDuplDesc.ModeDesc.Height;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 32;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biSizeImage = (4 * screenObject->lOutputDuplDesc.ModeDesc.Width * screenObject->lOutputDuplDesc.ModeDesc.Height);
bmpInfo.bmiHeader.biXPelsPerMeter = 0;
bmpInfo.bmiHeader.biYPelsPerMeter = 0;
bmpInfo.bmiHeader.biClrUsed = 0;
bmpInfo.bmiHeader.biClrImportant = 0;
CaptureBuffer = NULL;
HDC pXorDC = CreateCompatibleDC(DisplayDC);
HBITMAP hXorDib = CreateDIBSection(DisplayDC, &bmpInfo, DIB_RGB_COLORS, (void**)&CaptureBuffer, NULL, 0);
hXorTemp = (HBITMAP)SelectObject(pXorDC, hXorDib);
// startGrab this thread capture a windows screen after init()
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&startGrab, NULL, 0, NULL);
}
void startGrab()
{
for (int index = 0; index < 100; index++)
{
// grabImage will capture window screen and send image as a buffer to `CaptureBuffer`
screenObject->grabImage();
PaintViewerWindow();
UpdateWindow(global_hWnd);
::Sleep(2000);
}
}
void PaintViewerWindow()
{
HDC paintDC;
PAINTSTRUCT ps;
paintDC = BeginPaint(global_hWnd, &ps);
SetStretchBltMode(paintDC, HALFTONE);
BitBlt(paintDC, 0, 0, 1366, 768, pXorDC, 0, 0, SRCCOPY);
EndPaint(global_hWnd, &ps);
}
I find a solution for the above problem
It because of UCHAR *CaptureBuffer am directly passing CaptureBuffer to the CreateDIBSection now i changed to copying UCHAR *CaptureBuffer to char *DisplayBuffer its working fine now.
UCHAR *CaptureBuffer = NULL;
char *DisplayBuffer = NULL;
long CaptureSize = NULL;
void saveImage(unsigned int frame_num, BITMAPINFO &lBmpInfo, std::unique_ptr<BYTE> pBuf, UCHAR* &CaptureBuffer, long &CaptureSize)
{
BITMAPFILEHEADER bmpFileHeader;
bmpFileHeader.bfReserved1 = 0;
bmpFileHeader.bfReserved2 = 0;
bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + lBmpInfo.bmiHeader.biSizeImage;
bmpFileHeader.bfType = 'MB';
bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
CaptureSize = lBmpInfo.bmiHeader.biSizeImage;
CaptureBuffer = (UCHAR*)malloc(CaptureSize);
memcpy(CaptureBuffer, pBuf.get(), lBmpInfo.bmiHeader.biSizeImage);
//Here am copying CaptureBuffer(uchar) to DisplayBuffer(char)
memcpy(DisplayBuffer, CaptureBuffer, CaptureSize);
lresult = 0;
}
void CreateDIBForVideo()
{
// ScreenCaptureProcessorGDI is a class it have initialization for capture window screen
screenObject = new ScreenCaptureProcessorGDI();
screenObject->init();
HDC DisplayDC = CreateDC((LPCWSTR)"DISPLAY", NULL, NULL, NULL);
BITMAPINFO bmpInfo = { 0 };
bmpInfo.bmiHeader.biSize= sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = screenObject->lOutputDuplDesc.ModeDesc.Width;
bmpInfo.bmiHeader.biHeight= screenObject->lOutputDuplDesc.ModeDesc.Height;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 32;
bmpInfo.bmiHeader.biCompression = BI_RGB;
bmpInfo.bmiHeader.biSizeImage = (4 * screenObject->lOutputDuplDesc.ModeDesc.Width * screenObject->lOutputDuplDesc.ModeDesc.Height);
bmpInfo.bmiHeader.biXPelsPerMeter = 0;
bmpInfo.bmiHeader.biYPelsPerMeter = 0;
bmpInfo.bmiHeader.biClrUsed = 0;
bmpInfo.bmiHeader.biClrImportant = 0;
CaptureBuffer = NULL;
HDC pXorDC = CreateCompatibleDC(DisplayDC);
HBITMAP hXorDib = CreateDIBSection(DisplayDC, &bmpInfo, DIB_RGB_COLORS, (void**)&DisplayBuffer, NULL, 0);
hXorTemp = (HBITMAP)SelectObject(pXorDC, hXorDib);
// startGrab this thread capture a windows screen after init()
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&startGrab, NULL, 0, NULL);
}

MFC casting Handle into pointer and DIB to DDB conversion

I am trying to create a bitmap by hardcoding an array of pixel values, converting this array of pixels into a DIB, and then turn this DIB into a DDB. I found two functions to convert CreateBitmapFromPixels and DIBToDDB on the internet. My problem is that the program would crash at line 244. I found that, at line 243, lpbi does not retrieve information from hDIB. Then I added the code at lines 229 and 230 to see if doing the same thing in the function that created the BITMAPINFO structure would help. Still, nothing was gotten from the HBITMAP. I am wondering if there is anything wrong with casting a handle into a pointer, what does it do, and are there other ways to get the HBITMAPINFOHEADER from a handle to a DIB so I can fix the problem.
HBITMAP ColorChange2Dlg::CreateBitmapFromPixels( HDC hDC,
UINT uWidth, UINT uHeight, UINT uBitsPerPixel, LPVOID pBits)
{
if(uBitsPerPixel < 8) // NOT IMPLEMENTED YET
return NULL;
if(uBitsPerPixel == 8)
return Create8bppBitmap(hDC, uWidth, uHeight, pBits);
HBITMAP hBitmap = 0;
if ( !uWidth || !uHeight || !uBitsPerPixel )
return hBitmap;
LONG lBmpSize = uWidth * uHeight * (uBitsPerPixel/8) ;
BITMAPINFO bmpInfo = { 0 };
bmpInfo.bmiHeader.biBitCount = uBitsPerPixel;
bmpInfo.bmiHeader.biHeight = uHeight;
bmpInfo.bmiHeader.biWidth = uWidth;
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
if(bmpInfo.bmiHeader.biBitCount==32) {
bmpInfo.bmiHeader.biCompression=BI_RGB;
//bmpInfo.bmiColors=NULL;
}
// Pointer to access the pixels of bitmap
UINT * pPixels = 0;
hBitmap = CreateDIBSection( hDC, (BITMAPINFO *)&
bmpInfo, DIB_RGB_COLORS, (void **)&
pPixels , NULL, 0);
if ( !hBitmap )
return hBitmap; // return if invalid bitmaps
//SetBitmapBits( hBitmap, lBmpSize, pBits);
// Directly Write
memcpy(pPixels, pBits, lBmpSize );
LPBITMAPINFOHEADER lpbi; //Line 229
lpbi = (LPBITMAPINFOHEADER)hBitmap; //Line 230
return hBitmap;
}
HBITMAP ColorChange2Dlg::DIBToDDB( HANDLE hDIB, CDC& dc )
{
LPBITMAPINFOHEADER lpbi;
HBITMAP hbm;
CPalette pal;
CPalette* pOldPal;
//CClientDC dc(NULL);
if (hDIB == NULL)
return NULL;
lpbi = (LPBITMAPINFOHEADER)hDIB; //Line 243
int nColors = lpbi->biClrUsed ? lpbi->biClrUsed : 1 << lpbi->biBitCount; //Line 244
BITMAPINFO &bmInfo = *(LPBITMAPINFO)hDIB ;
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 and select a logical palette if needed
if( nColors <= 256 && dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE)
{
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;
}
pal.CreatePalette( pLP );
delete[] pLP;
// Select and realize the palette
pOldPal = dc.SelectPalette( &pal, FALSE );
dc.RealizePalette();
}
hbm = CreateDIBitmap(dc.GetSafeHdc(), // handle to device context
(LPBITMAPINFOHEADER)lpbi, // pointer to bitmap info header
(LONG)CBM_INIT, // initialization flag
lpDIBBits, // pointer to initialization data
(LPBITMAPINFO)lpbi, // pointer to bitmap info
DIB_RGB_COLORS ); // color-data usage
if (pal.GetSafeHandle())
dc.SelectPalette(pOldPal,FALSE);
return hbm;
}
void ColorChange2Dlg::OnBnClickedButton1()
{
// TODO: Add your control notification handler code here
CClientDC dc(this);
COLORREF *pix = (COLORREF *)malloc(255*255*sizeof(COLORREF));
//int x = 1;
if(pix!=NULL){
for(int i=0;i<255;i++)
{
for(int j=0;j<255;j++)
{
pix[i*255+j] = RGB(i,j,0);
}
}
}
CDC tempDC;
tempDC.CreateCompatibleDC(&dc);
HBITMAP dib = CreateBitmapFromPixels(tempDC.m_hDC,255,255,8*sizeof(COLORREF),(BYTE*)pix);
HBITMAP finalMap = DIBToDDB(dib,tempDC);
HBITMAP oldMap = (HBITMAP)tempDC.SelectObject(finalMap);
dc.BitBlt(201,50,255,255,&tempDC,0,0,SRCCOPY);
tempDC.SelectObject(oldMap);
tempDC.DeleteDC();
}
To write compatible code, it's better not to access bits directly at all. You can use Gradient functions and GDI or GDI+ draw functions to do anything you want.
The code you have in mind pix[i*255+j] = RGB(i,j,0); is of a 32-bit image. Each pixel points to a color. It's not a palette image where each pixel points to an entry in the color table.
If display is 32 bit (most modern computers are, but check to make sure), you can do this with the following code
CBitmap m_bitmap;
void CMyWnd::make_bitmap()
{
if (m_bitmap.GetSafeHandle()) return;
int w = 255;
int h = 255;
int *pix = new int[w*h];
for (int i = 0; i < w; i++)
for (int j = 0; j < h; j++)
pix[i + j*w] = RGB(i, j, 0);
m_bitmap.CreateBitmap(w, h, 1, 32, pix);
delete[]pix;
}
And to draw the bitmap:
void CMyWnd::paint_bitmap(CDC &dc)
{
if (!m_bitmap.GetSafeHandle()) return;
CDC memdc;
memdc.CreateCompatibleDC(&dc);
HBITMAP oldbitmap = (HBITMAP)memdc.SelectObject(m_bitmap);
BITMAP bm;
m_bitmap.GetBitmap(&bm);
dc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &memdc, 0, 0, SRCCOPY);
memdc.SelectObject(oldbitmap);
}
void CMyWnd::OnPaint()
{
__super::OnPaint();
CClientDC dc(this);
paint_bitmap(dc);
}
Edit: For historical reasons the RGB value are saved backward as BGR. Use this function instead:
void CMyWnd::make_bitmap()
{
if (m_bitmap.GetSafeHandle()) return;
int w = 256;
int h = 256;
BYTE *pix = new BYTE[4*w*h];
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
int p = (i + j*w) * 4;
pix[p + 0] = 0;//blue
pix[p + 1] = i;//green
pix[p + 2] = j;//red
pix[p + 3] = 0;//not used in GDI functions
}
}
m_bitmap.CreateBitmap(w, h, 1, 32, pix);
delete[]pix;
}

Get and save bitmap from HWND (8, 4, 1 bits-per-pixel - black image)

I write this simple code for saving bitmap to file (*.bmp) from HWND.
#include <stdio.h>
#include <windows.h>
bool captureAndSave(const HWND& hWnd, int nBitCount, const char* szFilePath)
{
if(!szFilePath || !strlen(szFilePath))
{
printf("bad function arguments\n");
return false;
}
//calculate the number of color indexes in the color table
int nColorTableEntries = -1;
switch(nBitCount)
{
case 1:
nColorTableEntries = 2;
break;
case 4:
nColorTableEntries = 16;
break;
case 8:
nColorTableEntries = 256;
break;
case 16:
case 24:
case 32:
nColorTableEntries = 0;
break;
default:
nColorTableEntries = -1;
break;
}
if(nColorTableEntries == -1)
{
printf("bad bits-per-pixel argument\n");
return false;
}
HDC hDC = GetDC(hWnd);
HDC hMemDC = CreateCompatibleDC(hDC);
int nWidth = 0;
int nHeight = 0;
if(hWnd != HWND_DESKTOP)
{
RECT rect;
GetClientRect(hWnd, &rect);
nWidth = rect.right - rect.left;
nHeight = rect.bottom - rect.top;
}
else
{
nWidth = ::GetSystemMetrics(SM_CXSCREEN);
nHeight = ::GetSystemMetrics(SM_CYSCREEN);
}
HBITMAP hBMP = CreateCompatibleBitmap(hDC, nWidth, nHeight);
SelectObject(hMemDC, hBMP);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);
int nStructLength = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
LPBITMAPINFOHEADER lpBitmapInfoHeader = (LPBITMAPINFOHEADER)new char[nStructLength];
::ZeroMemory(lpBitmapInfoHeader, nStructLength);
lpBitmapInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
lpBitmapInfoHeader->biWidth = nWidth;
lpBitmapInfoHeader->biHeight = nHeight;
lpBitmapInfoHeader->biPlanes = 1;
lpBitmapInfoHeader->biBitCount = nBitCount;
lpBitmapInfoHeader->biCompression = BI_RGB;
lpBitmapInfoHeader->biXPelsPerMeter = 0;
lpBitmapInfoHeader->biYPelsPerMeter = 0;
lpBitmapInfoHeader->biClrUsed = nColorTableEntries;
lpBitmapInfoHeader->biClrImportant = nColorTableEntries;
DWORD dwBytes = ((DWORD) nWidth * nBitCount) / 32;
if(((DWORD) nWidth * nBitCount) % 32) {
dwBytes++;
}
dwBytes *= 4;
DWORD dwSizeImage = dwBytes * nHeight;
lpBitmapInfoHeader->biSizeImage = dwSizeImage;
LPBYTE lpDibBits = 0;
HBITMAP hBitmap = ::CreateDIBSection(hMemDC, (LPBITMAPINFO)lpBitmapInfoHeader, DIB_RGB_COLORS, (void**)&lpDibBits, NULL, 0);
SelectObject(hMemDC, hBitmap);
BitBlt(hMemDC, 0, 0, nWidth, nHeight, hDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd, hDC);
BITMAPFILEHEADER bmfh;
bmfh.bfType = 0x4d42; // 'BM'
int nHeaderSize = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
bmfh.bfSize = 0;
bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * nColorTableEntries;
FILE *pFile = 0;
fopen_s(&pFile, szFilePath, "wb");
if(!pFile)
{
::DeleteObject(hBMP);
::DeleteObject(hBitmap);
delete[]lpBitmapInfoHeader;
printf("can not open file\n");
return false;
}
DWORD nColorTableSize = 0;
if (nBitCount != 24)
nColorTableSize = (1 << nBitCount) * sizeof(RGBQUAD);
else
nColorTableSize = 0;
fwrite(&bmfh, sizeof(BITMAPFILEHEADER), 1, pFile);
fwrite(lpBitmapInfoHeader, nHeaderSize,1,pFile);
if(nBitCount < 16)
{
int nBytesWritten = 0;
RGBQUAD *rgbTable = new RGBQUAD[nColorTableEntries * sizeof(RGBQUAD)];
//fill RGBQUAD table and write it in file
for(int i = 0; i < nColorTableEntries; ++i)
{
rgbTable[i].rgbRed = rgbTable[i].rgbGreen = rgbTable[i].rgbBlue = i;
rgbTable[i].rgbReserved = 0;
fwrite(&rgbTable[i], sizeof(RGBQUAD), 1, pFile);
}
delete[]rgbTable;
/*
RGBQUAD rgb;
for (DWORD i = 0; i < nColorTableEntries ; i++)
{
rgb.rgbBlue = rgb.rgbGreen = rgb.rgbRed = (BYTE)(i*(255/(nColorTableEntries-1)));
nBytesWritten = fwrite(&rgb, 1, sizeof(rgb), pFile);
if (nBytesWritten != sizeof(rgb))
{
printf("error while writing rgb header\n");
fclose(pFile);
::DeleteObject(hBMP);
::DeleteObject(hBitmap);
delete[]lpBitmapInfoHeader;
return false;
}
}
*/
}
fwrite(lpDibBits, dwSizeImage, 1, pFile);
fclose(pFile);
::DeleteObject(hBMP);
::DeleteObject(hBitmap);
delete[]lpBitmapInfoHeader;
}
int main(int argc, char **argv)
{
captureAndSave(HWND_DESKTOP, 1, "1.bmp");
captureAndSave(HWND_DESKTOP, 4, "4.bmp");
captureAndSave(HWND_DESKTOP, 8, "8.bmp");
captureAndSave(HWND_DESKTOP, 16, "16.bmp");
captureAndSave(HWND_DESKTOP, 24, "24.bmp");
captureAndSave(HWND_DESKTOP, 32, "32.bmp");
return 0;
}
Images are saved properly for 32, 24 and 16 bits per pixel. But for 8, 4 and 1 bits per pixel image contains only black pixels.
Please tell me what I'm doing wrong.
When GDI copies images to an indexed surface, it needs to map the colors it has on the source to colors available on the destination.
Until, and unless, you create and select a palette into the destination DC, GDI won't know what colors are available, and will map the colors using the default palette, which will define black and white only.
The could be quite a lot involved in this - ideally you'd want to scan the source image, create a map of all the colors used and their freqencies and use that to compute an ideal palette.
Or, just use CreateHalfTonePalette.
In your case, you are blitting onto a DIBSection with the required bit depth, so you need to initialize the DIBSections color-table before performing the blit.
iirc, When blitting to a DIB Section, having the DIBSections color table setup is more important than selecting an HPALETTE into the DC - but you can use the palette created by CreateHalfTonePalette, and extract the resulting color table.
For 8/4/1 bit images, which are indexed images, must write RGBQUAD table to file, because raw bitmap data is not colors, but indexes to RQBQUAD table.