1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/imaglist.cpp
3 // Purpose:     wxImageList implementation for Win32
4 // Author:      Julian Smart
5 // Modified by:
6 // Created:     04/01/98
7 // Copyright:   (c) Julian Smart
8 // Licence:     wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10 
11 // ============================================================================
12 // declarations
13 // ============================================================================
14 
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18 
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21 
22 #ifdef __BORLANDC__
23     #pragma hdrstop
24 #endif
25 
26 #ifndef WX_PRECOMP
27     #include "wx/app.h"
28     #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
29     #include "wx/window.h"
30     #include "wx/icon.h"
31     #include "wx/dc.h"
32     #include "wx/string.h"
33     #include "wx/dcmemory.h"
34     #include "wx/intl.h"
35     #include "wx/log.h"
36     #include "wx/image.h"
37     #include <stdio.h>
38 #endif
39 
40 #include "wx/imaglist.h"
41 #include "wx/dc.h"
42 #include "wx/msw/dc.h"
43 #include "wx/msw/dib.h"
44 #include "wx/msw/private.h"
45 
46 // ----------------------------------------------------------------------------
47 // wxWin macros
48 // ----------------------------------------------------------------------------
49 
50 IMPLEMENT_DYNAMIC_CLASS(wxImageList, wxObject)
51 
52 #define GetHImageList()     ((HIMAGELIST)m_hImageList)
53 
54 // ----------------------------------------------------------------------------
55 // private functions
56 // ----------------------------------------------------------------------------
57 
58 // returns the mask if it's valid, otherwise the bitmap mask and, if it's not
59 // valid neither, a "solid" mask (no transparent zones at all)
60 static HBITMAP GetMaskForImage(const wxBitmap& bitmap, const wxBitmap& mask);
61 
62 // ============================================================================
63 // implementation
64 // ============================================================================
65 
66 // ----------------------------------------------------------------------------
67 // wxImageList creation/destruction
68 // ----------------------------------------------------------------------------
69 
wxImageList()70 wxImageList::wxImageList()
71 {
72     m_hImageList = 0;
73 }
74 
75 // Creates an image list
Create(int width,int height,bool mask,int initial)76 bool wxImageList::Create(int width, int height, bool mask, int initial)
77 {
78     UINT flags = 0;
79 
80     // as we want to be able to use 32bpp bitmaps in the image lists, we always
81     // use ILC_COLOR32, even if the display resolution is less -- the system
82     // will make the best effort to show the bitmap if we do this resulting in
83     // quite acceptable display while using a lower depth ILC_COLOR constant
84     // (e.g. ILC_COLOR16) shows completely broken bitmaps
85 #ifdef __WXWINCE__
86     flags |= ILC_COLOR;
87 #else
88     flags |= ILC_COLOR32;
89 #endif
90 
91     // For comctl32.dll < 6 always use masks as it doesn't support alpha.
92     if ( mask || wxApp::GetComCtl32Version() < 600 )
93         flags |= ILC_MASK;
94 
95     // Grow by 1, I guess this is reasonable behaviour most of the time
96     m_hImageList = (WXHIMAGELIST) ImageList_Create(width, height, flags,
97                                                    initial, 1);
98     if ( !m_hImageList )
99     {
100         wxLogLastError(wxT("ImageList_Create()"));
101     }
102 
103     return m_hImageList != 0;
104 }
105 
~wxImageList()106 wxImageList::~wxImageList()
107 {
108     if ( m_hImageList )
109     {
110         ImageList_Destroy(GetHImageList());
111         m_hImageList = 0;
112     }
113 }
114 
115 // ----------------------------------------------------------------------------
116 // wxImageList attributes
117 // ----------------------------------------------------------------------------
118 
119 // Returns the number of images in the image list.
GetImageCount() const120 int wxImageList::GetImageCount() const
121 {
122     wxASSERT_MSG( m_hImageList, wxT("invalid image list") );
123 
124     return ImageList_GetImageCount(GetHImageList());
125 }
126 
127 // Returns the size (same for all images) of the images in the list
GetSize(int WXUNUSED (index),int & width,int & height) const128 bool wxImageList::GetSize(int WXUNUSED(index), int &width, int &height) const
129 {
130     wxASSERT_MSG( m_hImageList, wxT("invalid image list") );
131 
132     return ImageList_GetIconSize(GetHImageList(), &width, &height) != 0;
133 }
134 
135 // ----------------------------------------------------------------------------
136 // wxImageList operations
137 // ----------------------------------------------------------------------------
138 
139 // Adds a bitmap, and optionally a mask bitmap.
140 // Note that wxImageList creates new bitmaps, so you may delete
141 // 'bitmap' and 'mask'.
Add(const wxBitmap & bitmap,const wxBitmap & mask)142 int wxImageList::Add(const wxBitmap& bitmap, const wxBitmap& mask)
143 {
144     HBITMAP hbmp;
145     bool useMask;
146 
147 #if wxUSE_WXDIB && wxUSE_IMAGE
148     // wxBitmap normally stores alpha in pre-multiplied format but
149     // ImageList_Draw() does pre-multiplication internally so we need to undo
150     // the pre-multiplication here. Converting back and forth like this is, of
151     // course, very inefficient but it's better than wrong appearance so we do
152     // this for now until a better way can be found.
153     AutoHBITMAP hbmpRelease;
154     if ( bitmap.HasAlpha() )
155     {
156         wxImage img = bitmap.ConvertToImage();
157 
158         // For comctl32.dll < 6 remove alpha channel from image
159         // to prevent possible interferences with the mask.
160         if ( wxApp::GetComCtl32Version() < 600 )
161         {
162             img.ClearAlpha();
163             useMask = true;
164         }
165         else
166         {
167             useMask = false;
168         }
169 
170         hbmp = wxDIB(img, wxDIB::PixelFormat_NotPreMultiplied).Detach();
171         hbmpRelease.Init(hbmp);
172     }
173     else
174 #endif // wxUSE_WXDIB && wxUSE_IMAGE
175     {
176         hbmp = GetHbitmapOf(bitmap);
177         useMask = true;
178     }
179 
180     // Use mask only if we don't have alpha, the bitmap isn't drawn correctly
181     // if we use both.
182     AutoHBITMAP hbmpMask;
183     if ( useMask )
184         hbmpMask.Init(GetMaskForImage(bitmap, mask));
185 
186     int index = ImageList_Add(GetHImageList(), hbmp, hbmpMask);
187     if ( index == -1 )
188     {
189         wxLogError(_("Couldn't add an image to the image list."));
190     }
191 
192     return index;
193 }
194 
195 // Adds a bitmap, using the specified colour to create the mask bitmap
196 // Note that wxImageList creates new bitmaps, so you may delete
197 // 'bitmap'.
Add(const wxBitmap & bitmap,const wxColour & maskColour)198 int wxImageList::Add(const wxBitmap& bitmap, const wxColour& maskColour)
199 {
200     HBITMAP hbmp;
201 
202 #if wxUSE_WXDIB && wxUSE_IMAGE
203     // See the comment in overloaded Add() above.
204     AutoHBITMAP hbmpRelease;
205     if ( bitmap.HasAlpha() )
206     {
207         wxImage img = bitmap.ConvertToImage();
208 
209         if ( wxApp::GetComCtl32Version() < 600 )
210         {
211             img.ClearAlpha();
212         }
213 
214         hbmp = wxDIB(img, wxDIB::PixelFormat_NotPreMultiplied).Detach();
215         hbmpRelease.Init(hbmp);
216     }
217     else
218 #endif // wxUSE_WXDIB && wxUSE_IMAGE
219         hbmp = GetHbitmapOf(bitmap);
220 
221     int index = ImageList_AddMasked(GetHImageList(),
222                                     hbmp,
223                                     wxColourToRGB(maskColour));
224     if ( index == -1 )
225     {
226         wxLogError(_("Couldn't add an image to the image list."));
227     }
228 
229     return index;
230 }
231 
232 // Adds a bitmap and mask from an icon.
Add(const wxIcon & icon)233 int wxImageList::Add(const wxIcon& icon)
234 {
235     int index = ImageList_AddIcon(GetHImageList(), GetHiconOf(icon));
236     if ( index == -1 )
237     {
238         wxLogError(_("Couldn't add an image to the image list."));
239     }
240 
241     return index;
242 }
243 
244 // Replaces a bitmap, optionally passing a mask bitmap.
245 // Note that wxImageList creates new bitmaps, so you may delete
246 // 'bitmap' and 'mask'.
Replace(int index,const wxBitmap & bitmap,const wxBitmap & mask)247 bool wxImageList::Replace(int index,
248                           const wxBitmap& bitmap,
249                           const wxBitmap& mask)
250 {
251     HBITMAP hbmp;
252     bool useMask;
253 
254 #if wxUSE_WXDIB && wxUSE_IMAGE
255     // See the comment in Add() above.
256     AutoHBITMAP hbmpRelease;
257     if ( bitmap.HasAlpha() )
258     {
259         wxImage img = bitmap.ConvertToImage();
260 
261         if ( wxApp::GetComCtl32Version() < 600 )
262         {
263             img.ClearAlpha();
264             useMask = true;
265         }
266         else
267         {
268             useMask = false;
269         }
270 
271         hbmp = wxDIB(img, wxDIB::PixelFormat_NotPreMultiplied).Detach();
272         hbmpRelease.Init(hbmp);
273     }
274     else
275 #endif // wxUSE_WXDIB && wxUSE_IMAGE
276     {
277         hbmp = GetHbitmapOf(bitmap);
278         useMask = true;
279     }
280 
281     AutoHBITMAP hbmpMask;
282     if ( useMask )
283         hbmpMask.Init(GetMaskForImage(bitmap, mask));
284 
285     if ( !ImageList_Replace(GetHImageList(), index, hbmp, hbmpMask) )
286     {
287         wxLogLastError(wxT("ImageList_Replace()"));
288         return false;
289     }
290 
291     return true;
292 }
293 
294 // Replaces a bitmap and mask from an icon.
Replace(int i,const wxIcon & icon)295 bool wxImageList::Replace(int i, const wxIcon& icon)
296 {
297     bool ok = ImageList_ReplaceIcon(GetHImageList(), i, GetHiconOf(icon)) != -1;
298     if ( !ok )
299     {
300         wxLogLastError(wxT("ImageList_ReplaceIcon()"));
301     }
302 
303     return ok;
304 }
305 
306 // Removes the image at the given index.
Remove(int index)307 bool wxImageList::Remove(int index)
308 {
309     bool ok = ImageList_Remove(GetHImageList(), index) != 0;
310     if ( !ok )
311     {
312         wxLogLastError(wxT("ImageList_Remove()"));
313     }
314 
315     return ok;
316 }
317 
318 // Remove all images
RemoveAll()319 bool wxImageList::RemoveAll()
320 {
321     // don't use ImageList_RemoveAll() because mingw32 headers don't have it
322     return Remove(-1);
323 }
324 
325 // Draws the given image on a dc at the specified position.
326 // If 'solidBackground' is true, Draw sets the image list background
327 // colour to the background colour of the wxDC, to speed up
328 // drawing by eliminating masked drawing where possible.
Draw(int index,wxDC & dc,int x,int y,int flags,bool solidBackground)329 bool wxImageList::Draw(int index,
330                        wxDC& dc,
331                        int x, int y,
332                        int flags,
333                        bool solidBackground)
334 {
335     wxDCImpl *impl = dc.GetImpl();
336     wxMSWDCImpl *msw_impl = wxDynamicCast( impl, wxMSWDCImpl );
337     if (!msw_impl)
338        return false;
339 
340     HDC hDC = GetHdcOf(*msw_impl);
341     wxCHECK_MSG( hDC, false, wxT("invalid wxDC in wxImageList::Draw") );
342 
343     COLORREF clr = CLR_NONE;    // transparent by default
344     if ( solidBackground )
345     {
346         const wxBrush& brush = dc.GetBackground();
347         if ( brush.IsOk() )
348         {
349             clr = wxColourToRGB(brush.GetColour());
350         }
351     }
352 
353     ImageList_SetBkColor(GetHImageList(), clr);
354 
355     UINT style = 0;
356     if ( flags & wxIMAGELIST_DRAW_NORMAL )
357         style |= ILD_NORMAL;
358     if ( flags & wxIMAGELIST_DRAW_TRANSPARENT )
359         style |= ILD_TRANSPARENT;
360     if ( flags & wxIMAGELIST_DRAW_SELECTED )
361         style |= ILD_SELECTED;
362     if ( flags & wxIMAGELIST_DRAW_FOCUSED )
363         style |= ILD_FOCUS;
364 
365     bool ok = ImageList_Draw(GetHImageList(), index, hDC, x, y, style) != 0;
366     if ( !ok )
367     {
368         wxLogLastError(wxT("ImageList_Draw()"));
369     }
370 
371     return ok;
372 }
373 
374 // Get the bitmap
GetBitmap(int index) const375 wxBitmap wxImageList::GetBitmap(int index) const
376 {
377     int bmp_width = 0, bmp_height = 0;
378     GetSize(index, bmp_width, bmp_height);
379 
380     wxBitmap bitmap(bmp_width, bmp_height);
381     wxMemoryDC dc;
382     dc.SelectObject(bitmap);
383 
384 #if wxUSE_WXDIB && wxUSE_IMAGE
385     IMAGEINFO ii;
386     ImageList_GetImageInfo(GetHImageList(), index, &ii);
387     if ( ii.hbmMask )
388     {
389         // draw it the first time to find a suitable mask colour
390         ((wxImageList*)this)->Draw(index, dc, 0, 0, wxIMAGELIST_DRAW_TRANSPARENT);
391         dc.SelectObject(wxNullBitmap);
392 
393         // find the suitable mask colour
394         wxImage image = bitmap.ConvertToImage();
395         unsigned char r = 0, g = 0, b = 0;
396         image.FindFirstUnusedColour(&r, &g, &b);
397 
398         // redraw whole image and bitmap in the mask colour
399         image.Create(bmp_width, bmp_height);
400         image.Replace(0, 0, 0, r, g, b);
401         bitmap = wxBitmap(image);
402 
403         // redraw icon over the mask colour to actually draw it
404         dc.SelectObject(bitmap);
405         ((wxImageList*)this)->Draw(index, dc, 0, 0, wxIMAGELIST_DRAW_TRANSPARENT);
406         dc.SelectObject(wxNullBitmap);
407 
408         // get the image, set the mask colour and convert back to get transparent bitmap
409         image = bitmap.ConvertToImage();
410         image.SetMaskColour(r, g, b);
411         bitmap = wxBitmap(image);
412     }
413     else // no mask
414     {
415         // Just draw it normally.
416         ((wxImageList*)this)->Draw(index, dc, 0, 0, wxIMAGELIST_DRAW_NORMAL);
417         dc.SelectObject(wxNullBitmap);
418 
419         // And adjust its alpha flag as the destination bitmap would get it if
420         // the source one had it.
421         //
422         // Note that perhaps we could just call UseAlpha() which would set the
423         // "has alpha" flag unconditionally as it doesn't seem to do any harm,
424         // but for now only do it if necessary, just to be on the safe side,
425         // even if it requires more work (and takes more time).
426         bitmap.MSWUpdateAlpha();
427     }
428 #else
429     wxBitmap bitmap;
430 #endif
431     return bitmap;
432 }
433 
434 // Get the icon
GetIcon(int index) const435 wxIcon wxImageList::GetIcon(int index) const
436 {
437     HICON hIcon = ImageList_ExtractIcon(0, GetHImageList(), index);
438     if (hIcon)
439     {
440         wxIcon icon;
441         icon.SetHICON((WXHICON)hIcon);
442 
443         int iconW, iconH;
444         GetSize(index, iconW, iconH);
445         icon.SetSize(iconW, iconH);
446 
447         return icon;
448     }
449     else
450         return wxNullIcon;
451 }
452 
453 // ----------------------------------------------------------------------------
454 // helpers
455 // ----------------------------------------------------------------------------
456 
GetMaskForImage(const wxBitmap & bitmap,const wxBitmap & mask)457 static HBITMAP GetMaskForImage(const wxBitmap& bitmap, const wxBitmap& mask)
458 {
459 #if wxUSE_IMAGE
460     wxBitmap bitmapWithMask;
461 #endif // wxUSE_IMAGE
462 
463     HBITMAP hbmpMask;
464     wxMask *pMask;
465     bool deleteMask = false;
466 
467     if ( mask.IsOk() )
468     {
469         hbmpMask = GetHbitmapOf(mask);
470         pMask = NULL;
471     }
472     else
473     {
474         pMask = bitmap.GetMask();
475 
476 #if wxUSE_IMAGE
477         // check if we don't have alpha in this bitmap -- we can create a mask
478         // from it (and we need to do it for the older systems which don't
479         // support 32bpp bitmaps natively)
480         if ( !pMask )
481         {
482             wxImage img(bitmap.ConvertToImage());
483             if ( img.HasAlpha() )
484             {
485                 img.ConvertAlphaToMask();
486                 bitmapWithMask = wxBitmap(img);
487                 pMask = bitmapWithMask.GetMask();
488             }
489         }
490 #endif // wxUSE_IMAGE
491 
492         if ( !pMask )
493         {
494             // use the light grey count as transparent: the trouble here is
495             // that the light grey might have been changed by Windows behind
496             // our back, so use the standard colour map to get its real value
497             wxCOLORMAP *cmap = wxGetStdColourMap();
498             wxColour col;
499             wxRGBToColour(col, cmap[wxSTD_COL_BTNFACE].from);
500 
501             pMask = new wxMask(bitmap, col);
502 
503             deleteMask = true;
504         }
505 
506         hbmpMask = (HBITMAP)pMask->GetMaskBitmap();
507     }
508 
509     // windows mask convention is opposite to the wxWidgets one
510     HBITMAP hbmpMaskInv = wxInvertMask(hbmpMask);
511 
512     if ( deleteMask )
513     {
514         delete pMask;
515     }
516 
517     return hbmpMaskInv;
518 }
519