NO

Author Topic: Simple menu with bitmap  (Read 22979 times)

Offline TimoVJL

  • Global Moderator
  • Member
  • *****
  • Posts: 2115
Simple menu with bitmap
« on: August 17, 2010, 07:11:15 PM »
Simple way to put bitmaps to menu.
Or is there simpler way to do that ?

Code: [Select]
...
hBitmap = LoadBitmap(GetModuleHandle(TEXT("comctl32")), MAKEINTRESOURCE(120));
...
hBMNew = GetBitmapByPos(hBitmap, 6);
...
SetMenuItemBitmaps(hMenuT, IDM_NEW, MF_BYCOMMAND, hBMNew, hBMNew);
...

Code: [Select]
HBITMAP GetBitmapByPos(HBITMAP hBMSrc, int nPos)
{
HDC hDCSrc, hDCDst;
BITMAP bm;
HBITMAP hBMDst = NULL;

if ((hDCSrc = CreateCompatibleDC(NULL)) != NULL) {
if ((hDCDst = CreateCompatibleDC(NULL)) != NULL) {
SelectObject(hDCSrc, hBMSrc);
GetObject(hBMSrc, sizeof(bm), &bm);
hBMDst = CreateBitmap(bm.bmHeight, bm.bmHeight, bm.bmPlanes, bm.bmBitsPixel, NULL);
if (hBMDst) {
GetObject(hBMDst, sizeof(bm), &bm);
HBITMAP hOldBmp = SelectObject(hDCDst, hBMDst);
BitBlt(hDCDst, 0, 0, bm.bmHeight, bm.bmHeight, hDCSrc, nPos*bm.bmHeight, 0, SRCCOPY);
SelectObject(hDCDst, hOldBmp);
}
DeleteDC(hDCDst);
}
DeleteDC(hDCSrc);
}
return hBMDst;
}

This version shows whole picture:
Code: [Select]
HBITMAP GetBitmapByPos4Menu(HBITMAP hBMSrc, int nPos)
{
HDC hDCSrc, hDCDst;
BITMAP bm;
HBITMAP hBMDst = NULL;
int cx, cy;

cx = GetSystemMetrics(SM_CXMENUCHECK);
cy = GetSystemMetrics(SM_CYMENUCHECK);
if ((hDCSrc = CreateCompatibleDC(NULL)) != NULL) {
if ((hDCDst = CreateCompatibleDC(NULL)) != NULL) {
SelectObject(hDCSrc, hBMSrc);
GetObject(hBMSrc, sizeof(bm), &bm);
hBMDst = CreateBitmap(cx, cy, bm.bmPlanes, bm.bmBitsPixel, NULL);
if (hBMDst) {
HBITMAP hOldBmp = SelectObject(hDCDst, hBMDst);
StretchBlt(hDCDst, 0, 0, cx, cy, hDCSrc, nPos*bm.bmHeight, 0, bm.bmHeight, bm.bmHeight, SRCCOPY);
SelectObject(hDCDst, hOldBmp);
GetObject(hBMDst, sizeof(bm), &bm);
}
DeleteDC(hDCDst);
}
DeleteDC(hDCSrc);
}
return hBMDst;
}
EDIT:
This function creates bitmap with selected background color:
(Example in WSDIFrameMenuBM.zip uses it)
Code: [Select]
HBITMAP CreateBitmapMasked(HBITMAP hBMSrc, COLORREF crTransparent, COLORREF crBackground)
{
HDC hDCMem, hDCMem2, hDCDst1;
BITMAP bm;
HBITMAP hBMDst1 = NULL;
HBITMAP hBMDst2 = NULL;
HBITMAP hBMMask = NULL;

GetObject(hBMSrc, sizeof(bm), &bm);
hDCMem = CreateCompatibleDC(NULL);
hDCMem2 = CreateCompatibleDC(NULL);
hBMDst1 = CreateBitmap(bm.bmWidth, bm.bmHeight, bm.bmPlanes, bm.bmBitsPixel, NULL); // new bitmap
hBMDst2 = CreateBitmap(bm.bmWidth, bm.bmHeight, bm.bmPlanes, bm.bmBitsPixel, NULL); // copy bitmap
hBMMask = CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, NULL); // mask bitmap
// make copy of source bitmap
SelectObject(hDCMem, hBMSrc); // source
SelectObject(hDCMem2, hBMDst2); // target
BitBlt(hDCMem2, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, SRCCOPY); //
DeleteDC(hDCMem); // free source
DeleteDC(hDCMem2);
// make masks
hDCMem = CreateCompatibleDC(NULL);
hDCMem2 = CreateCompatibleDC(NULL);
SelectObject(hDCMem, hBMDst2);
SelectObject(hDCMem2, hBMMask);
SetBkColor(hDCMem, crTransparent);

BitBlt(hDCMem2, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, SRCCOPY);
BitBlt(hDCMem, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem2, 0, 0, SRCINVERT);
DeleteDC(hDCMem);
DeleteDC(hDCMem2);

RECT rc;
rc.top = rc.left = 0;
rc.right = bm.bmWidth;
rc.bottom = bm.bmHeight;
hDCMem = CreateCompatibleDC(NULL);
hDCDst1 = CreateCompatibleDC(NULL);
SelectObject(hDCDst1, hBMDst1);
FillRect(hDCDst1, &rc, (HBRUSH)crBackground);

SelectObject(hDCMem, hBMMask);
BitBlt(hDCDst1, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, SRCAND);
SelectObject(hDCMem, hBMDst2);
BitBlt(hDCDst1, 0, 0, bm.bmWidth, bm.bmHeight, hDCMem, 0, 0, SRCPAINT);
DeleteDC(hDCMem);
DeleteDC(hDCDst1);

DeleteObject(hBMDst2);
DeleteObject(hBMMask);
return hBMDst1;
}
EDIT 20221014: fixed GetBitmapByPos4Menu()

Even simpler way with comctl32:
EDIT 20140114: WSDIMenuBM_OD1_2.zip with ownerdraw and ImageList
In WM_CREATE
Code: [Select]
...
himl = ImageList_LoadBitmap(GetModuleHandle(TEXT("comctl32")), MAKEINTRESOURCE(120), 16, 15, RGB(192,192,192));
InitMenuBitmaps(hwnd);
...
Code: [Select]
void InitMenuBitmaps(HWND hwnd)
{
MENUITEMINFO mii;
HMENU hMenu;
hMenu = GetSubMenu(GetMenu(hwnd), 0);
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_BITMAP | MIIM_DATA;
mii.hbmpItem = HBMMENU_CALLBACK;
mii.dwItemData = 6;
SetMenuItemInfo(hMenu, IDM_NEW, FALSE, &mii);
...
}
Code: [Select]
case WM_DRAWITEM:
return OnDrawItem(hwnd,(const DRAWITEMSTRUCT*)lParam),0;
Code: [Select]
void OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDrawItem)
{
if (lpDrawItem->CtlType == ODT_MENU) {
if (lpDrawItem->itemData) {
ImageList_Draw(himl, lpDrawItem->itemData, lpDrawItem->hDC, 2, 2, ILD_NORMAL );
}
}
return;
}
« Last Edit: January 15, 2014, 12:56:20 PM by timovjl »
May the source be with you

Offline Stefan Pendl

  • Global Moderator
  • Member
  • *****
  • Posts: 582
    • Homepage
Re: Simple menu with bitmap
« Reply #1 on: October 12, 2011, 11:08:38 PM »
Sorry to bump this old thread, but the information below is really useful if one likes to use this tip/trick.

Toolbar images are usually in the size of 16x16 pixels.

For Windows XP and below the menu image size is 13x13 pixels, so the created image will be of poor quality.
To overcome this you can create a separate image, which is already scaled.

For Vista and above the menu image size is 15x15 pixels, so the quality is much better.

Any white pixel of the menu image will get transparent, so you need to change the magenta background of the toolbar image into white.
I noticed that on Vista and above the white background is not turned into transparent, but I will look for a solution.

This poor-mans menu images are great, since owner-drawn menus are quite complex.

If anyone has already a solution for the problems mentioned above, it would be great to read them ;)
---
Stefan

Proud member of the UltraDefrag Development Team

zbaka

  • Guest
Re: Simple menu with bitmap
« Reply #2 on: October 13, 2011, 10:11:55 AM »
I did not used toolbars since at least 10 years.  :P I think, if i remember, that the "transparent" color is the top-left (0,0) or bottom-left pixel (depending on the bitmap orientation format). No matter what this color is. An app i made in 1998 with a toolbar works correctly under Windows 7. The "transparent" color of the toolbar bitmap is a light grey, and this is the first pixel (top-left).

For the menus, i can say a lot. Owner draw menus are not complex at all, you just need to handle WM_DRAWITEM & WM_MEASUREITEM. All the complex stuff is hidden far far away. With a few lines of code you can add bitmaps/icons of any size you want to each item, using GetTextExtentPoint32, DrawText, LoadImage & DrawIconEx, PatBlt or AlphaBlend for example. I made a lot of cool & fun stuff with completely owner draw menus & custom controls subclassing a few years ago, quickly reaching their limits. If you want a real complex task of the same kind, try Custom Draw (for example to paint an entire treeview control exactly like you want).

Finally, after playing with OD menus & controls, i made my own menu system and my own custom controls. I wrote a complete layered menu system with per-pixel alpha blending support in 1997, using pascal (delphi) & SSE (MMX) asm. The complete source code was about 180K, including functions to load/save/edit standard menus resources. The API was strictly matching the Win32 API, plus some additional functions of course. This was made to preserve compatibility with existing code / menu resources. I wanted to port it to C a few weeks ago, but i decided to write a new enhanced release from scratch, with no support for the MENU resources. This new release, wrote in a few days (but i'm still working on it), finally uses the GDI+ flat API & SSE2. Internally, the menu display is a 16 bytes aligned and oversized (if needed) 32Bit ARGB buffer, mapped to a GDI+  Bitmap object when needed with GdipCreateBitmapFromScan0(). This is quite simple. GDI+ uses my non-premultiplied buffer as is, so there is no need to allocate another buffer or a bitmap/DIBSection, or to premult before calling AlphaBlend() like with GDI.

The most complex task with menus is not the drawing, this is all the invisible stuff : compute the popup window size from the items list, display & track the popup menus & submenus at the right position on screen, automatically hide/close the submenus & menu when needed, handle a large set of items by scrolling, etc... This needs to perfectly know the windows messaging system.
Drawing items in an OD menu using the Win32 GDI API is very easy. You cannot imagine the source code behind a "simple" function like DrawText()...  :'(     
 
With my own menu system, i can make cool menus and i can do exactly what i want, without subclassing or any OD stuff. Each item is defined using a big struct, but i can define only what i want, without using mask flags, because a 0 value means "not defined" (= use default) for each field of the struct (also for colors, because 0 is a totally transparent color in an alpha blended world). Here is 2 screenshots (the 2nd is deformed, i don't know why)

The source code is now about 70K, including a lot of comments everywhere. I will publish it when finished, like some others C libraries I made (a very small, fast and easy to use WMI library for example, which can display queries results & supports WMI events).

Offline Stefan Pendl

  • Global Moderator
  • Member
  • *****
  • Posts: 582
    • Homepage
Re: Simple menu with bitmap
« Reply #3 on: October 13, 2011, 11:33:30 AM »
The menus look great ;)

The only drawback of your new library is the need for GDI+, which is not supported on NT4.0 as far as I know, which I have to support due to measures set by my project manger.

Looking forward seeing your menu system in action 8)
---
Stefan

Proud member of the UltraDefrag Development Team

Offline Stefan Pendl

  • Global Moderator
  • Member
  • *****
  • Posts: 582
    • Homepage
Re: Simple menu with bitmap
« Reply #4 on: October 14, 2011, 01:43:55 AM »
Attached find an image of the transparency issue for menus on Vista and above.
Would be nice, if someone could point me in the right direction to resolve this.
---
Stefan

Proud member of the UltraDefrag Development Team

CommonTater

  • Guest
Re: Simple menu with bitmap
« Reply #5 on: October 14, 2011, 02:14:20 AM »
You probably want to do something quite similar to this...

Code: [Select]
    tbb = ImageList_LoadImage(PgmInst,L"TBBITMAP",32,0,RGB(255,255,255),
                        IMAGE_BITMAP,LR_CREATEDIBSECTION | LR_LOADTRANSPARENT);

I believe there is a similar function for loading individual images from resources as well....

http://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx

The "transparency dot" is the top left dot of the first image (location 0,0).

The attached is an example on a toolbar...


« Last Edit: October 14, 2011, 02:22:56 AM by CommonTater »

Offline TimoVJL

  • Global Moderator
  • Member
  • *****
  • Posts: 2115
Re: Simple menu with bitmap
« Reply #6 on: October 14, 2011, 09:25:29 AM »
Quote
Attached find an image of the transparency issue for menus on Vista and above.
Would be nice, if someone could point me in the right direction to resolve this.
HBITMAP GetBitmapByPos4Menu(HBITMAP hBMSrc, int nPos) function fixed.
Just wrong size of bitmap.
Thank's for pointing to that error.
May the source be with you

Offline Stefan Pendl

  • Global Moderator
  • Member
  • *****
  • Posts: 582
    • Homepage
Re: Simple menu with bitmap
« Reply #7 on: October 14, 2011, 10:24:47 AM »
The attached is an example on a toolbar...

As the subject stats this is not about toolbars, it is about menus.
Toolbars are working without problems here, only the menus act a bit wired ;)
---
Stefan

Proud member of the UltraDefrag Development Team

CommonTater

  • Guest
Re: Simple menu with bitmap
« Reply #8 on: October 14, 2011, 10:36:58 AM »
The attached is an example on a toolbar...

As the subject stats this is not about toolbars, it is about menus.
Toolbars are working without problems here, only the menus act a bit wired ;)

I was trying  to point out (apparently with little success) that it might be the parameters when the bitmap is loaded that were causing the problem.  Loading a bitmap is the same for toolbars, menus, trees, etc.

Offline Stefan Pendl

  • Global Moderator
  • Member
  • *****
  • Posts: 582
    • Homepage
Re: Simple menu with bitmap
« Reply #9 on: October 14, 2011, 11:40:46 AM »
The MSDN article about menus tells us, that the menu image is anded with the background of the menu, so white turns into transparent.
This seems to work for XP and below, but for Vista and above it does not.

I will try the masking function and report back.
---
Stefan

Proud member of the UltraDefrag Development Team

zbaka

  • Guest
Re: Simple menu with bitmap
« Reply #10 on: October 15, 2011, 02:23:41 PM »
The "transparency dot" is the top left dot of the first image (location 0,0).
Yes, that's what i wrote. This has nothing to do with the color itself, the first pixel (0, 0) must be the "transparent" color (by convention) for the whole bitmap (it will be converted to the background color). There is no support for true transparency (alpha channel and color blending) under Windows and especially under GDI. All the GDI's stuff to handle "transparency" is color and bit masking (via PlgBlt, PatBlt, MaskBlt, TransparentBlt, and theirs friends). Even the AlphaBlend() function is not able to handle color blending properly. To handle such tasks without third party software, like Aero or the layered windows does, you need to use DirectX, DirectShow, or the GDI+ API for example. GDI is a completely dead API preserved only for backward compatibility and very basic tasks.

If you want to do what LoadImage(LR_TRANSPARENT) do, with more control (for example you can define a transparent color for all your bitmaps, no matter where are located these "transparent" pixels), here is an example (to understand how easy it is) :

Code: [Select]
// Load the bitmap (file or resource) as a DIB with LoadImage(..., LR_CREATEDIBSECTION)
// Call GetObject() with a BITMAP struct  to retrieve the DIB infos (no need to use a DIBSECTION struct)
// Get the color you need to replace your "transparent" color into your picture, for example GetSysColor(COLOR_MENU)
// You can use this sample for 24 & 32 bpp bitmaps, by changing the pBits pointer type (RGBQUAD* for 32BPP)
// This is a sample, written on the fly, to understand how easy it is to achieve such fake transparency effect. It may contains errors
// and of course, it is not optimized at all. And finally, if the display device is set to 8 or 16 bits, dithering will occurs & may
// change the bitmap appearance/"transparency". With the power of modern computers, this kind of old tricks are totally
// obsolete. Handling such small alpha blended bitmaps is so fast today even with non-optimized code that color & bit masking
// is completely useless.

BITMAP bmDib; // The BITMAP struct filled by GetObject(YourDibHandle, sizeof(BITMAP), &BITMAP)
RGBTRIPLE * pBits; // RGBQUAD * for 32BPP
COLORREF color;
int x, y;

for (y = 0; y < bmDib.bmHeight; y++)
{
   // Parse each scan line (row) of the bitmap
   pBits = (RGBTRIPLE*) ((BYTE*)bmDib.bmBits + (bmDib.bmWidthBytes * y));
   for (x = 0; x < bmDib.bmWidth; x++)
   {
        color = RGB(pBits[x].rgbtRed, pBits[x].rgbtGreen, pBits[x].rgbtBlue);
       // for 32BPP processing -> color = *(DWORD*) pBits ;  Then pBits++ on each run after the test or on the for
        if (color == MY_TRANSPARENT_COLOR)
        {
              // Change your fake "transparent" predefined color to match the background color where your image will be displayed
              pBits[x].rgbtRed = GetRValue(MyNewBackgroundColor);     
              pBits[x].rgbtGreen = GetGValue(MyNewBackgroundColor);     
              pBits[x].rgbtBlue = GetBValue(MyNewBackgroundColor);
        }
    }
}     
GdiFlush(); // should always be called after modifying a DIBSection directly
// Done ! This is exactly what LoadImage(LR_TRANSPARENT) do, with a GetPixel(0,0) for MY_TRANSPARENT_COLOR value
// Pass the dib handle to the API you need. You have a "transparent" bitmap ! ( I did not tried with SetMenuItemBitmaps()
// because this function needs black & white bitmaps (2 colors, 1BPP), not color ones.

Now the bad news : If the user changes the color scheme/resolution/color depth while your app is running, you may have to handle the message in your WndProc,  process each bitmap again and send them to the required API. This is why GDI+ was created, to handle images with true alpha channel, instead of such color masking or bitwise masking like icons.

To resize (resample) such a small bitmap, you cannot rely on the GDI API if you expect good results, because GDI is not able to do a filtered resampling (StretchBlt is not a resampler, it stretches & discards a lot of pixels). Linear (bi, tri) filtering is easy to implement and very short in size, it is fast and gives correct results with such small pictures, by smoothing (averaging) adjacent pixels. But after such processing, your "transparent" pixels will be blended among others pixels, and so you will loose your "transparency" on some areas. GDI+ is able to do such processing (linear or bicubic filtered resampling) without loosing transparency (alpha), while GDI is not.

But one of the notoriously known & most funny things about GDI+ (excepted the fact it is incredibly slow for some tasks), is that it is unable to properly handle true transparent XP icons (.ico) files (32BPP icons with alpha channel). GDI+ produces the same result as you describe when drawing an icon to a menu or anywhere, because it discards the mask bitmap and sets the alpha channel to 0xFF for the entire bitmap when it load an icon. As i know, only C# & VB (perhaps C++?) wrapper classes are implemented to handle the icons properly with GDI+, via additional code.


 
« Last Edit: October 15, 2011, 02:37:06 PM by zbaka »

Offline TimoVJL

  • Global Moderator
  • Member
  • *****
  • Posts: 2115
Re: Simple menu with bitmap
« Reply #11 on: October 15, 2011, 07:41:05 PM »
This example convert 'transparent' color to selected color.
Code: [Select]
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>

void WriteBMPFile(HBITMAP hBitmap, LPTSTR filename, HDC hDC);

HBITMAP CopyBitmap2(HBITMAP hbmSrc, COLORREF clrTP, COLORREF clrBK)
{
HDC hdcSrc, hdcDst;
HBITMAP hbmOld, hbmNew;
BITMAP bm;
if ((hdcSrc = CreateCompatibleDC(NULL)) != NULL) {
if ((hdcDst = CreateCompatibleDC(NULL)) != NULL) {
int nRow, nCol;
GetObject(hbmSrc, sizeof(bm), &bm);
hbmOld = SelectObject(hdcSrc, hbmSrc);
hbmNew = CreateBitmap(bm.bmWidth, bm.bmHeight, bm.bmPlanes, bm.bmBitsPixel, NULL);
SelectObject(hdcDst, hbmNew);
BitBlt(hdcDst, 0, 0, bm.bmWidth, bm.bmHeight, hdcSrc, 0, 0, SRCCOPY);
SelectObject(hdcSrc, hbmOld);
for (nRow = 0; nRow < bm.bmHeight; nRow++)
for (nCol = 0; nCol < bm.bmWidth; nCol++)
if (GetPixel(hdcDst, nCol, nRow) == clrTP)
SetPixel(hdcDst, nCol, nRow, clrBK);
//WriteBMPFile(hbmNew, "Masked.bmp", hdcDst);
DeleteDC(hdcDst);
}
DeleteDC(hdcSrc);
}
return hbmNew;
}

int __cdecl WinMainCRTStartup(void)
{
InitCommonControls();
HBITMAP hBitmap = LoadBitmap(GetModuleHandle(TEXT("comctl32")), MAKEINTRESOURCE(120));
if (hBitmap) {
//HBITMAP hBMMasked = CopyBitmap2(hBitmap, RGB(0xC0, 0xC0, 0xC0), GetSysColor(COLOR_MENU));
HBITMAP hBMMasked = CopyBitmap2(hBitmap, RGB(0xC0, 0xC0, 0xC0), RGB(0x0, 0x0, 0x0));
}
ExitProcess(0);
}
For menu
Code: [Select]
HBITMAP CopyBitmap4Menu(HBITMAP hbmSrc)
{
HDC hdcSrc, hdcDst;
HBITMAP hbmOld, hbmNew;
BITMAP bm;
COLORREF clrTP, clrBK;

if ((hdcSrc = CreateCompatibleDC(NULL)) != NULL) {
if ((hdcDst = CreateCompatibleDC(NULL)) != NULL) {
int nRow, nCol;
GetObject(hbmSrc, sizeof(bm), &bm);
hbmOld = SelectObject(hdcSrc, hbmSrc);
hbmNew = CreateBitmap(bm.bmWidth, bm.bmHeight, bm.bmPlanes, bm.bmBitsPixel, NULL);
SelectObject(hdcDst, hbmNew);
BitBlt(hdcDst, 0, 0, bm.bmWidth, bm.bmHeight, hdcSrc, 0, 0, SRCCOPY);
SelectObject(hdcSrc, hbmOld);
clrTP = GetPixel(hdcDst, 0, 0);
clrBK = GetSysColor(COLOR_MENU);
for (nRow = 0; nRow < bm.bmHeight; nRow++)
for (nCol = 0; nCol < bm.bmWidth; nCol++)
if (GetPixel(hdcDst, nCol, nRow) == clrTP)
SetPixel(hdcDst, nCol, nRow, clrBK);
WriteBMPFile(hbmNew, "Masked1.bmp", hdcDst);
DeleteDC(hdcDst);
}
DeleteDC(hdcSrc);
}
return hbmNew;
}
« Last Edit: October 15, 2011, 08:03:28 PM by timovjl »
May the source be with you

laurro

  • Guest
Re: Simple menu with bitmap
« Reply #12 on: August 13, 2012, 11:28:11 PM »
   For Stefan

I do not know if your problem with menus is solved , but if not, maybe this is a solution (see zip folder).
It works in Vista ok.
The program has only one function, nothing special , only the basic skeleton, without error checking, no GDI+.
The function can be easily modified to work with resources or how to choose the transparent color
(in this version the transparent color is set by the first pixel in the original bitmap).
I apologize for my English and I hope it can be useful to you.

   Laur

Offline Stefan Pendl

  • Global Moderator
  • Member
  • *****
  • Posts: 582
    • Homepage
Re: Simple menu with bitmap
« Reply #13 on: August 14, 2012, 07:30:11 AM »
Thanks for reminding me that I have to report back.

I have been successful using the masking function.
---
Stefan

Proud member of the UltraDefrag Development Team

laurro

  • Guest
Re: Simple menu with bitmap
« Reply #14 on: August 14, 2012, 08:36:59 AM »
  Ok .
Laur