I have a perfectly working code that creates 32bpp bitmap and I need to change it so that 8bpp bitmap is created.
Here's the piece of code that creates 32bpp bitmap, draws into it, then it creates a bitmap file and store it into the vector of bytes:
// prepare bitmap:
BYTE* bitmap_data = NULL;
HDC hDC = GetDC(NULL);
HDC memHDC = CreateCompatibleDC(hDC);
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = desiredWidth; // desiredWidth is 800
bmi.bmiHeader.biHeight = desiredHeight; // desiredHeight is 202
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = (((desiredWidth * bmi.bmiHeader.biBitCount + 31) & ~31) >> 3) * desiredHeight;
HBITMAP bitmap = CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, (void**)&bitmap_data, NULL, NULL);
ReleaseDC(NULL, hDC);
DeleteDC(hDC);
... // drawing into bitmap
// prepare bitmap file header:
BITMAPFILEHEADER bf;
memset(&bf, 0, sizeof(BITMAPFILEHEADER));
bf.bfType = MAKEWORD('B', 'M');
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;
bf.bfSize = bf.bfOffBits + bmi.bmiHeader.biSizeImage;
// write bitmap file into the vector:
std::vector<BYTE> bitmapData;
bitmapData.insert(bitmapData.end(), (BYTE*)&bf, ((BYTE*)&bf) + sizeof(BITMAPFILEHEADER));
bitmapData.insert(bitmapData.end(), (BYTE*)&bmi.bmiHeader, ((BYTE*)&bmi.bmiHeader) + sizeof(BITMAPINFOHEADER));
bitmapData.insert(bitmapData.end(), bitmap_data, bitmap_data + bmi.bmiHeader.biSizeImage);
And later the vector is stored into the file:
std::ofstream of("picture.bmp", std::ofstream::out | std::ofstream::binary);
of.write((char*)&bitmapData[0], bitmapData.size());
of.close();
and here's the output image:
What I've tried:
First step was naturally replacing 32 with 8 in this line: bmi.bmiHeader.biBitCount = 32; which resulted into image filled with solid grey colour. Then based on this answer I made following changes:
BITMAPINFO bmi;
memset(&bmi, 0, sizeof(BITMAPINFO));
changed into:
struct BITMAPINFO256 {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[256];
} bmi;
memset(&bmi, 0, sizeof(BITMAPINFO256));
added this loop right before CreateDIBSection is called:
for (UINT i = 0; i < 256; i++) {
bmi.bmiColors[i].rgbRed = i;
bmi.bmiColors[i].rgbGreen = i;
bmi.bmiColors[i].rgbBlue = i;
}
and when the bmi.bmiHeader is being written into the vector, the RGBQUAD array is included: so sizeof(BITMAPINFO256) expresses the size of the header.
The new code (full code here) produces this output:
Why the new image looks that way? What's going on there? What am I missing?
Any help will be appreciated.
It looks like an alignment problem. Make sure you update bfOffBits in the BITMAPFILEHEADER so that it points to the first byte of the pixel data. (If you don't change it, then it probably points to the beginning of the palette.)
In other words, sizeof(RGBQUAD)*256 should be added here as well:
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + bmi.bmiHeader.biSize;
Also makes sure the first scanline starts on a DWORD boundary. That is, its offset from the beginning of the file should be a multiple of four bytes. Likewise, each scanline should be padded out to a multiple of four bytes. (You may not see these problems if your widths are nice even numbers. It's good to have an odd-width image among your test cases.)
You need to specify the size of the palette that you attached. Right now it's zero, so the palette is showing up as the first bunch of pixels in your image.
bmi.bmiHeader.biClrUsed = 256;
You need to generate a palette for your image.
Each pixel in a 32bit image is stored as 8-bits for Alpha, Red, Green and Blue.
Where as in a 8bit image, the value in each pixel an 8bit index into the palette.
Your for(i=0..255) { bmi.bmiColors[i].rgbRed = i; ....} code is generated an grey-scale palette.
If the whole image is coming out as grey then it sounds like an alignment error, from memory the width of a palettized image must be a multiple of 4.
Try saving a SMALL 256 colour (aka 8-bit image) from Paint and compare in a hex editor.
Related
I'm trying to use StretchBlt in order to copy pixels from a memory hdc to the window hdc.
The memory hdc gets the image from an invisible window which renders a stream using openGL.
Here's my code:
BITMAPINFOHEADER createBitmapHeader(int width, int height) {
BITMAPINFOHEADER header;
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = width;
header.biHeight = height;
header.biPlanes = 1;
header.biBitCount = 32;
header.biCompression = BI_RGB;
header.biSizeImage = 0;
header.biXPelsPerMeter = 0;
header.biYPelsPerMeter = 0;
header.biClrUsed = 0;
header.biClrImportant = 0;
return header;
}
...
HDC memoryHdc = CreateCompatibleDC(windowHdc);
BITMAPINFO bitmapInfo;
bitmapInfo.bmiHeader = createBitmapHeader(targetDimensions.width, targetDimensions.height);
HBITMAP bitmap = CreateDIBitmap(windowHdc, &bitmapInfo.bmiHeader, CBM_INIT, offscreenBuffer, &bitmapInfo, DIB_RGB_COLORS);
SelectObject(memoryHdc, bitmap);
DeleteObject(bitmap);
SetStretchBltMode(windowHdc, COLORONCOLOR);
StretchBlt(windowHdc,
targetDimensions.x, targetDimensions.y,
targetDimensions.width, -targetDimensions.height,
memoryHdc,
sourceDimensions.x, sourceDimensions.y,
sourceDimensions.width, sourceDimensions.height,
SRCCOPY);
DeleteDC(memoryHdc);
Where windowHdc is the hdc of the window to which I want the StretchBlt to copy the pixels to, and offscreenBuffer is a void* to the pixels copied from the offscreen window in which the openGL is rendering.
This code works great, except that the image is upside down and I want it vertically flipped.
I know that this happens because:
If nHeightSrc and nHeightDest have different signs, the function
creates a mirror image of the bitmap along the y-axis
But when I remove the minus sign and both target and source heights are the same then I see no image in the window.
Just to check, I tried to put the minus on the sourceDimensions.height but that also results in no image, and the same if I try to negate the widths (both target and source).
Any idea why?
Thanks.
I am writing an extremely small C++ program to help me animate sprites. I'd like it to take data I copy to the clipboard from photoshop, manipulate it in my program, then overwrite the clipboard with the transform.
The problem though is that I'm not sure how to read the initial clipboard from photoshop.
I can load the clipboard with GetClipboardData(CF_DIB), and get a valid handle, but I've no idea how to use that handle. I've tried using SFML's Image::LoadFromMemory(handle, GlobalSize(handle)) which is able to load bitmap files from memory, but that doesn't seem to work.
Will I need to actually parse the entire format? What format structure would I be looking at in that case? Would there perhaps be any way I could quickly mangle the data so it might look like a bitmap file? Could it be easier/possible to simply save it to file using the windows API? (I could then load that file with SFML to edit, that way)
It's just a quick and dirty tool for myself to save a lot of grunt work in photoshop, so efficiency or robustness aren't important at all.
Learn the bitmap structure from Wikipedia and then write it out to a file and then write out the pixels..
I've tested the below with Paint on Windows 8.1. I opened an image with paint and then pressed Ctrl + C to copy to the clipboard.. then I ran the following code and it copied the clipboard image to the desktop:
#include <iostream>
#include <fstream>
#include <windows.h>
int main()
{
std::cout<<"Format Bitmap: "<<IsClipboardFormatAvailable(CF_BITMAP)<<"\n";
std::cout<<"Format DIB: "<<IsClipboardFormatAvailable(CF_DIB)<<"\n";
std::cout<<"Format DIBv5: "<<IsClipboardFormatAvailable(CF_DIBV5)<<"\n";
if (IsClipboardFormatAvailable(CF_DIB))
{
if (OpenClipboard(NULL))
{
HANDLE hClipboard = GetClipboardData(CF_DIB);
if (hClipboard != NULL && hClipboard != INVALID_HANDLE_VALUE)
{
void* dib = GlobalLock(hClipboard);
if (dib)
{
BITMAPINFOHEADER* info = reinterpret_cast<BITMAPINFOHEADER*>(dib);
BITMAPFILEHEADER fileHeader = {0};
fileHeader.bfType = 0x4D42;
fileHeader.bfOffBits = 54;
fileHeader.bfSize = (((info->bmiHeader.biWidth * info->bmiHeader.biBitCount + 31) & ~31) / 8
* info->bmiHeader.biHeight) + fileHeader.bfOffBits;
std::cout<<"Type: "<<std::hex<<fileHeader.bfType<<std::dec<<"\n";
std::cout<<"bfSize: "<<fileHeader.bfSize<<"\n";
std::cout<<"Reserved: "<<fileHeader.bfReserved1<<"\n";
std::cout<<"Reserved2: "<<fileHeader.bfReserved2<<"\n";
std::cout<<"Offset: "<<fileHeader.bfOffBits<<"\n";
std::cout<<"biSize: "<<info->bmiHeader.biSize<<"\n";
std::cout<<"Width: "<<info->bmiHeader.biWidth<<"\n";
std::cout<<"Height: "<<info->bmiHeader.biHeight<<"\n";
std::cout<<"Planes: "<<info->bmiHeader.biPlanes<<"\n";
std::cout<<"Bits: "<<info->bmiHeader.biBitCount<<"\n";
std::cout<<"Compression: "<<info->bmiHeader.biCompression<<"\n";
std::cout<<"Size: "<<info->bmiHeader.biSizeImage<<"\n";
std::cout<<"X-res: "<<info->bmiHeader.biXPelsPerMeter<<"\n";
std::cout<<"Y-res: "<<info->bmiHeader.biYPelsPerMeter<<"\n";
std::cout<<"ClrUsed: "<<info->bmiHeader.biClrUsed<<"\n";
std::cout<<"ClrImportant: "<<info->bmiHeader.biClrImportant<<"\n";
std::ofstream file("C:/Users/Brandon/Desktop/Test.bmp", std::ios::out | std::ios::binary);
if (file)
{
file.write(reinterpret_cast<char*>(&fileHeader), sizeof(BITMAPFILEHEADER));
file.write(reinterpret_cast<char*>(info), sizeof(BITMAPINFOHEADER));
file.write(reinterpret_cast<char*>(++info), bmp.dib.biSizeImage);
}
GlobalUnlock(dib);
}
}
CloseClipboard();
}
}
return 0;
}
I wasn't going to post an answer initially, after all you already have a good enough answer. But I guess I was coerced into doing so anyway, besides, this is the primary question that pops when you search for GetClipboardData CF_DIB, so might as well try to present a more complete solution.
Unfortunately, clipboard formats are a minefield. And GDI bitmaps are an even bigger minefield. CF_DIB gives you a "packed DIB", you do need to parse it to some extent if you actually want to do anything meaningful with it. This is the layout (pseudo code):
struct PACKED_DIB {
struct BITMAPINFO {
BITMAPINFOHEADER bih; // 40 bytes
DWORD optional_RGB_bitmaks[]; // (variable size)
DWORD optional_color_table[]; // (variable size)
}
BYTE pixel_data_array[]; // (variable size)
}
The total size of the structure is given by GlobalSize(). The crucial bit of information that is required for any further processing is the offset, in bytes, from the start of the BITMAPINFO structure to the start of the pixel data array. If the optional bitmasks and the color table are absent, this offset is constant 40 (sizeof(BITMAPINFOHEADER)). Whether this is the case depends entirely on how an application has put the bitmap into the clipboard. Most applications do this because it's the simplest way.
This code calculates that offset:
// Returns the offset, in bytes, from the start of the BITMAPINFO, to the start of the pixel data array, for a packed DIB.
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader)
{
INT OffsetExtra = 0;
if (BitmapInfoHeader->biSize == sizeof(BITMAPINFOHEADER) /* 40 */)
{
// This is the common BITMAPINFOHEADER type. In this case, there may be bit masks following the BITMAPINFOHEADER
// and before the actual pixel bits (does not apply if bitmap has <= 8 bpp)
if (BitmapInfoHeader->biBitCount > 8)
{
if (BitmapInfoHeader->biCompression == BI_BITFIELDS)
{
OffsetExtra += 3 * sizeof(RGBQUAD);
}
else if (BitmapInfoHeader->biCompression == 6 /* BI_ALPHABITFIELDS */)
{
// Not widely supported, but technically a valid DIB format.
// You *can* get this in the clipboard, although neither GDI nor stb_image will like it.
OffsetExtra += 4 * sizeof(RGBQUAD);
}
}
}
if (BitmapInfoHeader->biClrUsed > 0)
{
// We have no choice but to trust this value.
OffsetExtra += BitmapInfoHeader->biClrUsed * sizeof(RGBQUAD);
}
else
{
// In this case, the color table contains the maximum number for the current bit count (0 if > 8bpp)
if (BitmapInfoHeader->biBitCount <= 8)
{
// 1bpp: 2
// 4bpp: 16
// 8bpp: 256
OffsetExtra += sizeof(RGBQUAD) << BitmapInfoHeader->biBitCount;
}
}
return BitmapInfoHeader->biSize + OffsetExtra;
}
Below is a program that demonstrates several things you can do with this offset:
Write the clipboard image to a .bmp file
Load it from memory using SFML (sf::Image::loadFromMemory)
Put it back in the clipboard from an SFML image
Convert it to a HBITMAP (so it can be used in GDI)
Put it back in the clipboard from a GDI HBITMAP
#include <sdkddkver.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#if DEMO_SFML
#include <SFML/Graphics.hpp>
#endif
static BOOL OpenClipboard_ButTryABitHarder(HWND ClipboardOwner);
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader);
static void PutBitmapInClipboard_AsDIB(HBITMAP hBitmap);
static void PutBitmapInClipboard_From32bppTopDownRGBAData(INT Width, INT Height, const void *Data32bppRGBA);
int wmain(int argc, wchar_t *argv[])
{
if (!OpenClipboard_ButTryABitHarder(NULL))
{
// Could not open clipboard. This usually indicates that another application is permanently blocking it.
return 1;
}
HGLOBAL ClipboardDataHandle = (HGLOBAL)GetClipboardData(CF_DIB);
if (!ClipboardDataHandle)
{
// Clipboard object is not a DIB, and is not auto-convertible to DIB
CloseClipboard();
return 0;
}
BITMAPINFOHEADER *BitmapInfoHeader = (BITMAPINFOHEADER *)GlobalLock(ClipboardDataHandle);
assert(BitmapInfoHeader); // This can theoretically fail if mapping the HGLOBAL into local address space fails. Very pathological, just act as if it wasn't a bitmap in the clipboard.
SIZE_T ClipboardDataSize = GlobalSize(ClipboardDataHandle);
assert(ClipboardDataSize >= sizeof(BITMAPINFOHEADER)); // Malformed data. While older DIB formats exist (e.g. BITMAPCOREHEADER), they are not valid data for CF_DIB; it mandates a BITMAPINFO struct. If this fails, just act as if it wasn't a bitmap in the clipboard.
INT PixelDataOffset = GetPixelDataOffsetForPackedDIB(BitmapInfoHeader);
// ============================================================================================================
// ============================================================================================================
//
// Example 1: Write it to a .bmp file
//
// The clipboard contains a packed DIB, whose start address coincides with BitmapInfoHeader, and whose total size is ClipboardDataSize.
// By definition, we can jam the whole DIB memory into a BMP file as-is, except that we need to prepend a BITMAPFILEHEADER struct.
// The tricky part is that for BITMAPFILEHEADER.bfOffBits, which must be calculated using the information in BITMAPINFOHEADER.
// The BMP file layout:
// #offset 0: BITMAPFILEHEADER
// #offset 14 (sizeof(BITMAPFILEHEADER)): BITMAPINFOHEADER
// #offset 14 + BitmapInfoHeader->biSize: Optional bit masks and color table
// #offset 14 + DIBPixelDataOffset: pixel bits
// #offset 14 + ClipboardDataSize: EOF
size_t TotalBitmapFileSize = sizeof(BITMAPFILEHEADER) + ClipboardDataSize;
wprintf(L"BITMAPINFOHEADER size: %u\r\n", BitmapInfoHeader->biSize);
wprintf(L"Format: %hubpp, Compression %u\r\n", BitmapInfoHeader->biBitCount, BitmapInfoHeader->biCompression);
wprintf(L"Pixel data offset within DIB: %u\r\n", PixelDataOffset);
wprintf(L"Total DIB size: %zu\r\n", ClipboardDataSize);
wprintf(L"Total bitmap file size: %zu\r\n", TotalBitmapFileSize);
BITMAPFILEHEADER BitmapFileHeader = {};
BitmapFileHeader.bfType = 0x4D42;
BitmapFileHeader.bfSize = (DWORD)TotalBitmapFileSize; // Will fail if bitmap size is nonstandard >4GB
BitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + PixelDataOffset;
HANDLE FileHandle = CreateFileW(L"test.bmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (FileHandle != INVALID_HANDLE_VALUE)
{
DWORD dummy = 0;
BOOL Success = true;
Success &= WriteFile(FileHandle, &BitmapFileHeader, sizeof(BITMAPFILEHEADER), &dummy, NULL);
Success &= WriteFile(FileHandle, BitmapInfoHeader, (DWORD)ClipboardDataSize, &dummy, NULL);
Success &= CloseHandle(FileHandle);
if (Success)
{
wprintf(L"File saved.\r\n");
}
}
#if DEMO_SFML
// ============================================================================================================
// ============================================================================================================
//
// Example 2: Load it from memory in SFML
//
// SFML expects a whole bitmap file, including its BITMAPFILEHEADER, in memory.
// So this is similar to Example 1, except in memory.
BYTE *BitmapFileContents = (BYTE *)malloc(TotalBitmapFileSize);
assert(BitmapFileContents);
memcpy(BitmapFileContents, &BitmapFileHeader, sizeof(BITMAPFILEHEADER));
// Append DIB
memcpy(BitmapFileContents + sizeof(BITMAPFILEHEADER), BitmapInfoHeader, ClipboardDataSize);
sf::Image image;
image.loadFromMemory(BitmapFileContents, TotalBitmapFileSize);
// The memory can be freed once the image has been loaded in SFML.
free(BitmapFileContents);
// Manipulate it:
image.flipHorizontally();
// Put it back in the clipboard:
PutBitmapInClipboard_From32bppTopDownRGBAData(image.getSize().x, image.getSize().y, image.getPixelsPtr());
#else
// ============================================================================================================
// ============================================================================================================
//
// Example 3: Convert to HBITMAP for GDI
//
BYTE *PixelDataFromClipboard = (BYTE *)BitmapInfoHeader + PixelDataOffset;
// This will only work if the DIB format is supported by GDI. Not all formats are supported.
BYTE *PixelDataNew;
HBITMAP hBitmap = CreateDIBSection(NULL, (BITMAPINFO *)BitmapInfoHeader, DIB_RGB_COLORS, (void **)&PixelDataNew, NULL, 0);
assert(hBitmap);
// Need to copy the data from the clipboard to the new DIBSection.
BITMAP BitmapDesc = {};
GetObjectW(hBitmap, sizeof(BitmapDesc), &BitmapDesc);
SIZE_T PixelDataBytesToCopy = (SIZE_T)BitmapDesc.bmHeight * BitmapDesc.bmWidthBytes;
SIZE_T PixelDataBytesAvailable = ClipboardDataSize - PixelDataOffset;
if (PixelDataBytesAvailable < PixelDataBytesToCopy)
{
// Malformed data; doesn't contain enough pixels.
PixelDataBytesToCopy = PixelDataBytesAvailable;
}
memcpy(PixelDataNew, PixelDataFromClipboard, PixelDataBytesToCopy);
// NOTE: While it is possible to create a DIB section without copying the pixel data, in general you'd want to
// copy it anyway because the clipboard needs to be closed asap.
// Draw something on it.
PixelDataNew[7] = 0;
PixelDataNew[11] = 100;
HDC hdc = CreateCompatibleDC(NULL);
assert(hdc);
SelectObject(hdc, hBitmap);
RECT rc = { 0, 0, BitmapDesc.bmWidth / 2, BitmapDesc.bmHeight / 2 };
HBRUSH brush = CreateSolidBrush(RGB(250, 100, 0));
FillRect(hdc, &rc, brush);
DeleteObject(brush);
DeleteDC(hdc);
// ============================================================================================================
// ============================================================================================================
//
// Copy it back to the clipboard.
//
PutBitmapInClipboard_AsDIB(hBitmap);
#endif // DEMO_SFML
GlobalUnlock(ClipboardDataHandle);
CloseClipboard();
return 0;
}
static BOOL OpenClipboard_ButTryABitHarder(HWND hWnd)
{
for (int i = 0; i < 20; ++i)
{
// This can fail if the clipboard is currently being accessed by another application.
if (OpenClipboard(hWnd)) return true;
Sleep(10);
}
return false;
}
// Returns the offset, in bytes, from the start of the BITMAPINFO, to the start of the pixel data array, for a packed DIB.
static INT GetPixelDataOffsetForPackedDIB(const BITMAPINFOHEADER *BitmapInfoHeader)
{
INT OffsetExtra = 0;
if (BitmapInfoHeader->biSize == sizeof(BITMAPINFOHEADER) /* 40 */)
{
// This is the common BITMAPINFOHEADER type. In this case, there may be bit masks following the BITMAPINFOHEADER
// and before the actual pixel bits (does not apply if bitmap has <= 8 bpp)
if (BitmapInfoHeader->biBitCount > 8)
{
if (BitmapInfoHeader->biCompression == BI_BITFIELDS)
{
OffsetExtra += 3 * sizeof(RGBQUAD);
}
else if (BitmapInfoHeader->biCompression == 6 /* BI_ALPHABITFIELDS */)
{
// Not widely supported, but valid.
OffsetExtra += 4 * sizeof(RGBQUAD);
}
}
}
if (BitmapInfoHeader->biClrUsed > 0)
{
// We have no choice but to trust this value.
OffsetExtra += BitmapInfoHeader->biClrUsed * sizeof(RGBQUAD);
}
else
{
// In this case, the color table contains the maximum number for the current bit count (0 if > 8bpp)
if (BitmapInfoHeader->biBitCount <= 8)
{
// 1bpp: 2
// 4bpp: 16
// 8bpp: 256
OffsetExtra += sizeof(RGBQUAD) << BitmapInfoHeader->biBitCount;
}
}
return BitmapInfoHeader->biSize + OffsetExtra;
}
// Helper function for interaction with libraries like stb_image.
// Data will be copied, so you can do what you want with it after this function returns.
static void PutBitmapInClipboard_From32bppTopDownRGBAData(INT Width, INT Height, const void *Data32bppRGBA)
{
// Nomenclature: Data at offset 0 is R top left corner, offset 1 is G top left corner, etc.
// This is pretty much the opposite of what a HBITMAP normally does.
assert(Width > 0);
assert(Height > 0);
assert(Data32bppRGBA);
// GDI won't help us here if we want to preserve the alpha channel. It doesn't support BI_ALPHABITFIELDS, and
// we can't use BI_RGB directly because BI_RGB actually means BGRA in reality.
// That means, unfortunately it's not going to be a simple memcpy :(
DWORD PixelDataSize = 4/*32bpp*/ * Width * Height;
// We need BI_BITFIELDS for RGB color masks here.
size_t TotalSize = sizeof(BITMAPINFOHEADER) + PixelDataSize;
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, TotalSize);
assert(hGlobal);
void *mem = GlobalLock(hGlobal);
assert(mem);
BITMAPINFOHEADER *bih = (BITMAPINFOHEADER *)mem;
bih->biSize = sizeof(BITMAPINFOHEADER);
bih->biWidth = Width;
bih->biHeight = -Height; // Negative height means top-down bitmap
bih->biPlanes = 1;
bih->biBitCount = 32;
bih->biCompression = BI_RGB;
bih->biSizeImage = PixelDataSize;
BYTE *PixelData = (BYTE *)mem + sizeof(BITMAPINFOHEADER);
DWORD NumPixels = Width * Height;
for (DWORD i = 0; i < NumPixels; ++i)
{
// Convert RGBA to BGRA
DWORD tmp = ((DWORD *)Data32bppRGBA)[i];
DWORD tmp2 = tmp & 0xff00ff00; // assumes LE
tmp2 |= (tmp >> 16) & 0xff;
tmp2 |= (tmp & 0xff) << 16;
((DWORD *)PixelData)[i] = tmp2;
}
GlobalUnlock(hGlobal);
EmptyClipboard();
SetClipboardData(CF_DIB, hGlobal);
// The hGlobal now belongs to the clipboard. Do not free it.
}
// Bitmap will be copied, so you can do what you want with it after this function returns.
static void PutBitmapInClipboard_AsDIB(HBITMAP hBitmap)
{
// Need this to get the bitmap dimensions.
BITMAP desc = {};
int tmp = GetObjectW(hBitmap, sizeof(desc), &desc);
assert(tmp != 0);
// We need to build this structure in a GMEM_MOVEABLE global memory block:
// BITMAPINFOHEADER (40 bytes)
// PixelData (4 * Width * Height bytes)
// We're enforcing 32bpp BI_RGB, so no bitmasks and no color table.
// NOTE: SetClipboardData(CF_DIB) insists on the size 40 version of BITMAPINFOHEADER, otherwise it will misinterpret the data.
DWORD PixelDataSize = 4/*32bpp*/ * desc.bmWidth * desc.bmHeight; // Correct alignment happens implicitly.
assert(desc.bmWidth > 0);
assert(desc.bmHeight > 0);
size_t TotalSize = sizeof(BITMAPINFOHEADER) + PixelDataSize;
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, TotalSize);
assert(hGlobal);
void *mem = GlobalLock(hGlobal);
assert(mem);
BITMAPINFOHEADER *bih = (BITMAPINFOHEADER *)mem;
bih->biSize = sizeof(BITMAPINFOHEADER);
bih->biWidth = desc.bmWidth;
bih->biHeight = desc.bmHeight;
bih->biPlanes = 1;
bih->biBitCount = 32;
bih->biCompression = BI_RGB;
bih->biSizeImage = PixelDataSize;
HDC hdc = CreateCompatibleDC(NULL);
assert(hdc);
HGDIOBJ old = SelectObject(hdc, hBitmap);
assert(old != nullptr); // This can fail if the hBitmap is still selected into a different DC.
void *PixelData = (BYTE *)mem + sizeof(BITMAPINFOHEADER);
// Pathologial "bug": If the bitmap is a DDB that originally belonged to a device with a different palette, that palette is lost. The caller would need to give us the correct HDC, but this is already insane enough as it is.
tmp = GetDIBits(hdc, hBitmap, 0, desc.bmHeight, PixelData, (BITMAPINFO *)bih, DIB_RGB_COLORS);
assert(tmp != 0);
// NOTE: This will correctly preserve the alpha channel if possible, but it's up to the receiving application to handle it.
DeleteDC(hdc);
GlobalUnlock(hGlobal);
EmptyClipboard();
SetClipboardData(CF_DIB, hGlobal);
// The hGlobal now belongs to the clipboard. Do not free it.
}
I intend this code to be mostly production-ready because I need it for myself, if anyone finds a problem I'd be happy to hear about it.
Some additional notes for reference:
Tested on Win10
Tested on WinXP (except SFML), although the %zu doesn't work in older CRTs, who knew
Error handling is not production-ready.
What the explanation of CF_DIB really wanted to say is "it's a packed DIB". There is no official guarantee that it will be a plain BITMAPINFOHEADER, i.e. biSize == 40 though, although it is likely that this is the case.
The BITMAPFINO documentation explains that the structure is really variable in length, and that BITMAPINFOHEADER.biClrUsed needs to be taken into account, but it fails to mention BI_BITFIELDS.
BITMAPINFOHEADER has more details on this, but fails to mention BI_ALPHABITFIELDS or the fact that the bitmasks are only present if the polymorphic BITMAPINFOHEADER struct is actually a plain BITMAPINFOHEADER (i.e. biSize == 40). Later versions, like BITMAPV5HEADER, include the bitmasks unconditionally.
All in all, the wikipedia article on the BMP file format contains a much more concise and coherent explanation of the DIB memory layout.
Older versions of Paint handled the clipboard, including the offset calculation in a very similar fashion to what I did above in GetDIBPixelDataOffset (obviously I can't post that verbatim here). It does not assume that biSize == 40. Newer versions of Paint use COleServerItem for clipboard handling.
As a final reference, the source code of GTK, which is used by GIMP and other cross-platform software, implements CF_DIB very similarly. That code even handles web browser specific formats, so a bit harder to follow. It's the transmute_cf_dib_to_image_bmp function. The function's length parameter comes from GlobalSize. Note that it also does not assume that biSize == 40.
If SetClipboardData is called with CF_BITMAP, it requires a DDB (it will silently fail if you pass it a HBITMAP that is really a DIB). A CF_BITMAP that is implicitly converted to a DIB uses BI_BITFIELDS, this also applies to screenshots (at least if the original DDB was compatible with the screen DC).
Putting bitmaps into the clipboard is a whole new can of worms. If a DDB is put in the clipboard with CF_BITMAP, that bitmap is not copied (at least not initially). If any program manipulates it, all programs accessing the clipboard will see the manipulated bitmap. However, as soon as any one application ever requests it as a CF_DIB, Windows applies a bunch of magic, and that is no longer true, the bitmap is now a copy. This does not apply to bitmaps that were put in the clipboard as CF_DIB, those immediately become immune to manipulations by other programs. CF_DIB seems to have fewer unpleasant implications and surprises, and also seems to be used by most applications. While you could try to preserve the original bitmap's format when putting it in the clipboard, I chose to use a fixed catch-all format for outgoing data because it's already crazy enough.
SetClipboardData implies that CF_DIB doesn't work with Windows Store apps, but I was unable to confirm that claim. Then again, the paragraph about a NULL owner is also incorrect.
Both PutBitmapInClipboard_AsDIB and PutBitmapInClipboard_From32bppTopDownRGBAData do preserve the alpha channel if possible, although GDI drawing functions as demonstrated don't support alpha and will destroy it (SFML will handle it just fine). Putting the alpha channel in the MSB and using BI_RGB seems to be the de-facto standard for storing alpha in DIBs.
I have a two dimensional array of data that I want to display as an image.
The plan goes something like this -
Create a bitmap using CreateCompatibleBitmap (this results in a solid black bitmap and I can display this with no problems)
Edit the pixels of this bitmap to match my data
BitBlt the bitmap to the window
I think that I need a pointer to the place in memory where the pixel data begins. I've tried many different methods of doing this and googled it for 3 days and still haven't been able to even edit a single pixel.
Using a loop of SetPixel(HDC, x, y, Color) to set each pixel works but VERY slowly.
I have accomplished this in C# by locking the bitmap and editing the bits, but I am new to C++ and can't seem to figure out how to do something similar.
I have mostly been trying to use memset(p, value, length)
For "p" I have tried using the handle returned from CreateCompatibleBitmap, the DC for the bitmap, and the DC for the window. I have tried all sorts of values for the value and length.
I'm not sure if this is the right thing to use though.
I don't have to use a bitmap, that's just the only thing I know to do. Actually it would be awesome to find a way to directly change the main window's DC.
I do want to avoid libraries though. I am doing this purely for learning C++.
This took QUITE a bit of research so I'll post exactly how it is done for anyone else who may be looking.
This colors every pixel red.
hDC = BeginPaint(hWnd, &Ps);
const int
width = 400,
height = 400,
size = width * height * 3;
byte * data;
data = new byte[size];
for (int i = 0; i < size; i += 3)
{
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 255;
}
BITMAPINFOHEADER bmih;
bmih.biBitCount = 24;
bmih.biClrImportant = 0;
bmih.biClrUsed = 0;
bmih.biCompression = BI_RGB;
bmih.biWidth = width;
bmih.biHeight = height;
bmih.biPlanes = 1;
bmih.biSize = 40;
bmih.biSizeImage = size;
BITMAPINFO bmpi;
bmpi.bmiHeader = bmih;
SetDIBitsToDevice(hDC, 0, 0, width, height, 0, 0, 0, height, data, &bmpi, DIB_RGB_COLORS);
delete[] data;
memset can be used on the actually RGB information array (but you need to also know the format of the bitmap, if a pixel has 32 or 24 bits ).
From a bit of research on msdn, it seems that what you want to get is the BITMAP structure :
http://msdn.microsoft.com/en-us/library/k1sf4cx2.aspx
There you have the bmBits on which you can memset.
How to get there from your function ?
Well, CreateCompatibleBitmap returns a HBITMAP structure and it seems you can get BITMAP from HBITMAP with the following code :
BITMAP bmp;
GetObject(hBmp, sizeof(BITMAP), &bmp);
This however seems to get a you copy of the existing bitmap info, which only solves your memset problem (you can now set the bitmap information with memset, eventhou I don't see any other use for memeset besides making the bmp all white or black).
There should be a function that allows you to set the DC bites to a bitmap thou, so you should be able to use the new bitmap as a parameter.
I want to read a rectangular area, or whole screen pixels. As if screenshot button was pressed.
How i do this?
Edit: Working code:
void CaptureScreen(char *filename)
{
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
HWND hDesktopWnd = GetDesktopWindow();
HDC hDesktopDC = GetDC(hDesktopWnd);
HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC, nScreenWidth, nScreenHeight);
SelectObject(hCaptureDC, hCaptureBitmap);
BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight, hDesktopDC, 0,0, SRCCOPY|CAPTUREBLT);
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = nScreenWidth;
bmi.bmiHeader.biHeight = nScreenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
RGBQUAD *pPixels = new RGBQUAD[nScreenWidth * nScreenHeight];
GetDIBits(
hCaptureDC,
hCaptureBitmap,
0,
nScreenHeight,
pPixels,
&bmi,
DIB_RGB_COLORS
);
// write:
int p;
int x, y;
FILE *fp = fopen(filename, "wb");
for(y = 0; y < nScreenHeight; y++){
for(x = 0; x < nScreenWidth; x++){
p = (nScreenHeight-y-1)*nScreenWidth+x; // upside down
unsigned char r = pPixels[p].rgbRed;
unsigned char g = pPixels[p].rgbGreen;
unsigned char b = pPixels[p].rgbBlue;
fwrite(fp, &r, 1);
fwrite(fp, &g, 1);
fwrite(fp, &b, 1);
}
}
fclose(fp);
delete [] pPixels;
ReleaseDC(hDesktopWnd, hDesktopDC);
DeleteDC(hCaptureDC);
DeleteObject(hCaptureBitmap);
}
Starting with your code and omitting error checking ...
// Create a BITMAPINFO specifying the format you want the pixels in.
// To keep this simple, we'll use 32-bits per pixel (the high byte isn't
// used).
BITMAPINFO bmi = {0};
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = nScreenWidth;
bmi.bmiHeader.biHeight = nScreenHeight;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
// Allocate a buffer to receive the pixel data.
RGBQUAD *pPixels = new RGBQUAD[nScreenWidth * nScreenHeight];
// Call GetDIBits to copy the bits from the device dependent bitmap
// into the buffer allocated above, using the pixel format you
// chose in the BITMAPINFO.
::GetDIBits(hCaptureDC,
hCaptureBitmap,
0, // starting scanline
nScreenHeight, // scanlines to copy
pPixels, // buffer for your copy of the pixels
&bmi, // format you want the data in
DIB_RGB_COLORS); // actual pixels, not palette references
// You can now access the raw pixel data in pPixels. Note that they are
// stored from the bottom scanline to the top, so pPixels[0] is the lower
// left pixel, pPixels[1] is the next pixel to the right,
// pPixels[nScreenWidth] is the first pixel on the second row from the
// bottom, etc.
// Don't forget to free the pixel buffer.
delete [] pPixels;
Rereading your question, it sounds like we may have gotten off on a tangent with the screen capture. If you just want to check some pixels on the screen, you can use GetPixel.
HDC hdcScreen = ::GetDC(NULL);
COLORREF pixel = ::GetPixel(hdcScreen, x, y);
ReleaseDC(NULL, hdcScreen);
if (pixel != CLR_INVALID) {
int red = GetRValue(pixel);
int green = GetGValue(pixel);
int blue = GetBValue(pixel);
...
} else {
// Error, x and y were outside the clipping region.
}
If you're going to read a lot of pixels, then you're better off with a screen capture and then using GetDIBits. Calling GetPixel zillions of times will be slow.
You make a screenshot with BitBlt(). The size of the shot is set with the nWidth and nHeight arguments. The upper left corner is set with the nXSrc and nYSrc arguments.
You can use the code below to read the screen pixels:
HWND desktop = GetDesktopWindow();
HDC desktopHdc = GetDC(desktop);
COLORREF color = GetPixel(desktopHdc, x, y);
HBITMAP is not a pointer or an array, it is a handle that is managed by Windows and has meaning only to Windows. You must ask Windows to copy the pixels somewhere for use.
To get an individual pixel value, you can use GetPixel without even needing a bitmap. This will be slow if you need to access many pixels.
To copy a bitmap to memory you can access, use the GetDIBits function.
I need to create a custom control to display bmp images with alpha channel. The background can be painted in different colors and the images have shadows so I need to truly "paint" the alpha channel.
Does anybody know how to do it?
I also want if possible to create a mask using the alpha channel information to know whether the mouse has been click on the image or on the transparent area.
Any kind of help will be appreciated!
Thanks.
Edited(JDePedro): As some of you have suggested I've been trying to use alpha blend to paint the bitmap with alpha channel. This just a test I've implemented where I load a 32-bit bitmap from resources and I try to paint it using AlphaBlend function:
void CAlphaDlg::OnPaint()
{
CClientDC dc(this);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP);
BITMAP BitMap;
bitmap.GetBitmap(&BitMap);
int nWidth = BitMap.bmWidth;
int nHeight = BitMap.bmHeight;
CBitmap *pOldBitmap = dcMem.SelectObject(&bitmap);
BLENDFUNCTION m_bf;
m_bf.BlendOp = AC_SRC_OVER;
m_bf.BlendFlags = 0;
m_bf.SourceConstantAlpha = 255;
m_bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(dc.GetSafeHdc(), 100, 100, nWidth, nHeight, dcMem.GetSafeHdc(), 0, 0,nWidth, nHeight,m_bf);
dcMem.SelectObject(pOldBitmap);
CDialog::OnPaint();
}
This is just a test so I put the code in the OnPaint of the dialog (I also tried the AlphaBlend function of the CDC object).
The non-transparent areas are being painted correctly but I get white where the bitmap should be transparent.
Any help???
This is a screenshot..it's not easy to see but there is a white rectangle around the blue circle:
alt text http://img385.imageshack.us/img385/7965/alphamh8.png
Ok. I got it! I have to pre-multiply every pixel for the alpha value. Someone can suggest the optimized way to do that?
For future google users, here is a working pre-multiply function. Note that this was taken from http://www.viksoe.dk/code/alphatut1.htm .
inline void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
BITMAP bm = { 0 };
GetObject(hBmp, sizeof(bm), &bm);
BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
if( pBitData == NULL ) return;
LPBYTE pData = pBitData;
::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
for( int y = 0; y < bm.bmHeight; y++ ) {
for( int x = 0; x < bm.bmWidth; x++ ) {
pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
pData += 4;
}
}
::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
::LocalFree(pBitData);
}
So then your OnPaint becomes:
void MyButton::OnPaint()
{
CPaintDC dc(this);
CRect rect(0, 0, 16, 16);
static bool pmdone = false;
if (!pmdone) {
PremultiplyBitmapAlpha(dc, m_Image);
pmdone = true;
}
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 255;
bf.AlphaFormat = AC_SRC_ALPHA;
HDC src_dc = m_Image.GetDC();
::AlphaBlend(dc, rect.left, rect.top, 16, 16, src_dc, 0, 0, 16, 16, bf);
m_Image.ReleaseDC();
}
And the loading of the image (in the constructor of your control):
if ((HBITMAP)m_Image == NULL) {
m_Image.LoadFromResource(::AfxGetResourceHandle(), IDB_RESOURCE_OF_32_BPP_BITMAP);
}
The way I usually do this is via a DIBSection - a device independent bitmap that you can modify the pixels of directly. Unfortunately there isn't any MFC support for DIBSections: you have to use the Win32 function CreateDIBSection() to use it.
Start by loading the bitmap as 32-bit RGBA (that is, four bytes per pixel: one red, one green, one blue and one for the alpha channel). In the control, create a suitably sized DIBSection. Then, in the paint routine
Copy the bitmap data into the DIBSection's bitmap data, using the alpha channel byte to blend the bitmap image with the background colour.
Create a device context and select the DIBSection into it.
Use BitBlt() to copy from the new device context to the paint device context.
You can create a mask given the raw bitmap data simply by looking at the alpha channel values - I'm not sure what you're asking here.
You need to do an alpha blend with your background color, then take out the alpha channel to paint it to the control.
The alpha channel should just be every 4th byte of your image. You can use that directly for your mask, or you can just copy every 4th byte to a new mask image.
Painting it is very easy with the AlphaBlend function.
As for you mask, you'll need to get the bits of the bitmap and examine the alpha channel byte for each pixel you're interested in.
An optimised way to pre-multiply the RGB channels with the alpha channel is to set up a [256][256] array containing the calculated multiplication results. The first dimension is the alpha value, the second is the R/G/B value, the values in the array are the pre-multiplied values you need.
With this array set up correctly, you can calculate the value you need like this:
R = multiplicationLookup[alpha][R];
G = multiplicationLookup[alpha][G];
B = multiplicationLookup[alpha][B];
You are on the right track, but need to fix two things.
First use ::LoadImage( .. LR_CREATEDIBSECTION ..) instead of CBitmap::LoadBitmap. Two, you have to "pre-multiply" RGB values of every pixel in a bitmap to their respective A value. This is a requirement of AlphaBlend function, see AlphaFormat description on this MSDN page. T
The lpng has a working code that does the premultiplication of the DIB data.