Save HBITMAP to *.bmp file using only Win32 - c

I have a HBITMAP in my pure Win32 project (no external libraries are used). Can I export it to a *.bmp file using only Winapi and/or CRT functions so I don't have to add dependencies to the project?

There is no API to save into file directly because, generally, having a bitmap handle does not mean you have direct access to bitmap data. Your solution is to copy bitmap into another bitmap with data access (DIB) and then using it data to write into file.
You typically either create another bitmap using CreateDIBSection, or you get bitmap data with GetDIBits.
CreateFile, WriteFile writes data into file.
You write: BITMAPFILEHEADER, then BITMAPINFOHEADER, then palette (which you typically don't have when bits/pixel is over 8), then data itself.
See also:
C++: Hbitmap/BITMAP into .bmp file (this answer)
Saving .bmp file using hBitmap = CreateDIBSection() in C Win32
Storing an Image on MSDN
The Code
This is the code from the MSDN article (Note that you need to define the errhandler() function):
PBITMAPINFO CreateBitmapInfoStruct(HWND hwnd, HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
if (!GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp))
errhandler("GetObject", hwnd);
// Convert the color format to a count of bits.
cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24)
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * (1<< cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel
else
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24)
pbmi->bmiHeader.biClrUsed = (1<<cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
// The width must be DWORD aligned unless the bitmap is RLE
// compressed.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
* pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
void CreateBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi,
HBITMAP hBMP, HDC hDC)
{
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
pbih = (PBITMAPINFOHEADER) pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
if (!lpBits)
errhandler("GlobalAlloc", hwnd);
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS))
{
errhandler("GetDIBits", hwnd);
}
// Create the .BMP file.
hf = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
(DWORD) 0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
if (hf == INVALID_HANDLE_VALUE)
errhandler("CreateFile", hwnd);
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
(LPDWORD) &dwTmp, NULL))
{
errhandler("WriteFile", hwnd);
}
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, ( NULL)))
errhandler("WriteFile", hwnd);
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL))
errhandler("WriteFile", hwnd);
// Close the .BMP file.
if (!CloseHandle(hf))
errhandler("CloseHandle", hwnd);
// Free memory.
GlobalFree((HGLOBAL)lpBits);
}

One function code for HBITMAP to *.bmp file.
BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName)
{
HDC hDC;
int iBits;
WORD wBitCount;
DWORD dwPaletteSize = 0, dwBmBitsSize = 0, dwDIBSize = 0, dwWritten = 0;
BITMAP Bitmap0;
BITMAPFILEHEADER bmfHdr;
BITMAPINFOHEADER bi;
LPBITMAPINFOHEADER lpbi;
HANDLE fh, hDib, hPal, hOldPal2 = NULL;
hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
DeleteDC(hDC);
if (iBits <= 1)
wBitCount = 1;
else if (iBits <= 4)
wBitCount = 4;
else if (iBits <= 8)
wBitCount = 8;
else
wBitCount = 24;
GetObject(hBitmap, sizeof(Bitmap0), (LPSTR)&Bitmap0);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = Bitmap0.bmWidth;
bi.biHeight = -Bitmap0.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = wBitCount;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrImportant = 0;
bi.biClrUsed = 256;
dwBmBitsSize = ((Bitmap0.bmWidth * wBitCount + 31) & ~31) / 8
* Bitmap0.bmHeight;
hDib = GlobalAlloc(GHND, dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;
hPal = GetStockObject(DEFAULT_PALETTE);
if (hPal)
{
hDC = GetDC(NULL);
hOldPal2 = SelectPalette(hDC, (HPALETTE)hPal, FALSE);
RealizePalette(hDC);
}
GetDIBits(hDC, hBitmap, 0, (UINT)Bitmap0.bmHeight, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER)
+ dwPaletteSize, (BITMAPINFO *)lpbi, DIB_RGB_COLORS);
if (hOldPal2)
{
SelectPalette(hDC, (HPALETTE)hOldPal2, TRUE);
RealizePalette(hDC);
ReleaseDC(NULL, hDC);
}
fh = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (fh == INVALID_HANDLE_VALUE)
return FALSE;
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
WriteFile(fh, (LPSTR)lpbi, dwDIBSize, &dwWritten, NULL);
GlobalUnlock(hDib);
GlobalFree(hDib);
CloseHandle(fh);
return TRUE;
}

Yet another minimalistic option is to use OLE's IPicture. It's always been around, still a part of Win32 API:
#define _S(exp) (([](HRESULT hr) { if (FAILED(hr)) _com_raise_error(hr); return hr; })(exp));
PICTDESC pictdesc = {};
pictdesc.cbSizeofstruct = sizeof(pictdesc);
pictdesc.picType = PICTYPE_BITMAP;
pictdesc.bmp.hbitmap = hBitmap;
CComPtr<IPicture> picture;
_S( OleCreatePictureIndirect(&pictdesc, __uuidof(IPicture), FALSE, (LPVOID*)&picture) );
// Save to a stream
CComPtr<IStream> stream;
_S( CreateStreamOnHGlobal(NULL, TRUE, &stream) );
LONG cbSize = 0;
_S( picture->SaveAsFile(stream, TRUE, &cbSize) );
// Or save to a file
CComPtr<IPictureDisp> disp;
_S( picture->QueryInterface(&disp) );
_S( OleSavePictureFile(disp, CComBSTR("C:\\Temp\\File.bmp")) );

i'll leave this self contained proof of concept here since I'll probably need to look it up later since it's not obvious. It takes a screenshot of the desktop window and saves it into bitmap.bmp:
#include <Windows.h>
#include <stdio.h>
#include <assert.h>
/* forward declarations */
PBITMAPINFO CreateBitmapInfoStruct(HBITMAP);
void CreateBMPFile(LPTSTR pszFile, HBITMAP hBMP);
int main(int argc, char **argv);
PBITMAPINFO CreateBitmapInfoStruct(HBITMAP hBmp)
{
BITMAP bmp;
PBITMAPINFO pbmi;
WORD cClrBits;
// Retrieve the bitmap color format, width, and height.
assert(GetObject(hBmp, sizeof(BITMAP), (LPSTR)&bmp));
// Convert the color format to a count of bits.
cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
if (cClrBits == 1)
cClrBits = 1;
else if (cClrBits <= 4)
cClrBits = 4;
else if (cClrBits <= 8)
cClrBits = 8;
else if (cClrBits <= 16)
cClrBits = 16;
else if (cClrBits <= 24)
cClrBits = 24;
else cClrBits = 32;
// Allocate memory for the BITMAPINFO structure. (This structure
// contains a BITMAPINFOHEADER structure and an array of RGBQUAD
// data structures.)
if (cClrBits < 24)
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * (1<< cClrBits));
// There is no RGBQUAD array for these formats: 24-bit-per-pixel or 32-bit-per-pixel
else
pbmi = (PBITMAPINFO) LocalAlloc(LPTR,
sizeof(BITMAPINFOHEADER));
// Initialize the fields in the BITMAPINFO structure.
pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pbmi->bmiHeader.biWidth = bmp.bmWidth;
pbmi->bmiHeader.biHeight = bmp.bmHeight;
pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
if (cClrBits < 24)
pbmi->bmiHeader.biClrUsed = (1<<cClrBits);
// If the bitmap is not compressed, set the BI_RGB flag.
pbmi->bmiHeader.biCompression = BI_RGB;
// Compute the number of bytes in the array of color
// indices and store the result in biSizeImage.
// The width must be DWORD aligned unless the bitmap is RLE
// compressed.
pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
* pbmi->bmiHeader.biHeight;
// Set biClrImportant to 0, indicating that all of the
// device colors are important.
pbmi->bmiHeader.biClrImportant = 0;
return pbmi;
}
void CreateBMPFile(LPTSTR pszFile, HBITMAP hBMP)
{
HANDLE hf; // file handle
BITMAPFILEHEADER hdr; // bitmap file-header
PBITMAPINFOHEADER pbih; // bitmap info-header
LPBYTE lpBits; // memory pointer
DWORD dwTotal; // total count of bytes
DWORD cb; // incremental count of bytes
BYTE *hp; // byte pointer
DWORD dwTmp;
PBITMAPINFO pbi;
HDC hDC;
hDC = CreateCompatibleDC(GetWindowDC(GetDesktopWindow()));
SelectObject(hDC, hBMP);
pbi = CreateBitmapInfoStruct(hBMP);
pbih = (PBITMAPINFOHEADER) pbi;
lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);
assert(lpBits) ;
// Retrieve the color table (RGBQUAD array) and the bits
// (array of palette indices) from the DIB.
assert(GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
DIB_RGB_COLORS));
// Create the .BMP file.
hf = CreateFile(pszFile,
GENERIC_READ | GENERIC_WRITE,
(DWORD) 0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
assert(hf != INVALID_HANDLE_VALUE) ;
hdr.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
// Compute the size of the entire file.
hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof(RGBQUAD) + pbih->biSizeImage);
hdr.bfReserved1 = 0;
hdr.bfReserved2 = 0;
// Compute the offset to the array of color indices.
hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
pbih->biSize + pbih->biClrUsed
* sizeof (RGBQUAD);
// Copy the BITMAPFILEHEADER into the .BMP file.
assert(WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
(LPDWORD) &dwTmp, NULL));
// Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
assert(WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
+ pbih->biClrUsed * sizeof (RGBQUAD),
(LPDWORD) &dwTmp, ( NULL)));
// Copy the array of color indices into the .BMP file.
dwTotal = cb = pbih->biSizeImage;
hp = lpBits;
assert(WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL));
// Close the .BMP file.
assert(CloseHandle(hf));
// Free memory.
GlobalFree((HGLOBAL)lpBits);
}
int main(int argc, char **argv)
{
HWND hwnd;
HDC hdc[2];
HBITMAP hbitmap;
RECT rect;
hwnd = GetDesktopWindow();
GetClientRect(hwnd, &rect);
hdc[0] = GetWindowDC(hwnd);
hbitmap = CreateCompatibleBitmap(hdc[0], rect.right, rect.bottom);
hdc[1] = CreateCompatibleDC(hdc[0]);
SelectObject(hdc[1], hbitmap);
BitBlt (
hdc[1],
0,
0,
rect.right,
rect.bottom,
hdc[0],
0,
0,
SRCCOPY
);
CreateBMPFile("bitmap.bmp", hbitmap);
return 0;
}

Yes, this is possible, using the Windows Imaging Component (WIC). WIC offers built-in encoders, so that you don't have to manually write out bitmap headers and data. It also allows you to choose a different encoder (e.g. PNG), by changing as little as one line of code.
The process is fairly straight forward. It consists of the following steps:
Retrieve properties from the source HBITMAP using GetObject (dimensions, bit depth).
Create an IWICImagingFactory instance.
Create an IWICBitmap instance from the HBITMAP (IWICImagingFactory::CreateBitmapFromHBITMAP).
Create an IWICStream instance (IWICImagingFactory::CreateStream), and attach it to a filename (IWICStream::InitializeFromFilename).
Create an IWICBitmapEncoder instance (IWICImagingFactory::CreateEncoder), and associate it with the stream (IWICBitmapEncoder::Initialize).
Create an IWICBitmapFrameEncode instance (IWICBitmapEncoder::CreateNewFrame), and initialize it in compliance with the source HBITMAP (IWICBitmapFrameEncode::Initialize, IWICBitmapFrameEncode::SetSize, IWICBitmapFrameEncode::SetPixelFormat).
Write bitmap data to the frame (IWICBitmapFrameEncode::WriteSource).
Commit frame and data to stream (IWICBitmapFrameEncode::Commit, IWICBitmapEncoder::Commit).
Translated to code:
#define COBJMACROS
#include <Objbase.h>
#include <wincodec.h>
#include <Windows.h>
#include <Winerror.h>
#pragma comment(lib, "Windowscodecs.lib")
HRESULT WriteBitmap(HBITMAP bitmap, const wchar_t* pathname) {
HRESULT hr = S_OK;
// (1) Retrieve properties from the source HBITMAP.
BITMAP bm_info = { 0 };
if (!GetObject(bitmap, sizeof(bm_info), &bm_info))
hr = E_FAIL;
// (2) Create an IWICImagingFactory instance.
IWICImagingFactory* factory = NULL;
if (SUCCEEDED(hr))
hr = CoCreateInstance(&CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER,
&IID_IWICImagingFactory, &factory);
// (3) Create an IWICBitmap instance from the HBITMAP.
IWICBitmap* wic_bitmap = NULL;
if (SUCCEEDED(hr))
hr = IWICImagingFactory_CreateBitmapFromHBITMAP(factory, bitmap, NULL,
WICBitmapIgnoreAlpha,
&wic_bitmap);
// (4) Create an IWICStream instance, and attach it to a filename.
IWICStream* stream = NULL;
if (SUCCEEDED(hr))
hr = IWICImagingFactory_CreateStream(factory, &stream);
if (SUCCEEDED(hr))
hr = IWICStream_InitializeFromFilename(stream, pathname, GENERIC_WRITE);
// (5) Create an IWICBitmapEncoder instance, and associate it with the stream.
IWICBitmapEncoder* encoder = NULL;
if (SUCCEEDED(hr))
hr = IWICImagingFactory_CreateEncoder(factory, &GUID_ContainerFormatBmp, NULL,
&encoder);
if (SUCCEEDED(hr))
hr = IWICBitmapEncoder_Initialize(encoder, (IStream*)stream,
WICBitmapEncoderNoCache);
// (6) Create an IWICBitmapFrameEncode instance, and initialize it
// in compliance with the source HBITMAP.
IWICBitmapFrameEncode* frame = NULL;
if (SUCCEEDED(hr))
hr = IWICBitmapEncoder_CreateNewFrame(encoder, &frame, NULL);
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_Initialize(frame, NULL);
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_SetSize(frame, bm_info.bmWidth, bm_info.bmHeight);
if (SUCCEEDED(hr)) {
GUID pixel_format = GUID_WICPixelFormat24bppBGR;
hr = IWICBitmapFrameEncode_SetPixelFormat(frame, &pixel_format);
}
// (7) Write bitmap data to the frame.
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_WriteSource(frame, (IWICBitmapSource*)wic_bitmap,
NULL);
// (8) Commit frame and data to stream.
if (SUCCEEDED(hr))
hr = IWICBitmapFrameEncode_Commit(frame);
if (SUCCEEDED(hr))
hr = IWICBitmapEncoder_Commit(encoder);
// Cleanup
if (frame)
IWICBitmapFrameEncode_Release(frame);
if (encoder)
IWICBitmapEncoder_Release(encoder);
if (stream)
IWICStream_Release(stream);
if (wic_bitmap)
IWICBitmap_Release(wic_bitmap);
if (factory)
IWICImagingFactory_Release(factory);
return hr;
}
Here's a companion test application to showcase the usage. Make sure to #define OEMRESOURCE prior to including any system headers to allow use of the OBM_ images.
int wmain(int argc, wchar_t** argv) {
HRESULT hr = S_OK;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr))
return -1;
HBITMAP bitmap = LoadImage(NULL, MAKEINTRESOURCE(OBM_ZOOM), IMAGE_BITMAP, 0, 0,
LR_DEFAULTCOLOR);
hr = WriteBitmap(bitmap, argv[1]);
// Cleanup
if (bitmap)
DeleteObject(bitmap);
CoUninitialize();
return 0;
}
This will load a system-provided bitmap, and save it to the pathname specified as an argument on the command line.
Limitations:
No support for alpha channels. Although bitmaps version 5 support alpha channels, I am not aware of any way to find out, whether an HBITMAP refers to a bitmap with an alpha channel, nor would I know, how to determine, whether it is pre-multiplied. If you do want to support an alpha channel, make sure to set the EnableV5Header32bppBGRA property to VARIANT_TRUE (see BMP Format: Encoding).
No support for palletized bitmaps (bpp <= 8). If you are dealing with palletized bitmaps, make sure to supply an appropriate HPALETTE in the call to IWICImagingFactory::CreateBitmapFromHBITMAP.
The encoder is initialized with the GUID_WICPixelFormat24bppBGR pixel format constant. A more versatile implementation would deduce a compatible pixel format from the source HBITMAP.

Related

Change image in GTK window on function call

I am writing a C program to display raw images using GTK. Now that I can display an image, I'd like to increase the functionality to display multiple images back-to-back in the same window (like a media player). This program is to be used in conjunction with an image decoder that updates the image buffers as more images are decoded, and GTK will follow update the window to let us know we have decoded the images correctly.
Since gtk_main() is the last function to be called, I have opened a separate thread to setup the window and keep it running. The next step is to run a function that loads the images and changes them in the GTK window, but I am having trouble getting this to work. Here is my code to setup the window:
gtk_init(NULL, NULL);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW (window), "Image Player");
g_signal_connect(window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
gtk_widget_show_all(window);
gtk_main();
This will run in it's own thread. Now, I want to run this block of code to change and display the image, which is working for a single image when I run them together in one function, but I'd like to simply call a function called changeImage() which reassigns the image variable through the updated pixbuf and automatically updates it in the window.
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data ((guchar*)buf_rgb, GDK_COLORSPACE_RGB,
FALSE, 8, width, height, stride_rgb*3, NULL, NULL);
image = gtk_image_new_from_pixbuf (pixbuf);
gtk_container_add(GTK_CONTAINER (window), image);
gtk_widget_show_all(window);
How would I achieve this in GTK? I have just started learning how to use this tool last week, I believe I'm supposed to create an event using g_signal_connect, but I'm not sure how to tie it to a function call like changeImage() where I can simply update the image or call the function and the image in the display window will change.
EDIT: Test script included below:
#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>
FILE *x1, *y_1, *z1;
FILE *x2, *y2, *z2;
// image needs to be converted from greyscale to RGB
// using this structure works with GTK
typedef struct{
uint8_t r;
uint8_t g;
uint8_t b;
}rgb;
GtkWidget *window;
GtkWidget *image;
GdkPixbuf *pixbuf;
GtkWidget *button;
GtkWidget *hbox;
// Function to display image 8-bits
// This is the function I was using to display 1 image per window
void displayImage8_RGB(uint8_t *x, uint8_t *y, uint8_t *z, const int width, const int height){
size_t size = width * height;
size_t stride_16 = width;
size_t stride_rgb = width;
rgb *buf_rgb = (rgb*)malloc(stride_rgb*height*sizeof(rgb));
for(size_t i = 0; i < width; i++){
for(size_t j = 0; j < height; j++){
//printf("%d\t%d\n", x, y);
float y_ = x[stride_16*j+i];
float cb = y[stride_16*j+i];
float cr = z[stride_16*j+i];
cr = cr - 128;
cb = cb - 128;
y_ = (y_ * 219) + 16;
cb = (cb * 244) + 128;
cr = (cr * 224) + 128;
uint16_t r_ = (y_ + 1.402 * cr) / 255;
uint16_t g_ = (y_ - 0.344136*cb - 0.714136*cr) / 255;
uint16_t b_ = (y_ + 1.772*cb) / 255;
if (r_ > 255){
r_ = (uint8_t) 0;
}
if (g_ > 255){
g_ = (uint8_t) 0;
}
if (b_ > 255){
b_ = (uint8_t) 0;
}
buf_rgb[stride_rgb*j+i].r = r_;
buf_rgb[stride_rgb*j+i].g = g_;
buf_rgb[stride_rgb*j+i].b = b_;
//printf("x: %d\ty: %d\tz: %d\nr: %d\tg: %d\tb: %d\n", x_c, y_c, z_c, buf_rgb[stride_rgb*j+i].r, buf_rgb[stride_rgb*j+i].g, buf_rgb[stride_rgb*j+i].b);
}
}
GtkWidget* image;
gtk_init (NULL, NULL);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data ((guchar*)buf_rgb, GDK_COLORSPACE_RGB,
FALSE, 8, width, height, stride_rgb*3, NULL, NULL);
gtk_window_set_title (GTK_WINDOW (window), "Image Viewer");
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
image = gtk_image_new_from_pixbuf (pixbuf);
gtk_container_add(GTK_CONTAINER (window), image);
gtk_widget_show_all (window);
gtk_main ();
free(buf_rgb);
return;
}
void changeImage(uint8_t *x, uint8_t *y, uint8_t *z, const int width, const int height){
printf("displaying image\n");
size_t size = width * height;
size_t stride_16 = width;
size_t stride_rgb = width;
rgb *buf_rgb = (rgb*)malloc(stride_rgb*height*sizeof(rgb));
for(size_t i = 0; i < width; i++){
for(size_t j = 0; j < height; j++){
//printf("%d\t%d\n", x, y);
float y_ = x[stride_16*j+i];
float cb = y[stride_16*j+i];
float cr = z[stride_16*j+i];
cr = cr - 128;
cb = cb - 128;
y_ = (y_ * 219) + 16;
cb = (cb * 244) + 128;
cr = (cr * 224) + 128;
uint16_t r_ = (y_ + 1.402 * cr) / 255;
uint16_t g_ = (y_ - 0.344136*cb - 0.714136*cr) / 255;
uint16_t b_ = (y_ + 1.772*cb) / 255;
if (r_ > 255){
r_ = (uint8_t) 0;
}
if (g_ > 255){
g_ = (uint8_t) 0;
}
if (b_ > 255){
b_ = (uint8_t) 0;
}
buf_rgb[stride_rgb*j+i].r = r_;
buf_rgb[stride_rgb*j+i].g = g_;
buf_rgb[stride_rgb*j+i].b = b_;
//printf("x: %d\ty: %d\tz: %d\nr: %d\tg: %d\tb: %d\n", x_c, y_c, z_c, buf_rgb[stride_rgb*j+i].r, buf_rgb[stride_rgb*j+i].g, buf_rgb[stride_rgb*j+i].b);
}
}
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data ((guchar*)buf_rgb, GDK_COLORSPACE_RGB,
FALSE, 8, width, height, stride_rgb*3, NULL, NULL);
gtk_image_set_from_pixbuf(GTK_IMAGE(image), pixbuf);
//gtk_container_add(GTK_CONTAINER (window), image);
//gtk_widget_show_all(window);
g_object_unref(pixbuf);
free(buf_rgb);
return;
}
void* setupWindow(){
gtk_init(NULL, NULL);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW (window), "Image Player");
g_signal_connect(window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
rgb *buf_rgb = (rgb*)malloc(1998*1080*sizeof(rgb));
memset(buf_rgb, 255, (1998*1080*3));
GdkPixbuf *s_pixbuf = gdk_pixbuf_new_from_data((guchar*)buf_rgb, GDK_COLORSPACE_RGB,
FALSE, 8, 1998, 1080, 1998*3, NULL, NULL);
image = gtk_image_new_from_pixbuf(s_pixbuf);
gtk_container_add(GTK_CONTAINER (window), image);
gtk_widget_show_all(window);
gtk_main();
}
int main(int argc, char *argv[]){
// for demo, pass files in this order:
// idwt_bin0_test1.raw idwt_bin1_test1.raw idwt_bin2_test1.raw idwt_bin0_test2.raw idwt_bin1_test2.raw idwt_bin2_test2.raw
// files passed to function are 3 components (.raw) for 2 uncompressed images
pthread_t win, changer;
x1 = fopen("mtest/idwt_bin0_test1.raw", "rb");
y_1 = fopen("mtest/idwt_bin1_test1.raw", "rb");
z1 = fopen("mtest/idwt_bin2_test1.raw", "rb");
x2 = fopen("mtest/idwt_bin0_test2.raw", "rb");
y2 = fopen("mtest/idwt_bin1_test2.raw", "rb");
z2 = fopen("mtest/idwt_bin2_test2.raw", "rb");
// test1 1998x1080
// busy4k 4096x2160
const int w = 1998, h = 1080;
int size = w * h; // multiply by 2 for 16-bit
uint8_t *xbuf1 = (uint8_t*)malloc(sizeof(uint8_t)*size);
uint8_t *ybuf1 = (uint8_t*)malloc(sizeof(uint8_t)*size);
uint8_t *zbuf1 = (uint8_t*)malloc(sizeof(uint8_t)*size);
uint8_t *xbuf2 = (uint8_t*)malloc(sizeof(uint8_t)*size);
uint8_t *ybuf2 = (uint8_t*)malloc(sizeof(uint8_t)*size);
uint8_t *zbuf2 = (uint8_t*)malloc(sizeof(uint8_t)*size);
// comp0 comp2 comp1
fread(xbuf1, 1, size, x1);
fread(ybuf1, 1, size, y_1);
fread(zbuf1, 1, size, z1);
fread(xbuf2, 1, size, x2);
fread(ybuf2, 1, size, y2);
fread(zbuf2, 1, size, z2);
// thread to setup main gtk window
pthread_create(&win, NULL, setupWindow, NULL);
changeImage(xbuf1, ybuf1, zbuf1, w, h); // change to image 1
sleep(3); // gtk's timer could be used instead
changeImage(xbuf2, ybuf2, zbuf2, w, h); // change to image 2
sleep(3);
fclose(x1);
fclose(x2);
fclose(y_1);
fclose(y2);
fclose(z1);
fclose(z2);
return 0;
}```

What's going on in my custom list view control that leads to rows either partially or fully not being redrawn when scrolling?

I'm building a custom listview control as the standard comctl32.dll one does not adequately fit my needs. Things seem to be going well for what I have so far, except for scrolling.
At first, things look like this (under wine, but real Windows is affected too);
The form of corruption changes depending on the type of scrolling used. If I scroll using the mouse wheel, every few rows is corrupted:
And if I scroll by dragging the thumb, this happens:
In both cases, clicking somewhere will redraw everything, and the corruption goes away.
I'm not really sure what I'm doing wrong here.
Here's the code; I will explain how it works below:
// 19 october 2014
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
#include <uxtheme.h>
#include <string.h>
#include <wchar.h>
#include <windowsx.h>
#include <vsstyle.h>
#include <vssym32.h>
// #qo LIBS: user32 kernel32 gdi32 comctl32
// TODO
// - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/09/54826.aspx (relies on the integrality parts? IDK)
// - might want to http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54944.aspx instead
// - http://msdn.microsoft.com/en-us/library/windows/desktop/bb775574%28v=vs.85%29.aspx
// - hscroll (harder)
// - keyboard navigation
// - mousewheel navigation
#define tableWindowClass L"gouitable"
struct table {
HWND hwnd;
HFONT defaultFont;
HFONT font;
intptr_t selected;
intptr_t count;
intptr_t firstVisible;
intptr_t pagesize; // in rows
int wheelCarry;
HWND header;
int headerHeight;
intptr_t nColumns;
HIMAGELIST imagelist;
int imagelistHeight;
};
static LONG rowHeight(struct table *t)
{
HFONT thisfont, prevfont;
TEXTMETRICW tm;
HDC dc;
LONG ret;
dc = GetDC(t->hwnd);
if (dc == NULL)
abort();
thisfont = t->font; // in case WM_SETFONT happens before we return
prevfont = (HFONT) SelectObject(dc, thisfont);
if (prevfont == NULL)
abort();
if (GetTextMetricsW(dc, &tm) == 0)
abort();
if (SelectObject(dc, prevfont) != (HGDIOBJ) (thisfont))
abort();
if (ReleaseDC(t->hwnd, dc) == 0)
abort();
ret = tm.tmHeight;
if (ret < t->imagelistHeight)
ret = t->imagelistHeight;
return ret;
}
static void redrawAll(struct table *t)
{
if (InvalidateRect(t->hwnd, NULL, TRUE) == 0)
abort();
if (UpdateWindow(t->hwnd) == 0)
abort();
}
static RECT realClientRect(struct table *t)
{
RECT r;
if (GetClientRect(t->hwnd, &r) == 0)
abort();
r.top += t->headerHeight;
return r;
}
static void recomputeHScroll(struct table *t)
{
HDITEMW item;
intptr_t i;
int width = 0;
RECT r;
SCROLLINFO si;
// TODO count dividers
for (i = 0; i < t->nColumns; i++) {
ZeroMemory(&item, sizeof (HDITEMW));
item.mask = HDI_WIDTH;
if (SendMessageW(t->header, HDM_GETITEM, (WPARAM) i, (LPARAM) (&item)) == FALSE)
abort();
width += item.cxy;
}
if (GetClientRect(t->hwnd, &r) == 0)
abort();
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_PAGE | SIF_RANGE;
si.nPage = r.right - r.left;
si.nMin = 0;
si.nMax = width - 1; // - 1 because endpoints inclusive
SetScrollInfo(t->hwnd, SB_HORZ, &si, TRUE);
}
static void finishSelect(struct table *t)
{
if (t->selected < 0)
t->selected = 0;
if (t->selected >= t->count)
t->selected = t->count - 1;
// TODO update only the old and new selected items
redrawAll(t);
// TODO scroll to the selected item if it's not entirely visible
}
static void keySelect(struct table *t, WPARAM wParam, LPARAM lParam)
{
// TODO figure out correct behavior with nothing selected
if (t->count == 0) // don't try to do anything if there's nothing to do
return;
switch (wParam) {
case VK_UP:
t->selected--;
break;
case VK_DOWN:
t->selected++;
break;
case VK_PRIOR:
t->selected -= t->pagesize;
break;
case VK_NEXT:
t->selected += t->pagesize;
break;
case VK_HOME:
t->selected = 0;
break;
case VK_END:
t->selected = t->count - 1;
break;
default:
// don't touch anything
return;
}
finishSelect(t);
}
static void selectItem(struct table *t, WPARAM wParam, LPARAM lParam)
{
int x, y;
LONG h;
x = GET_X_LPARAM(lParam);
y = GET_Y_LPARAM(lParam);
h = rowHeight(t);
y += t->firstVisible * h;
y -= t->headerHeight;
y /= h;
t->selected = y;
if (t->selected >= t->count)
t->selected = -1;
finishSelect(t);
}
// TODO on initial show the items are not arranged properly
// TODO the lowest visible row does not redraw properly after scrolling
// TODO the row behind the header bar does not redraw properly after scrolling
static void vscrollto(struct table *t, intptr_t newpos)
{
SCROLLINFO si;
RECT scrollArea;
if (newpos < 0)
newpos = 0;
if (newpos > (t->count - t->pagesize))
newpos = (t->count - t->pagesize);
scrollArea = realClientRect(t);
// negative because ScrollWindowEx() is "backwards"
if (ScrollWindowEx(t->hwnd, 0, (-(newpos - t->firstVisible)) * rowHeight(t),
&scrollArea, &scrollArea, NULL, NULL,
SW_ERASE | SW_INVALIDATE) == ERROR)
abort();
t->firstVisible = newpos;
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
si.nPage = t->pagesize;
si.nMin = 0;
si.nMax = t->count - 1; // nMax is inclusive
si.nPos = t->firstVisible;
SetScrollInfo(t->hwnd, SB_VERT, &si, TRUE);
}
static void vscrollby(struct table *t, intptr_t n)
{
vscrollto(t, t->firstVisible + n);
}
static void wheelscroll(struct table *t, WPARAM wParam)
{
int delta;
int lines;
UINT scrollAmount;
delta = GET_WHEEL_DELTA_WPARAM(wParam);
if (SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollAmount, 0) == 0)
abort();
if (scrollAmount == WHEEL_PAGESCROLL)
scrollAmount = t->pagesize;
if (scrollAmount == 0) // no mouse wheel scrolling (or t->pagesize == 0)
return;
// the rest of this is basically http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx
// see those pages for information on subtleties
delta += t->wheelCarry;
lines = delta * ((int) scrollAmount) / WHEEL_DELTA;
t->wheelCarry = delta - lines * WHEEL_DELTA / ((int) scrollAmount);
vscrollby(t, -lines);
}
static void vscroll(struct table *t, WPARAM wParam)
{
SCROLLINFO si;
intptr_t newpos;
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_POS | SIF_TRACKPOS;
if (GetScrollInfo(t->hwnd, SB_VERT, &si) == 0)
abort();
newpos = t->firstVisible;
switch (LOWORD(wParam)) {
case SB_TOP:
newpos = 0;
break;
case SB_BOTTOM:
newpos = t->count - t->pagesize;
break;
case SB_LINEUP:
newpos--;
break;
case SB_LINEDOWN:
newpos++;
break;
case SB_PAGEUP:
newpos -= t->pagesize;
break;
case SB_PAGEDOWN:
newpos += t->pagesize;
break;
case SB_THUMBPOSITION:
newpos = (intptr_t) (si.nPos);
break;
case SB_THUMBTRACK:
newpos = (intptr_t) (si.nTrackPos);
}
vscrollto(t, newpos);
}
static void resize(struct table *t)
{
RECT r;
SCROLLINFO si;
HDLAYOUT headerlayout;
WINDOWPOS headerpos;
// do this first so our scrollbar calculations can be correct
if (GetClientRect(t->hwnd, &r) == 0) // use the whole client rect
abort();
headerlayout.prc = &r;
headerlayout.pwpos = &headerpos;
if (SendMessageW(t->header, HDM_LAYOUT, 0, (LPARAM) (&headerlayout)) == FALSE)
abort();
if (SetWindowPos(t->header, headerpos.hwndInsertAfter, headerpos.x, headerpos.y, headerpos.cx, headerpos.cy, headerpos.flags | SWP_SHOWWINDOW) == 0)
abort();
t->headerHeight = headerpos.cy;
// now adjust the scrollbars
r = realClientRect(t);
t->pagesize = (r.bottom - r.top) / rowHeight(t);
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = t->count - 1;
si.nPage = t->pagesize;
SetScrollInfo(t->hwnd, SB_VERT, &si, TRUE);
recomputeHScroll(t);
}
static void drawItems(struct table *t, HDC dc, RECT cliprect)
{
HFONT thisfont, prevfont;
LONG height;
LONG y;
intptr_t i;
RECT controlSize; // for filling the entire selected row
intptr_t first, last;
POINT prevOrigin, prevViewportOrigin;
if (GetClientRect(t->hwnd, &controlSize) == 0)
abort();
height = rowHeight(t);
thisfont = t->font; // in case WM_SETFONT happens before we return
prevfont = (HFONT) SelectObject(dc, thisfont);
if (prevfont == NULL)
abort();
// adjust the clip rect and the window so that (0, 0) is always the first item
// adjust the viewport so that everything is shifted down t->headerHeight pixels
if (OffsetRect(&cliprect, 0, t->firstVisible * height) == 0)
abort();
if (GetWindowOrgEx(dc, &prevOrigin) == 0)
abort();
if (SetWindowOrgEx(dc, prevOrigin.x, prevOrigin.y + (t->firstVisible * height), NULL) == 0)
abort();
if (SetViewportOrgEx(dc, 0, t->headerHeight, &prevViewportOrigin) == 0)
abort();
// see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/29/54591.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/07/30/54600.aspx
first = cliprect.top / height;
if (first < 0)
first = 0;
last = (cliprect.bottom + height - 1) / height;
if (last >= t->count)
last = t->count;
y = first * height;
for (i = first; i < last; i++) {
RECT rsel;
HBRUSH background;
int textColor;
WCHAR msg[100];
RECT headeritem;
intptr_t j;
LRESULT xoff;
// TODO verify these two
background = (HBRUSH) (COLOR_WINDOW + 1);
textColor = COLOR_WINDOWTEXT;
if (t->selected == i) {
// these are the colors wine uses (http://source.winehq.org/source/dlls/comctl32/listview.c)
// the two for unfocused are also suggested by http://stackoverflow.com/questions/10428710/windows-forms-inactive-highlight-color
background = (HBRUSH) (COLOR_HIGHLIGHT + 1);
textColor = COLOR_HIGHLIGHTTEXT;
if (GetFocus() != t->hwnd) {
background = (HBRUSH) (COLOR_BTNFACE + 1);
textColor = COLOR_BTNTEXT;
}
}
// first fill the selection rect
rsel.left = controlSize.left;
rsel.top = y;
rsel.right = controlSize.right - controlSize.left;
rsel.bottom = y + height;
if (FillRect(dc, &rsel, background) == 0)
abort();
xoff = SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0);
// now draw the cells
if (SetTextColor(dc, GetSysColor(textColor)) == CLR_INVALID)
abort();
if (SetBkMode(dc, TRANSPARENT) == 0)
abort();
for (j = 0; j < t->nColumns; j++) {
if (SendMessageW(t->header, HDM_GETITEMRECT, (WPARAM) j, (LPARAM) (&headeritem)) == 0)
abort();
if (j == 1) { // TODO
IMAGELISTDRAWPARAMS ip;
ZeroMemory(&ip, sizeof (IMAGELISTDRAWPARAMS));
ip.cbSize = sizeof (IMAGELISTDRAWPARAMS);
ip.himl = t->imagelist;
ip.i = 0;
ip.hdcDst = dc;
ip.x = headeritem.left + xoff;
ip.y = y;
ip.cx = 0; // draw whole image
ip.cy = 0;
ip.xBitmap = 0;
ip.yBitmap = 0;
ip.rgbBk = CLR_NONE;
ip.fStyle = ILD_NORMAL | ILD_SCALE; // TODO alpha-blend; ILD_DPISCALE?
// TODO ILS_ALPHA?
if (ImageList_DrawIndirect(&ip) == 0)
abort();
continue;
}
rsel.left = headeritem.left + xoff;
rsel.top = y;
rsel.right = headeritem.right;
rsel.bottom = y + height;
// TODO vertical center in case the height is less than the icon height?
if (DrawTextExW(dc, msg, wsprintf(msg, L"Item %d", i), &rsel, DT_END_ELLIPSIS | DT_LEFT | DT_NOPREFIX | DT_SINGLELINE, NULL) == 0)
abort();
}
y += height;
}
// reset everything
if (SetViewportOrgEx(dc, prevViewportOrigin.x, prevViewportOrigin.y, NULL) == 0)
abort();
if (SetWindowOrgEx(dc, prevOrigin.x, prevOrigin.y, NULL) == 0)
abort();
if (SelectObject(dc, prevfont) != (HGDIOBJ) (thisfont))
abort();
}
static LRESULT CALLBACK tableWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
struct table *t;
HDC dc;
PAINTSTRUCT ps;
NMHDR *nmhdr = (NMHDR *) lParam;
NMHEADERW *nm = (NMHEADERW *) lParam;
t = (struct table *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if (t == NULL) {
// we have to do things this way because creating the header control will fail mysteriously if we create it first thing
// (which is fine; we can get the parent hInstance this way too)
if (uMsg == WM_NCCREATE) {
CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;
t = (struct table *) malloc(sizeof (struct table));
if (t == NULL)
abort();
ZeroMemory(t, sizeof (struct table));
t->hwnd = hwnd;
// TODO this should be a global
t->defaultFont = (HFONT) GetStockObject(SYSTEM_FONT);
if (t->defaultFont == NULL)
abort();
t->font = t->defaultFont;
t->selected = 5;t->count=100;//TODO
t->header = CreateWindowExW(0,
WC_HEADERW, L"",
// TODO is HOTTRACK needed?
WS_CHILD | HDS_FULLDRAG | HDS_HORZ | HDS_HOTTRACK,
0, 0, 0, 0,
t->hwnd, (HMENU) 100, cs->hInstance, NULL);
if (t->header == NULL)
abort();
{HDITEMW item;
ZeroMemory(&item, sizeof (HDITEMW));
item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT;
item.cxy = 200;
item.pszText = L"Column";
item.fmt = HDF_LEFT | HDF_STRING;
if (SendMessage(t->header, HDM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1))
abort();
ZeroMemory(&item, sizeof (HDITEMW));
item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT;
item.cxy = 150;
item.pszText = L"Column 2";
item.fmt = HDF_LEFT | HDF_STRING;
if (SendMessage(t->header, HDM_INSERTITEM, 1, (LPARAM) (&item)) == (LRESULT) (-1))
abort();
t->nColumns=2;
t->imagelist = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32, 1, 1);
if(t->imagelist==NULL)abort();
{
HICON icon;
int unused;
icon = LoadIconW(NULL, IDI_ERROR);
if(icon == NULL)abort();
if (ImageList_AddIcon(t->imagelist, icon) == -1)abort();
if (ImageList_GetIconSize(t->imagelist, &unused, &(t->imagelistHeight)) == 0)abort();
}
}
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) t);
}
// even if we did the above, fall through
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_PAINT:
dc = BeginPaint(hwnd, &ps);
if (dc == NULL)
abort();
drawItems(t, dc, ps.rcPaint);
EndPaint(hwnd, &ps);
return 0;
case WM_SETFONT:
t->font = (HFONT) wParam;
if (t->font == NULL)
t->font = t->defaultFont;
// also set the header font
SendMessageW(t->header, WM_SETFONT, wParam, lParam);
if (LOWORD(lParam) != FALSE) {
// the scrollbar page size will change so redraw that too
// also recalculate the header height
// TODO do that when this is FALSE too somehow
resize(t);
redrawAll(t);
}
return 0;
case WM_GETFONT:
return (LRESULT) t->font;
case WM_VSCROLL:
vscroll(t, wParam);
return 0;
case WM_MOUSEWHEEL:
wheelscroll(t, wParam);
return 0;
case WM_SIZE:
resize(t);
return 0;
case WM_LBUTTONDOWN:
selectItem(t, wParam, lParam);
return 0;
case WM_SETFOCUS:
case WM_KILLFOCUS:
// all we need to do here is redraw the highlight
// TODO localize to just the selected item
// TODO ensure giving focus works right
redrawAll(t);
return 0;
case WM_KEYDOWN:
keySelect(t, wParam, lParam);
return 0;
// TODO header double-click
case WM_NOTIFY:
if (nmhdr->hwndFrom == t->header)
switch (nmhdr->code) {
// I could use HDN_TRACK but wine doesn't emit that
case HDN_ITEMCHANGING:
case HDN_ITEMCHANGED: // TODO needed?
recomputeHScroll(t);
redrawAll(t);
return FALSE;
}
// otherwise fall through
default:
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
abort();
return 0; // unreached
}
void makeTableWindowClass(void)
{
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = tableWindowClass;
wc.lpfnWndProc = tableWndProc;
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // TODO correct?
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hInstance = GetModuleHandle(NULL);
if (RegisterClassW(&wc) == 0)
abort();
}
int main(void)
{
HWND mainwin;
MSG msg;
INITCOMMONCONTROLSEX icc;
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
icc.dwICC = ICC_LISTVIEW_CLASSES;
if (InitCommonControlsEx(&icc) == 0)
abort();
makeTableWindowClass();
mainwin = CreateWindowExW(0,
tableWindowClass, L"Main Window",
WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 400,
NULL, NULL, GetModuleHandle(NULL), NULL);
if (mainwin == NULL)
abort();
ShowWindow(mainwin, SW_SHOWDEFAULT);
if (UpdateWindow(mainwin) == 0)
abort();
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
t->firstVisible is the first visible row; t->pagesize is the number of rows per vscroll page, and t->headerHeight is the height of the header control in pixels. t->wheelcarry is the mouse wheel carryover, from this.
The actual height of a row is calculated by the rowHeight() function, which vscrollto(), the routine that actually calls ScrolLWindow(), multiplies the new t->firstVisible by to figure out how many pixels to scroll.
The realClientRect() logic in vscrollto() is to make sure that only the content area gets scrolled, and not the header control.
For drawing, I change the window origin to make (0, 0) be the first item (row 0) no matter what and then I change the viewport origin to account for the header. Readers of MSDN will note that this sounds strange (the documentation for both SetWindowOrgEx() and SetViewportOrgEx() say you generally don't use both), but my attempts at accounting for the header height manually have proven disastrous.
Again, this happens on both real Windows (did this earlier today on Windows 7) and wine.
What did I do wrong? Thanks.
I've had a play around with your code and I think the problem is definitely caused by your modifying the viewport and window origins. The issue is that adjusting the origins like that changes where new drawing will appear in the window, but it doesn't cause the update region to be shifted as well.
Therefore when there's only a partially invalidated area, as you get when scrolling, some of your drawing is clipped out by the update region.
It's actually not specific to scrolling - you can also see the same effect by dragging your window off the bottom of the screen, and then slowly dragging it back into view. The window will end up completely blank. If you comment out your call to SetViewportOrgEx the problem goes away completely.
You could probably solve this by adjusting the clipping region as well - you'd have to get the current update region, shift it by the same offset, and reselect it into the DC. Alternatively, you could redesign your painting code to take the height of the header into account when painting rather than shifting the viewport offset. You mentioned you had other issues when doing this - I suspect it will be easier to solve those than you think (i.e. it seems like a bit of an XY problem).

Create a bitmap in memory and use it

My goal is to create a bitmap in memory and use its handle as a parameter of the BM_SETIMAGE message (a message that set a button's bitmap).
The following is my code:
........
HDC hdc = GetDC(hwnd);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP hMemBmp = CreateCompatibleBitmap(hdc, 100, 100);
HBITMAP hOldBmp = (HBITMAP)SelectObject(memDC, hMemBmp);
Rectangle(memDC, 0, 0, 100, 100);
HBRUSH brush = CreateSolidBrush(RGB(0xff, 0xff, 0x00));
RECT rc;
rc.left = 0;
rc.top = 0;
rc.right = 100;
rc.bottom = 100;
FillRect(memDC, &rc, brush);
SendMessage(GetDlgItem(hDlg, IDC_SET_START_PAGE_BG), BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hMemBmp);
........
but it doesn't work, I just got black color on my button.
Questions:
Does FillRect(memDC, &rc, brush) modify the content of hMemBmp? If not, what SelectObject(memDC, hMemBmp) did for these 2 objects?
Should I use CreateBitmap instead of CreateCompatibleBitmap to do my task?
You should select the old bitmap back into the memory DC before you send the message. According to MSDN a bitmap can be selected into only a single DC at a time.

save file without save dialog

I have 3 global variables:
OPENFILENAME ofn;
TCHAR FileName[1024];
TCHAR Title[1024];
In WM_CREATE, I fill the information for ofn:
static TCHAR filter[] = TEXT("Bitmap Files (*.BMP)\0*.bmp\0") \
TEXT("All Files (*.*)\0*.*\0\0");
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.hInstance = NULL;
ofn.lpstrFilter = filter;
ofn.lpstrCustomFilter = NULL;
ofn.nMaxCustFilter = 0;
ofn.nFilterIndex = 0;
ofn.lpstrFile = NULL;
ofn.nMaxFile = 1024;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 1024;
ofn.lpstrInitialDir = NULL;
ofn.lpstrTitle = NULL;
ofn.Flags = 0;
ofn.nFileOffset = 0;
ofn.nFileExtension = 0;
ofn.lpstrDefExt = TEXT("bmp");
ofn.lCustData = 0;
ofn.lpfnHook = NULL;
ofn.lpTemplateName = NULL;
In WM_COMMAND with case ID_FILE_SAVE:, I have
case ID_FILE_SAVE:
if (!SaveAsBmpDialog(hwnd, FileName, Title))
return 0;
SaveAsBmp(FileName);
return 0;
where SaveAsBmpDialog is
static bool SaveAsBmpDialog(HWND hwnd, TCHAR *fileName, TCHAR *title)
{
ofn.hwndOwner = hwnd;
ofn.lpstrFile = fileName;
ofn.lpstrFileTitle = title;
ofn.Flags = NULL;
return GetSaveFileName(&ofn);
}
It works fine to save HDC to a Bitmap file (which can be open).
But in WM_CHAR with wParam == 'p', I have
SaveAsBmp(FileName);
return 0;
I use this function when SaveAsBmpDialog is called at once before, so FileName is a full-path already.
But I get a Bitmap file, which can not be open. (Even if I delete the file first, it creates the file, but still cannot be open). The file size is as before, so I think WriteFile in SaveAsBmp works.
I don't understand why it doesn't work without GetSaveFileName(&ofn) in WM_CHAR.
What I want is to save HDC to a exist file without save dialog.
ps: In SaveAsBmp, I have
HANDLE hFile = CreateFile(FileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD dwBytesWritten;
//SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
WriteFile(hFile, &fileHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
WriteFile(hFile, &info.bmiHeader, sizeof(BITMAPINFOHEADER) + info.bmiHeader.biClrUsed * sizeof(RGBQUAD), &dwBytesWritten, NULL);
WriteFile(hFile, pPixels, (int) info.bmiHeader.biSizeImage, &dwBytesWritten, NULL);
CloseHandle(hFile);

hwnd to ppm issue

I have a function which save a hwnd into a ppm file.
This function is inspired by a msdn example.
Both the msdn sample and my function work but ... I have an issue ...
But first, here is the function.
int CaptureAnImage(HWND hWnd)
{
HDC hdcWindow;
HDC hdcMemDC = NULL;
HBITMAP hbmScreen = NULL;
RECT rc;
BITMAPINFOHEADER bi;
DWORD dwBmpSize;
HANDLE hDIB;
char *lpbitmap;
int w, h;
FILE *f;
// Retrieve the handle to a display device context for the client
// area of the window.
hdcWindow = GetDC(hWnd);
// Create a compatible DC which is used in a BitBlt from the window DC
hdcMemDC = CreateCompatibleDC(hdcWindow);
if(!hdcMemDC) {
MessageBox(hWnd, "CreateCompatibleDC has failed","Failed", MB_OK);
goto done;
}
// Get the client area for size calculation
GetClientRect(hWnd, &rc);
w = rc.right - rc.left;
h=rc.bottom-rc.top;
// Create a compatible bitmap from the Window DC
hbmScreen = CreateCompatibleBitmap(hdcWindow, w, h);
if(!hbmScreen) {
MessageBox(hWnd, "CreateCompatibleBitmap Failed","Failed", MB_OK);
goto done;
}
// Select the compatible bitmap into the compatible memory DC.
SelectObject(hdcMemDC,hbmScreen);
// Bit block transfer into our compatible memory DC.
if(!BitBlt(hdcMemDC,
0,0,
w, h,
hdcWindow,
0,0,
SRCCOPY)) {
MessageBox(hWnd, "BitBlt has failed", "Failed", MB_OK);
goto done;
}
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = w;
bi.biHeight = h;
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
dwBmpSize = w*bi.biBitCount*h;
// Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
// call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
// have greater overhead than HeapAlloc.
hDIB = GlobalAlloc(GHND,dwBmpSize);
lpbitmap = (char *)GlobalLock(hDIB);
// Gets the "bits" from the bitmap and copies them into a buffer
// which is pointed to by lpbitmap.
GetDIBits(hdcWindow, hbmScreen, 0,
(UINT)h,
lpbitmap,
(BITMAPINFO *)&bi, DIB_RGB_COLORS);
f = fopen("./test.ppm", "wb");
if (!f) {
fprintf(stderr, "cannot create ppm file\n");
goto done;
}
fprintf(f, "P6\n%d %d\n255\n", w, h);
fwrite((LPSTR)lpbitmap, dwBmpSize, 1, f);
fclose(f);
//Unlock and Free the DIB from the heap
GlobalUnlock(hDIB);
GlobalFree(hDIB);
//Clean up
done:
DeleteObject(hbmScreen);
DeleteObject(hdcMemDC);
ReleaseDC(hWnd,hdcWindow);
return 0;
}
So here is the resulting image:
http://imageshack.us/photo/my-images/853/test2ne.jpg/
As you can see, there is a problem in the width size. Maybe because of the border of the window ?
If in the code, I change "w = rc.right - rc.left;" into "w = rc.right - rc.left - 10;", it's better. But I don't understand why I have to put "-10" and ... some pixel are missing on the right of the picture (maybe 10 pixels ?)
http://imageshack.us/photo/my-images/207/test3jq.jpg
And the last question:
is there any way to ask to GetDIBits function to put my byte in the inverted order ?
I don't wand to do a pixel by pixel copy since it will cost some cpu time. (ok, you may say that since I'm saving this file to disk, then I should not be concerned by cpu time, but my goal is not to save this picture to the disk. I'm doing it for debug purpose only)
thanks in advance for any help
Your problem is that each row of image data in a DIB must be DWORD aligned (i.e. aligned on a multiple of 4 bytes).
dwBmpSize = w*bi.biBitCount*h;
This should actually be:
dwBmpSize = ((w*bi.biBitCount+3)&~3) *h;
You will then have to account for this when writing the PPM file.
Also, the image is upside down because by default DIBs are "bottom-up" (row 0 is at the bottom). To make it "top-down" set the biHeight field to a negative value.

Resources