diff --git a/interface/wx/imaglist.h b/interface/wx/imaglist.h index 71cd5ad804..55d3a68826 100644 --- a/interface/wx/imaglist.h +++ b/interface/wx/imaglist.h @@ -28,8 +28,8 @@ enum @class wxImageList A wxImageList contains a list of images, which are stored in an unspecified - form. Images can have masks for transparent drawing, and can be made from a - variety of sources including bitmaps and icons. + form. Images can use alpha channel or masks for transparent drawing, and + can be made from a variety of sources including bitmaps and icons. wxImageList is used principally in conjunction with wxTreeCtrl and wxListCtrl classes. @@ -62,7 +62,12 @@ public: @param height Height of the images in the list. @param mask - @true if masks should be created for all images. + If @true, all images will have masks, with the mask being created + from the light grey pixels if not specified otherwise, i.e. if the + image doesn't have neither alpha channel nor mask and no mask is + explicitly specified when adding it. Note that if an image does + have alpha channel or mask, it will always be used, whether this + parameter is @true or @false. @param initialCount The initial size of the list. diff --git a/src/generic/imaglist.cpp b/src/generic/imaglist.cpp index 076dc105e3..9dc034bf40 100644 --- a/src/generic/imaglist.cpp +++ b/src/generic/imaglist.cpp @@ -71,62 +71,14 @@ bool wxGenericImageList::Create( int width, int height, bool mask, int WXUNUSED( wxBitmap wxGenericImageList::GetImageListBitmap(const wxBitmap& bitmap) const { wxBitmap bmp(bitmap); - if ( m_useMask ) + + // If we don't have neither mask nor alpha and were asked to use a mask, + // create a default one. + if ( m_useMask && !bmp.GetMask() && !bmp.HasAlpha() ) { - if ( bmp.GetMask() ) - { - if ( bmp.HasAlpha() ) - { - // We need to remove alpha channel for compatibility with - // native-based wxMSW wxImageList where stored images are not allowed - // to have both mask and alpha channel. -#if wxUSE_IMAGE - wxImage img = bmp.ConvertToImage(); - img.ClearAlpha(); - bmp = wxBitmap(img, -1, bmp.GetScaleFactor()); -#endif // wxUSE_IMAGE - } - } - else - { - if ( bmp.HasAlpha() ) - { - // Convert alpha channel to mask. -#if wxUSE_IMAGE - wxImage img = bmp.ConvertToImage(); - img.ConvertAlphaToMask(); - bmp = wxBitmap(img, -1, bmp.GetScaleFactor()); -#endif // wxUSE_IMAGE - } - else - { - // Like for wxMSW, use the light grey from standard colour map as transparent colour. - wxColour col = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); - bmp.SetMask(new wxMask(bmp, col)); - } - } - } - else - { - if ( bmp.GetMask() ) - { - if ( bmp.HasAlpha() ) - { - // TODO: It would be better to blend a mask with existing alpha values. - bmp.SetMask(NULL); - } - else - { - // Convert a mask to alpha values. -#if wxUSE_IMAGE - wxImage img = bmp.ConvertToImage(); - img.InitAlpha(); - bmp = wxBitmap(img, -1, bmp.GetScaleFactor()); -#else - bmp.SetMask(NULL); -#endif // wxUSE_IMAGE - } - } + // Like for wxMSW, use the light grey from standard colour map as transparent colour. + wxColour col = wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); + bmp.SetMask(new wxMask(bmp, col)); } // Ensure image size is the same as the size of the images on the image list. diff --git a/src/msw/imaglist.cpp b/src/msw/imaglist.cpp index 7204a6201c..f2631a3acb 100644 --- a/src/msw/imaglist.cpp +++ b/src/msw/imaglist.cpp @@ -36,7 +36,6 @@ #include "wx/imaglist.h" #include "wx/dc.h" -#include "wx/scopedptr.h" #include "wx/msw/dc.h" #include "wx/msw/dib.h" #include "wx/msw/private.h" @@ -53,9 +52,8 @@ wxIMPLEMENT_DYNAMIC_CLASS(wxImageList, wxObject); // private functions // ---------------------------------------------------------------------------- -// returns the mask if it's valid, otherwise the bitmap mask and, if it's not -// valid neither, a "solid" mask (no transparent zones at all) -static HBITMAP GetMaskForImage(const wxBitmap& bitmap, const wxBitmap& mask); +// returns the default transparent colour to use for creating the mask +static wxColour GetDefaultMaskColour(); // ============================================================================ // implementation @@ -81,12 +79,16 @@ bool wxImageList::Create(int width, int height, bool mask, int initial) // (e.g. ILC_COLOR16) shows completely broken bitmaps flags |= ILC_COLOR32; + m_useMask = mask; + // For comctl32.dll < 6 always use masks as it doesn't support alpha. - if ( mask || wxApp::GetComCtl32Version() < 600 ) - { - m_useMask = true; + // + // We also have to use masks when we don't have wxImage and wxDIB that are + // needed to handle alpha. +#if wxUSE_WXDIB && wxUSE_IMAGE + if ( wxApp::GetComCtl32Version() < 600 ) +#endif flags |= ILC_MASK; - } // Grow by 1, I guess this is reasonable behaviour most of the time m_hImageList = (WXHIMAGELIST) ImageList_Create(width, height, flags, @@ -142,6 +144,14 @@ class wxImageList::wxMSWBitmaps public: wxMSWBitmaps() : hbmp(NULL) { } +#if wxUSE_WXDIB && wxUSE_IMAGE + void InitFromImageWithAlpha(const wxImage& img) + { + hbmp = wxDIB(img, wxDIB::PixelFormat_NotPreMultiplied).Detach(); + hbmpRelease.Init(hbmp); + } +#endif // wxUSE_WXDIB && wxUSE_IMAGE + // These fields are filled by GetImageListBitmaps(). HBITMAP hbmp; AutoHBITMAP hbmpMask; @@ -151,10 +161,6 @@ private: // shouldn't be used otherwise, so it's private. AutoHBITMAP hbmpRelease; - friend void wxImageList::GetImageListBitmaps(wxMSWBitmaps&, - const wxBitmap&, - const wxBitmap&); - wxDECLARE_NO_COPY_CLASS(wxMSWBitmaps); }; @@ -162,25 +168,41 @@ void wxImageList::GetImageListBitmaps(wxMSWBitmaps& bitmaps, const wxBitmap& bitmap, const wxBitmap& mask) { + // This can be overwritten below if we need to modify the bitmap, but it + // doesn't cost anything to initialize the bitmap with this HBITMAP. + bitmaps.hbmp = GetHbitmapOf(bitmap); + #if wxUSE_WXDIB && wxUSE_IMAGE - // We can only use directly bitmaps without alpha and without mask unless - // the image list uses masks and need to modify bitmap in all the other - // cases, so check if this is necessary. - if ( bitmap.HasAlpha() || (!m_useMask && (mask.IsOk() || bitmap.GetMask())) ) + if ( wxApp::GetComCtl32Version() >= 600 ) { wxBitmap bmp(bitmap); - if ( mask.IsOk() || bmp.GetMask() ) + if ( mask.IsOk() ) { // Explicitly specified mask overrides the mask associated with the // bitmap, if any. - if ( mask.IsOk() ) - bmp.SetMask(new wxMask(mask)); + bmp.SetMask(new wxMask(mask)); + } + if ( bmp.GetMask() ) + { // Get rid of the mask by converting it to alpha. if ( bmp.HasAlpha() ) bmp.MSWBlendMaskWithAlpha(); } + else if ( m_useMask ) + { + // Create the mask from the default transparent colour if we have + // nothing else. + if ( !bmp.HasAlpha() ) + bmp.SetMask(new wxMask(bmp, GetDefaultMaskColour())); + } + else + { + // We actually don't have to do anything at all and can just use + // the original bitmap as is. + return; + } // wxBitmap normally stores alpha in pre-multiplied format but // ImageList_Draw() does pre-multiplication internally so we need to undo @@ -190,8 +212,8 @@ wxImageList::GetImageListBitmaps(wxMSWBitmaps& bitmaps, wxImage img = bmp.ConvertToImage(); if ( !img.HasAlpha() ) img.InitAlpha(); - bitmaps.hbmp = wxDIB(img, wxDIB::PixelFormat_NotPreMultiplied).Detach(); - bitmaps.hbmpRelease.Init(bitmaps.hbmp); + + bitmaps.InitFromImageWithAlpha(img); // In any case we'll never use mask at the native image list level as // it's incompatible with alpha and we need to use alpha. @@ -199,9 +221,46 @@ wxImageList::GetImageListBitmaps(wxMSWBitmaps& bitmaps, else #endif // wxUSE_WXDIB && wxUSE_IMAGE { - bitmaps.hbmp = GetHbitmapOf(bitmap); - if ( m_useMask ) - bitmaps.hbmpMask.Init(GetMaskForImage(bitmap, mask)); + wxMask maskToUse; + + HBITMAP hbmpMask = NULL; + + // Always use mask if it is specified. + if ( mask.IsOk() ) + { + hbmpMask = GetHbitmapOf(mask); + } + else if ( bitmap.GetMask() ) + { + hbmpMask = bitmap.GetMask()->GetMaskBitmap(); + } +#if wxUSE_WXDIB && wxUSE_IMAGE + // We can also use alpha, but we have to convert it to a mask as it is + // not supported by this comctl32.dll version. + else if ( bitmap.HasAlpha() ) + { + wxImage img = bitmap.ConvertToImage(); + img.ConvertAlphaToMask(); + bitmaps.InitFromImageWithAlpha(img); + + maskToUse.MSWCreateFromImageMask(img); + } +#endif // wxUSE_WXDIB && wxUSE_IMAGE + // We don't have neither mask nor alpha, only force creating the + // mask from colour if requested to do it. + else if ( m_useMask ) + { + maskToUse.Create(bitmap, GetDefaultMaskColour()); + } + + if ( !hbmpMask ) + hbmpMask = maskToUse.GetMaskBitmap(); + + if ( hbmpMask ) + { + // windows mask convention is opposite to the wxWidgets one + bitmaps.hbmpMask.Init(wxInvertMask(hbmpMask)); + } } } @@ -470,35 +529,14 @@ wxIcon wxImageList::GetIcon(int index) const // helpers // ---------------------------------------------------------------------------- -static HBITMAP GetMaskForImage(const wxBitmap& bitmap, const wxBitmap& mask) +static wxColour GetDefaultMaskColour() { - HBITMAP hbmpMask; - wxScopedPtr maskDeleter; + // use the light grey count as transparent: the trouble here is + // that the light grey might have been changed by Windows behind + // our back, so use the standard colour map to get its real value + wxCOLORMAP *cmap = wxGetStdColourMap(); + wxColour col; + wxRGBToColour(col, cmap[wxSTD_COL_BTNFACE].from); - if ( mask.IsOk() ) - { - hbmpMask = GetHbitmapOf(mask); - } - else - { - wxMask* pMask = bitmap.GetMask(); - if ( !pMask ) - { - // use the light grey count as transparent: the trouble here is - // that the light grey might have been changed by Windows behind - // our back, so use the standard colour map to get its real value - wxCOLORMAP *cmap = wxGetStdColourMap(); - wxColour col; - wxRGBToColour(col, cmap[wxSTD_COL_BTNFACE].from); - - pMask = new wxMask(bitmap, col); - - maskDeleter.reset(pMask); - } - - hbmpMask = (HBITMAP)pMask->GetMaskBitmap(); - } - - // windows mask convention is opposite to the wxWidgets one - return wxInvertMask(hbmpMask); + return col; } diff --git a/tests/graphics/imagelist.cpp b/tests/graphics/imagelist.cpp index c657670160..ea62a8db65 100644 --- a/tests/graphics/imagelist.cpp +++ b/tests/graphics/imagelist.cpp @@ -20,6 +20,36 @@ #include "wx/dcmemory.h" +static bool HasNoRealAlpha(const wxBitmap& bmp) +{ + if ( !bmp.HasAlpha() ) + return true; + + // wxMSW can add a fully opaque alpha channel to the bitmaps used in the + // image list. + const wxImage img = bmp.ConvertToImage(); + const unsigned char* p = img.GetAlpha(); + if ( !p ) + return true; + + const unsigned char* const end = p + img.GetWidth()*img.GetHeight(); + for ( ; p < end; ++p ) + { + if ( *p != wxALPHA_OPAQUE ) + return false; + } + + return true; +} + +static bool HasMaskOrAlpha(const wxBitmap& bmp) +{ + // When adding bitmaps with mask to the image list, the mask can be + // transformed to alpha channel internally, so check that the bitmap has + // either mask or alpha. + return bmp.HasAlpha() || bmp.GetMask(); +} + // ---------------------------------------------------------------------------- // tests // ---------------------------------------------------------------------------- @@ -88,24 +118,21 @@ TEST_CASE("ImageList:WithMask", "[imagelist][withmask]") int idx = il.Add(bmpRGB); CHECK(il.GetImageCount() == 1); wxBitmap bmp1 = il.GetBitmap(idx); - CHECK(bmp1.HasAlpha() == false); - CHECK(bmp1.GetMask() != NULL); + CHECK(HasNoRealAlpha(bmp1)); CHECK(bmp1.GetWidth() == 32); CHECK(bmp1.GetHeight() == 32); idx = il.Add(bmpRGBWithMask); CHECK(il.GetImageCount() == 2); wxBitmap bmp2 = il.GetBitmap(idx); - CHECK(bmp2.HasAlpha() == false); - CHECK(bmp2.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); idx = il.Add(bmpRGB, *wxRED); CHECK(il.GetImageCount() == 3); wxBitmap bmp3 = il.GetBitmap(idx); - CHECK(bmp3.HasAlpha() == false); - CHECK(bmp3.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp3)); CHECK(bmp3.GetWidth() == 32); CHECK(bmp3.GetHeight() == 32); } @@ -116,24 +143,21 @@ TEST_CASE("ImageList:WithMask", "[imagelist][withmask]") int idx = il.Add(bmpRGBA); CHECK(il.GetImageCount() == 1); wxBitmap bmp1 = il.GetBitmap(idx); - CHECK(bmp1.HasAlpha() == false); - CHECK(bmp1.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp1)); CHECK(bmp1.GetWidth() == 32); CHECK(bmp1.GetHeight() == 32); idx = il.Add(bmpRGBAWithMask); CHECK(il.GetImageCount() == 2); wxBitmap bmp2 = il.GetBitmap(idx); - CHECK(bmp2.HasAlpha() == false); - CHECK(bmp2.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); idx = il.Add(bmpRGBA, *wxRED); CHECK(il.GetImageCount() == 3); wxBitmap bmp3 = il.GetBitmap(idx); - CHECK(bmp3.HasAlpha() == false); - CHECK(bmp3.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp3)); CHECK(bmp3.GetWidth() == 32); CHECK(bmp3.GetHeight() == 32); } @@ -160,14 +184,12 @@ TEST_CASE("ImageList:WithMask", "[imagelist][withmask]") il.Replace(idx2, bmpRGBWithMask); wxBitmap bmp1 = il.GetBitmap(idx1); - CHECK(bmp1.HasAlpha() == false); - CHECK(bmp1.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp1)); CHECK(bmp1.GetWidth() == 32); CHECK(bmp1.GetHeight() == 32); wxBitmap bmp2 = il.GetBitmap(idx2); - CHECK(bmp2.HasAlpha() == false); - CHECK(bmp2.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); } @@ -184,14 +206,12 @@ TEST_CASE("ImageList:WithMask", "[imagelist][withmask]") il.Replace(idx2, bmpRGBAWithMask); wxBitmap bmp1 = il.GetBitmap(idx1); - CHECK(bmp1.HasAlpha() == false); - CHECK(bmp1.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp1)); CHECK(bmp1.GetWidth() == 32); CHECK(bmp1.GetHeight() == 32); wxBitmap bmp2 = il.GetBitmap(idx2); - CHECK(bmp2.HasAlpha() == false); - CHECK(bmp2.GetMask() != NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); } @@ -506,16 +526,14 @@ TEST_CASE("ImageList:NoMask", "[imagelist][nomask]") idx = il.Add(bmpRGBWithMask); CHECK(il.GetImageCount() == 2); wxBitmap bmp2 = il.GetBitmap(idx); - CHECK(bmp2.HasAlpha() == true); - CHECK(bmp2.GetMask() == NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); idx = il.Add(bmpRGB, *wxRED); CHECK(il.GetImageCount() == 3); wxBitmap bmp3 = il.GetBitmap(idx); - CHECK(bmp3.HasAlpha() == true); - CHECK(bmp3.GetMask() == NULL); + CHECK(HasMaskOrAlpha(bmp3)); CHECK(bmp3.GetWidth() == 32); CHECK(bmp3.GetHeight() == 32); } @@ -534,16 +552,14 @@ TEST_CASE("ImageList:NoMask", "[imagelist][nomask]") idx = il.Add(bmpRGBAWithMask); CHECK(il.GetImageCount() == 2); wxBitmap bmp2 = il.GetBitmap(idx); - CHECK(bmp2.HasAlpha() == true); - CHECK(bmp2.GetMask() == NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); idx = il.Add(bmpRGBA, *wxRED); CHECK(il.GetImageCount() == 3); wxBitmap bmp3 = il.GetBitmap(idx); - CHECK(bmp3.HasAlpha() == true); - CHECK(bmp3.GetMask() == NULL); + CHECK(HasMaskOrAlpha(bmp3)); CHECK(bmp3.GetWidth() == 32); CHECK(bmp3.GetHeight() == 32); } @@ -576,8 +592,7 @@ TEST_CASE("ImageList:NoMask", "[imagelist][nomask]") CHECK(bmp1.GetHeight() == 32); wxBitmap bmp2 = il.GetBitmap(idx2); - CHECK(bmp2.HasAlpha() == true); - CHECK(bmp2.GetMask() == NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); } @@ -600,8 +615,7 @@ TEST_CASE("ImageList:NoMask", "[imagelist][nomask]") CHECK(bmp1.GetHeight() == 32); wxBitmap bmp2 = il.GetBitmap(idx2); - CHECK(bmp2.HasAlpha() == true); - CHECK(bmp2.GetMask() == NULL); + CHECK(HasMaskOrAlpha(bmp2)); CHECK(bmp2.GetWidth() == 32); CHECK(bmp2.GetHeight() == 32); }