save jpeg from hBitmap to BYTE array [duplicate] - c++

I've got a HBITMAP that I want to save into a JPEG/PNG stream or array of bytes. The problem is that I'm using mingw as my compiler so I can't use CImage.. which would have made my life easier.
I can get the pixels from the bitmap without any problems, but I have no idea how to get access to them in JPEG/PNG-format.
Where do I start?

If you have access DirectX library you may use IStream to convert your image to JPEG
http://msdn.microsoft.com/en-us/library/windows/desktop/aa380034(v=vs.85).aspx
or if you have GDI+ something like this might work
Gdiplus::Bitmap bmp(hbmpImage,(HPALETTE)0);
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bmp.Save(L"D:\image.png",&pngClsid,NULL);
where GetEncoderLCLsid looks like this:
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
}
don't forget to initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if don't have access to either you may use libjpeg but you need to put all the dependencies packages from the GnuWin32 site. Much faster the code in this page should work, just forget about the boost
libjpeg dying without message

Another option is to use the WIC API (Windows Imaging Component), which gives direct access to image encoders and decoders. I believe GDI+ may use this under the covers.

Related

Creating GDI+ bitmaps in memory and then saving as png

I am new to C++ and been having trouble with writing a function using the GDI+ library to create a new bitmap in memory ( so not opening/reading an existing bitmap); then drawing on the bitmap; before saving it to png. In particular, I am having problems with the bitmap creation and saving code. I am constrained to using codeblocks and I can't use visual studios, even if I wanted to. The code is as follows:
#include "drawImage.h"
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
#include <stdio.h>
#include <iostream>
using namespace std;
using namespace Gdiplus;
drawImage::drawImage(){}
void drawImage::DrawBitmap(int width, int height){
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
{
//Create a bitmap
Bitmap myBitmap(width, height, PixelFormatCanonical);
Graphics g(&myBitmap);
Pen blackpen(Color(255,0,0,0), 3);
//draw on bitmap
int x1 = 1;
int x2 = 200;
int y1 = 1;
int y2 = 200;
g.DrawLine(&blackpen, x1,y1,x2,y2);
// Save bitmap (as a png)
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
myBitmap.Save(L"C:\\test\\test.png", &pngClsid, NULL);
}
GdiplusShutdown(gdiplusToken);
}
The issues I am having are as follows:
The 'saving' code does not compile and gives the error message "'GetEncoderClsid' was not declared in this scope". However, I got this direct from the Microsoft website here. I don't think this is the proper way of converting to png but I dont know an alternative way?
When the code is compiled and run (by commenting out the saving code), it then crashes on the line "Bitmap *myBitmap = new Bitmap(width, height, PixelFormatCanonical);" and gives an error message saying my executable has stopped working.
I have added the 'gdi32' linker library and also '-lgdiplus' as a linker option. Also, I have used this website to help with the gdi stuff although the section on bitmaps only deals with loading existing bitmaps (not creating new ones in memory)
I am totally lost on what to do, so any help or advice on this matter is much appreciated.
The core issue is passing the wrong pixel format to the Bitmap constructor. PixelFormatCanonical is not one of the supported pixel formats. It's a bit mask used to determine whether a pixel format is canonical (see IsCanonicalPixelFormat). You'll have to use a real pixel format, like the default PixelFormat32bppARGB.
The following code produces the desired output:
First up, a small helper class for GDI+ initialization. This ensures, that the d'tor (i.e. the call to GdiplusShutdown) is executed after all other objects have been destroyed. With respect to order of destruction, it serves the same purpose as the additional scope in the OP. In addition, it also allows for exceptions to be thrown.
#include <windows.h>
#include <gdiplus.h>
using namespace Gdiplus;
#include <stdexcept>
using std::runtime_error;
struct GdiplusInit {
GdiplusInit() {
GdiplusStartupInput inp;
GdiplusStartupOutput outp;
if ( Ok != GdiplusStartup( &token_, &inp, &outp ) )
throw runtime_error( "GdiplusStartup" );
}
~GdiplusInit() {
GdiplusShutdown( token_ );
}
private:
ULONG_PTR token_;
};
This code was taken from the MSDN sample Retrieving the Class Identifier for an Encoder.
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
}
Finally, the GDI+ rendering code. It uses objects with automatic storage duration throughout, making it more compact and safer.
void drawImage( int width, int height ) {
GdiplusInit gdiplusinit;
//Create a bitmap
Bitmap myBitmap( width, height, PixelFormat32bppARGB );
Graphics g( &myBitmap );
Pen blackpen( Color( 255, 0, 0, 0 ), 3 );
//draw on bitmap
g.DrawLine( &blackpen, 1, 1, 200, 200 );
// Save bitmap (as a png)
CLSID pngClsid;
int result = GetEncoderClsid( L"image/png", &pngClsid );
if ( result == -1 )
throw runtime_error( "GetEncoderClsid" );
if ( Ok != myBitmap.Save( L"C:\\test\\test.png", &pngClsid, NULL ) )
throw runtime_error( "Bitmap::Save" );
}
int main()
{
drawImage( 200, 200 );
return 0;
}
Note: It looks like GetEncoderClsid shouldn't be required, since those are well-known constants. However, trying to pass the appropriate WIC CLSID (CLSID_WICPngEncoder) to Bitmap::Save only produced a FileNotFound error.
For 1.: GetEncoderClsid is not a library function, it is an example helper defined here. If you want to use it copy the code from the site. (Btw your link explicitly states this.)
For 2.: You need to call GdiplusStartup before creating any GDI+ object or calling any GDI+ function. See its documentation here.
To be more precise about GdiplusShutdown because that seems to be making you new problems: It is required that all GDI+ objects are destroyed before GdiplusShutdown is called. That means that objects with static storage (for example pen) must go out of scope before GdiplusShutdown is called. That is not the case if you call it in DrawBitmap in the way you do now. Either call GdiplusStartup and GdiplusShutdown in main or add additional brackets {} around your code between GdiplusStartup and GdiplusShutdown, because these introduce a new scope and pen would be destroyed when } is reached.
Edit: Nr. 2 was fixed in the question by edit. For the remaining problem and improvement of the code see #IInspectable's answer.

How to read a bitmap from the Windows Clipboard

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.

GDI+ - converting bitmap pixels to JPG format

I have an array of a bitmap pixels. How can I convert them to JPG format and copy to another array? How to convert them back to bitmap from JPG pixels?
Checkout the Encoder CLSID function from here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms533843(v=vs.85).aspx
Modified original code from https://vctipsplusplus.wordpress.com/tag/image-conversion-gdi/:
int main()
{
// Initialize GDI+.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CLSID encoderClsid;
Status stat;
Image* image = new Image(L”Bird.bmp”);
// Get the CLSID of the JPEG encoder.
GetEncoderClsid(L”image/jpeg”, &encoderClsid);
stat = image->Save(L”Bird.png”, &encoderClsid, NULL);
if(stat == Ok)
printf(“Bird.png was saved successfully\n”);
else
printf(“Failure: stat = %d\n”, stat);
delete image;
GdiplusShutdown(gdiplusToken);
return 0;
}
Just change image/jpeg to whatever format you want to convert. The details are given on the MSDN link I mentioned above. Of course to work with the pixels, you'll need to convert the JPEG to BMP
You can create a memory stream using CreateStreamOnHGlobal or SHCreateMemStream, then use the GDI+ method Image.Save to save to the stream. Reverse the process to read back in.

updating jpeg properties in c++ using GDI+

I am tring to copy properties from one jpeg image to another in c++. As I don't have any library and I am working on windows, I am using GDI+ for this.
My code is as follow
void CopyJpegProperties(string targetImageName,string sourceImageName)
{
CLSID m_clsid;
EncoderParameters m_encoderParameters;
UINT size;
UINT count;
int quality=100;
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR m_gdiplusToken;
// start GDI+
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
{
// we are processing images inside a block to make sure that all of objects are deleted when GDI is shutdown.
// read source image properties
std::wstring wcSourceImageName=StringTools::StringToWString(sourceImageName);
Gdiplus::Bitmap sourceBitmap(wcSourceImageName.c_str());
sourceBitmap.GetPropertySize(&size, &count);
PropertyItem* pPropBuffer =(PropertyItem*)malloc(size);
sourceBitmap.GetAllPropertyItems(size, count, pPropBuffer);
// write to target image
std::wstring wcTargetImageName=StringTools::StringToWString(targetImageName);
Gdiplus::Bitmap targetBitmap(wcTargetImageName.c_str());
for(int i=0; i<count; i++)
{
targetBitmap.SetPropertyItem(&pPropBuffer[i]);
}
JpegTools::GetEncoderClsid(L"image/jpeg", &m_clsid);
m_encoderParameters.Count = 1;
m_encoderParameters.Parameter[0].Guid = EncoderQuality;
m_encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
m_encoderParameters.Parameter[0].NumberOfValues = 1;
m_encoderParameters.Parameter[0].Value = &quality;
Status stat = targetBitmap.Save(wcTargetImageName.c_str(), &m_clsid, &m_encoderParameters);
if(stat != Ok)
{
throw exception("Error in saving");
}
}
GdiplusShutdown(m_gdiplusToken);
}
This function fails with error code 7 which win32error.
I think the problem is that when I open the target file I can not write to it as it is locked by open function.
How can I fix it?
Is there any better way to read/write image properties in windows?

HBITMAP to JPEG /PNG without CImage in C++

I've got a HBITMAP that I want to save into a JPEG/PNG stream or array of bytes. The problem is that I'm using mingw as my compiler so I can't use CImage.. which would have made my life easier.
I can get the pixels from the bitmap without any problems, but I have no idea how to get access to them in JPEG/PNG-format.
Where do I start?
If you have access DirectX library you may use IStream to convert your image to JPEG
http://msdn.microsoft.com/en-us/library/windows/desktop/aa380034(v=vs.85).aspx
or if you have GDI+ something like this might work
Gdiplus::Bitmap bmp(hbmpImage,(HPALETTE)0);
CLSID pngClsid;
GetEncoderClsid(L"image/png", &pngClsid);
bmp.Save(L"D:\image.png",&pngClsid,NULL);
where GetEncoderLCLsid looks like this:
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
}
don't forget to initialize GDI+
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
if don't have access to either you may use libjpeg but you need to put all the dependencies packages from the GnuWin32 site. Much faster the code in this page should work, just forget about the boost
libjpeg dying without message
Another option is to use the WIC API (Windows Imaging Component), which gives direct access to image encoders and decoders. I believe GDI+ may use this under the covers.