12

I've seen here that you can create an image list with transparency. It works... sort of.

I used this to create an image list for a list control. The results were a little disappointing:

view of actual list image view of list image

The one on the left is how it should look. The one on the right is how the list control is displaying it. It looks like it just tried to use the alpha as a mask and any blended area is attempted to be approximated by dithering. Is there a way of getting this better so that I get an actual alpha blended image?

Here is the source if that makes any difference:

class CDlg : public CDialog
{
    DECLARE_DYNCREATE(CDlg)

public:
    CDlg(CWnd* pParent = NULL);   // standard constructor
    virtual ~CDlg();

    // Dialog Data
    enum { IDD = IDD_BS_PRINT };
    CGdiPlusBitmapResource m_pBitmap;

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    virtual BOOL OnInitDialog();

    DECLARE_MESSAGE_MAP()
public:
    CListCtrl m_printOptions;
};

BOOL CDlg::OnInitDialog()
{
    __super::OnInitDialog();

    m_pBitmap.Load(IDB_RIBBON_HOMELARGE, _T("PNG"), AfxGetResourceHandle());
    HBITMAP hBitmap;
    m_pBitmap.m_pBitmap->GetHBITMAP(RGB(0, 0, 0), &hBitmap);

    CImageList *pList = new CImageList;
    CBitmap bm;
    bm.Attach(hBitmap);
    pList->Create(32, 32, ILC_COLOR32, 0, 4);
    pList->Add(&bm, RGB(255, 0, 255));
    m_printOptions.SetImageList(pList, LVSIL_NORMAL);

//...
    return TRUE;
}

IMPLEMENT_DYNCREATE(CDlg, CDialog)

CBSPrintDlg::CBSPrintDlg(CWnd* pParent /*=NULL*/)
: CBCGPDialog(CBSPrintDlg::IDD, pParent)
{
}

CBSPrintDlg::~CBSPrintDlg()
{
}

void CBSPrintDlg::DoDataExchange(CDataExchange* pDX)
{
    CBCGPDialog::DoDataExchange(pDX);

    DDX_Control(pDX, IDC_PRINT_OPTIONS, m_printOptions);
}

For source of CGdiPlusBitmapResource implementation look here.

The original image with transparency is this: enter image description here

@Barmak tried with a different image and it looks fine. I think that is because the transparency is near the edge and not located within the image. See here:

enter image description here

Community
  • 1
  • 1
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • You can definitely have transparent buttons on a toolbar. I'm not familiar with `CGdiPlusBitmapResource` but the call to `GetHBITMAP` looks suspect. Supplying a colour key rather implies that transparency is being removed. – arx Oct 29 '14 at 17:33
  • Ok, so how would I read in a `PNG` and give it to a `CListCtrl` while maintaining it's alpha channel? – Adrian Oct 29 '14 at 18:11
  • Sorry, I've just tried GetHBITMAP and it's not the problem: it seems to preserve transparency as required. You may need to pre-multiply the alpha, but I don't have time to try that right now. – arx Oct 30 '14 at 00:34
  • Have you looked at using the ImgSource library from www.smalleranimals.com? It supports 32 bit transparent images and can copy them from one format to another. It might assist you with this issue. – Andrew Truckle Mar 15 '16 at 17:21
  • I couldn't duplicate that display error. Are you sure that's the *.png image used? – Barmak Shemirani Mar 20 '16 at 05:12
  • I've not used the MFC wrappers, but I know it's possible to put images with alpha into an image list. Your method isn't working (at least in part) because you're providing a COLORREF as the second argument, which is telling the image list to generate a mask. You don't want a mask, you want a 32-bpp image with alpha. Also, make sure the pixel values in the source image a pre-multiplied by the alpha. – Adrian McCarthy Mar 21 '16 at 23:48
  • See https://msdn.microsoft.com/en-us/library/windows/desktop/bb761389(v=vs.85).aspx#icons I think part of the problem is that you can't load the PNG into a regular bitmap. It has to be a DIBSECTION. – Adrian McCarthy Mar 21 '16 at 23:56

2 Answers2

6

Edit ----------

First parameter in Gdiplus::GetHBITMAP should be the background color. Using RGB(0, 0, 0) as background color causes the semi-transparent pixels to match with black.

Using Gdiplus::Color(255,255,255,255) (white) it will improve the appearance (because ListView background is also white). But it's better to change the background to Gdiplus::Color(0,255,255,255) (transparent) to match any background.

{
    CGdiPlusBitmapResource gdibmp;
    if (gdibmp.Load(IDB_RIBBON_HOMELARGE, _T("PNG"), AfxGetResourceHandle()))
    {
        HBITMAP hBitmap;
        gdibmp.m_pBitmap->GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap);
        ImageList_AddMasked(*pList, hBitmap, 0);
    }
}

This assume images are all 32x32 pixels. If images are different sizes, they have to be resized before being added to image list.

{
    CGdiPlusBitmapResource gdibmp;
    if (gdibmp.Load(id, _T("PNG"), AfxGetResourceHandle()))
    {
        //resize image to 32x32 pixels
        Gdiplus::Bitmap newBmp(32, 32, PixelFormat32bppPARGB);
        double oldh = (double)gdibmp.m_pBitmap->GetHeight();
        double oldw = (double)gdibmp.m_pBitmap->GetWidth();
        double neww = 32;
        double newh = 32;

        double ratio = oldw / oldh;
        if (oldw > oldh)
            newh = neww / ratio;
        else
            neww = newh * ratio;

        Gdiplus::Graphics graphics(&newBmp);
        graphics.SetInterpolationMode(Gdiplus::InterpolationMode::InterpolationModeHighQualityBicubic);
        graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
        graphics.DrawImage(gdibmp.m_pBitmap, 0, 0, (int)neww, (int)newh);

        //add `newBmp` to image list
        HBITMAP hBitmap;
        newBmp.GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap);
        ImageList_AddMasked(m_ImageList, hBitmap, 0);
    }
}


Using GdiPlus::GetHICON to get the icon handle... With CGdiPlusBitmapResource class, it should be possible to use the following:
HICON hicon;
m_pBitmap.Load(IDB_RIBBON_HOMELARGE, _T("PNG"), AfxGetResourceHandle());
m_pBitmap.m_pBitmap->GetHICON(&hicon);
pList->Add(hicon);

or using GetHBITMAP

Also make sure Visual Styles is enabled for improved appearance of ListView icons.

Test image with transparent background:

enter image description here

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • `it should be possible to use the following`. No, it doesn't work. :( Also, your 2nd option I don't understand. Is the png in the resource or is it an external file? Where in the `.RC` file do I put the `"image_name" RCDATA "path.png"` line? Does it matter? – Adrian Mar 21 '16 at 16:35
  • You might have a file called *.rc2 right next to *.rc file. Right click on *.rc2 and open it with text editor. You can add `"image_name" RCDATA "file_path.png"` near the end in *.rc2 --- Or add near the end of *.rc file --- It's similar way you added other png resources with `CGdiPlusBitmapResource ` – Barmak Shemirani Mar 21 '16 at 16:48
  • I downloaded `CGdiPlusBitmapResource` from the link and it works just fine with the test image which I added to the question. I am using VS2015/Windows 10. You should give more information about the errors you are getting, add full screen shot of window/dialog. I think you have not set the Visual Style. – Barmak Shemirani Mar 21 '16 at 17:13
  • I'm using VS2013, Win8.1 with a default MFC Application as a Window Dialog app project. I'll look into Visual Styles and see if I don't have them enabled. Oh, and when I run the `LoadResource_GdiBitmap()` function, `FindResource()` returns a nullptr. The pragma is in my dialogue .cpp file. – Adrian Mar 21 '16 at 17:19
  • So, using my original code, your image looks fine (see my question, I've added a sample), but I cannot get it to work with `HICON` functions that you are describing (either your first or second versions). I just get blank spaces where the icons should be. – Adrian Mar 21 '16 at 18:14
  • Must have typed in something incorrectly, because now your 1st solution at least now shows an image, though still with the weird dithered region around the spyglass. Another thing, if the PNG consists of several icons, it messes it up by scaling all of the images to fit in the first image in the list. This works with or without the `#pragma` setting. – Adrian Mar 21 '16 at 18:26
  • Use your own function then, with transparent background `m_pBitmap->GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap);` see edit. The images are assumed to be 32x32 (that's how you created the image list). But the new image which you added is slightly different dimension. That's a different issue. If you use `GetHICON` then the images are automatically resized. – Barmak Shemirani Mar 21 '16 at 20:43
  • Ahhh, that worked. Where did you get the information about `m_pBitmap->GetHBITMAP(Gdiplus::Color::Transparent, &hBitmap);`? That is EXACTLY what I was looking for! Once you post a reference, I'll give you the bounty. Oh, also, I didn't need the `#pragma`. Maybe it's defaulted in my current environment. – Adrian Mar 21 '16 at 21:22
  • `Color::Transparent` is defined in "gdipluscolor.h" as `0x00FFFFFF` I can't find any MSDN reference. In [Color class](https://msdn.microsoft.com/en-us/library/ms534427%28v=vs.85%29.aspx) there is reference to "Enumeration" but they didn't fill that column. In `Gdiplus::Color::Color(alpha, red, blue, green)` alpha is zero for 0% opaque color, and it is 0xFF for 100% opaque color. – Barmak Shemirani Mar 21 '16 at 23:23
  • then how did you know how to do that? – Adrian Mar 22 '16 at 02:11
  • It's not that hard to find. Just type in `Gdiplus::Color::` and Visual Studio will show all the predefined color values. – Barmak Shemirani Mar 22 '16 at 02:33
  • but how did you know that putting that value in there would enable the alpha channel? – Adrian Mar 22 '16 at 02:43
  • That part is explain in [`GetHBITMAP`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms536295%28v=vs.85%29.aspx) documentation. The first parameter in `GetHBITMAP` should be the background color. Originally you had it at `RGB(0, 0, 0)` that was causing a minor problem. If you change it to `Gdiplus::Color(255,255,255,255)` (white) it will improve (because ListView background is also white). But it's better to change it to `Gdiplus::Color(0,255,255,255)` (transparent) to match any background. – Barmak Shemirani Mar 22 '16 at 07:27
  • Ah, then add that to the answer and then this will be done and you can have your bounty. – Adrian Mar 22 '16 at 14:01
-1

The PNG image contains pixels that are partially transparent (alpha < 255). That's a pretty common accident with a program like Photoshop, the most likely cause is overlaying the spyglass image on top of the document image and not merging the layers correctly.

As given, the image can only look good when it is displayed on top of the light-gray or white background. But that didn't happen, the background was black. Now making the anti-aliasing pixels around the spyglass painfully obvious, they turned various shades of dark-gray depending on their alpha value and no longer blend with the white background of the document image. A very typical mishap when you use GDI functions, it does not like alpha.

You can doctor it with GDI+, ensuring that the background color is correct. But that's a fair amount of work and it still leaves you with the trouble of guessing at the original background color correctly.

Just really rather best to go back to whatever painting tool you used and fix the problem there. Quickest fix ought to be re-saving it as a 24bpp BMP image file, ymmv.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Although this is interesting in a general way, it doesn't come close to answering the question. The original question pointed at a class that uses GDI+ to attempt to put an semitransparent image in an image list, which didn't work and which is what the question is trying to ascertain as to why it didn't. – Adrian Mar 21 '16 at 16:41
  • It explains what went wrong and the *best* way to fix the problem. You can certainly pursue a different solution, it is however neither the best nor the simplest way. Just do it your way if that is what you like to do, take another year, nobody will stop you. Best if you talk to @Barmak. – Hans Passant Mar 21 '16 at 16:44
  • The background? What background? I never said any background was any particular colour. if you are talking about the list controls background, it is in fact white. The area around the spyglass actually has semitransparent pixels in it, but they are being displayed incorrectly. I will pursue a different solution only because your answer doesn't answer the question, and for no other reason then that. – Adrian Mar 21 '16 at 17:01
  • Image Lists fully support 32-bpp color with alpha despite that fact that they use GDI under the hood. Don't use any interface that talks about a mask and make sure the source image has pre-multiplied alpha. (The fact that you're trying to use fuchsia as the "transparency" color suggests that the source image in not pre-multiplied.) – Adrian McCarthy Mar 21 '16 at 23:52