Release 1.5.29.
[wine] / dlls / winemac.drv / image.c
1 /*
2  * MACDRV image functions
3  *
4  * Copyright 2013 Ken Thomases for CodeWeavers Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include "config.h"
22
23 #include "macdrv.h"
24 #include "winuser.h"
25
26 WINE_DEFAULT_DEBUG_CHANNEL(image);
27
28 #include "pshpack1.h"
29
30 typedef struct
31 {
32     BYTE bWidth;
33     BYTE bHeight;
34     BYTE bColorCount;
35     BYTE bReserved;
36     WORD wPlanes;
37     WORD wBitCount;
38     DWORD dwBytesInRes;
39     WORD nID;
40 } GRPICONDIRENTRY;
41
42 typedef struct
43 {
44     WORD idReserved;
45     WORD idType;
46     WORD idCount;
47     GRPICONDIRENTRY idEntries[1];
48 } GRPICONDIR;
49
50 #include "poppack.h"
51
52
53 /***********************************************************************
54  *              create_cgimage_from_icon_bitmaps
55  */
56 CGImageRef create_cgimage_from_icon_bitmaps(HDC hdc, HANDLE icon, HBITMAP hbmColor,
57                                             unsigned char *color_bits, int color_size, HBITMAP hbmMask,
58                                             unsigned char *mask_bits, int mask_size, int width,
59                                             int height, int istep)
60 {
61     int i, has_alpha = FALSE;
62     DWORD *ptr;
63     CGBitmapInfo alpha_format;
64     CGColorSpaceRef colorspace;
65     CFDataRef data;
66     CGDataProviderRef provider;
67     CGImageRef cgimage;
68
69     /* draw the cursor frame to a temporary buffer then create a CGImage from that */
70     memset(color_bits, 0x00, color_size);
71     SelectObject(hdc, hbmColor);
72     if (!DrawIconEx(hdc, 0, 0, icon, width, height, istep, NULL, DI_NORMAL))
73     {
74         WARN("Could not draw frame %d (walk past end of frames).\n", istep);
75         return NULL;
76     }
77
78     /* check if the cursor frame was drawn with an alpha channel */
79     for (i = 0, ptr = (DWORD*)color_bits; i < width * height; i++, ptr++)
80         if ((has_alpha = (*ptr & 0xff000000) != 0)) break;
81
82     if (has_alpha)
83         alpha_format = kCGImageAlphaFirst;
84     else
85         alpha_format = kCGImageAlphaNoneSkipFirst;
86
87     colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
88     if (!colorspace)
89     {
90         WARN("failed to create colorspace\n");
91         return NULL;
92     }
93
94     data = CFDataCreate(NULL, (UInt8*)color_bits, color_size);
95     if (!data)
96     {
97         WARN("failed to create data\n");
98         CGColorSpaceRelease(colorspace);
99         return NULL;
100     }
101
102     provider = CGDataProviderCreateWithCFData(data);
103     CFRelease(data);
104     if (!provider)
105     {
106         WARN("failed to create data provider\n");
107         CGColorSpaceRelease(colorspace);
108         return NULL;
109     }
110
111     cgimage = CGImageCreate(width, height, 8, 32, width * 4, colorspace,
112                             alpha_format | kCGBitmapByteOrder32Little,
113                             provider, NULL, FALSE, kCGRenderingIntentDefault);
114     CGDataProviderRelease(provider);
115     CGColorSpaceRelease(colorspace);
116     if (!cgimage)
117     {
118         WARN("failed to create image\n");
119         return NULL;
120     }
121
122     /* if no alpha channel was drawn then generate it from the mask */
123     if (!has_alpha)
124     {
125         unsigned int width_bytes = (width + 31) / 32 * 4;
126         CGImageRef cgmask, temp;
127
128         /* draw the cursor mask to a temporary buffer */
129         memset(mask_bits, 0xFF, mask_size);
130         SelectObject(hdc, hbmMask);
131         if (!DrawIconEx(hdc, 0, 0, icon, width, height, istep, NULL, DI_MASK))
132         {
133             WARN("Failed to draw frame mask %d.\n", istep);
134             CGImageRelease(cgimage);
135             return NULL;
136         }
137
138         data = CFDataCreate(NULL, (UInt8*)mask_bits, mask_size);
139         if (!data)
140         {
141             WARN("failed to create data\n");
142             CGImageRelease(cgimage);
143             return NULL;
144         }
145
146         provider = CGDataProviderCreateWithCFData(data);
147         CFRelease(data);
148         if (!provider)
149         {
150             WARN("failed to create data provider\n");
151             CGImageRelease(cgimage);
152             return NULL;
153         }
154
155         cgmask = CGImageMaskCreate(width, height, 1, 1, width_bytes, provider, NULL, FALSE);
156         CGDataProviderRelease(provider);
157         if (!cgmask)
158         {
159             WARN("failed to create mask\n");
160             CGImageRelease(cgimage);
161             return NULL;
162         }
163
164         temp = CGImageCreateWithMask(cgimage, cgmask);
165         CGImageRelease(cgmask);
166         CGImageRelease(cgimage);
167         if (!temp)
168         {
169             WARN("failed to create masked image\n");
170             return NULL;
171         }
172         cgimage = temp;
173     }
174
175     return cgimage;
176 }
177
178
179 /***********************************************************************
180  *              create_cgimage_from_icon
181  *
182  * Create a CGImage from a Windows icon.
183  */
184 CGImageRef create_cgimage_from_icon(HANDLE icon, int width, int height)
185 {
186     CGImageRef ret = NULL;
187     HDC hdc;
188     char buffer[FIELD_OFFSET(BITMAPINFO, bmiColors[256])];
189     BITMAPINFO *bitmapinfo = (BITMAPINFO*)buffer;
190     unsigned char *color_bits, *mask_bits;
191     HBITMAP hbmColor = 0, hbmMask = 0;
192     int color_size, mask_size;
193
194     TRACE("icon %p width %d height %d\n", icon, width, height);
195
196     if (!width && !height)
197     {
198         ICONINFO info;
199         BITMAP bm;
200
201         if (!GetIconInfo(icon, &info))
202             return NULL;
203
204         GetObjectW(info.hbmMask, sizeof(bm), &bm);
205         if (!info.hbmColor) bm.bmHeight = max(1, bm.bmHeight / 2);
206         width = bm.bmWidth;
207         height = bm.bmHeight;
208         TRACE("new width %d height %d\n", width, height);
209
210         DeleteObject(info.hbmColor);
211         DeleteObject(info.hbmMask);
212     }
213
214     hdc = CreateCompatibleDC(0);
215
216     bitmapinfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
217     bitmapinfo->bmiHeader.biWidth = width;
218     bitmapinfo->bmiHeader.biHeight = -height;
219     bitmapinfo->bmiHeader.biPlanes = 1;
220     bitmapinfo->bmiHeader.biCompression = BI_RGB;
221     bitmapinfo->bmiHeader.biXPelsPerMeter = 0;
222     bitmapinfo->bmiHeader.biYPelsPerMeter = 0;
223     bitmapinfo->bmiHeader.biClrUsed = 0;
224     bitmapinfo->bmiHeader.biClrImportant = 0;
225     bitmapinfo->bmiHeader.biBitCount = 32;
226     color_size = width * height * 4;
227     bitmapinfo->bmiHeader.biSizeImage = color_size;
228     hbmColor = CreateDIBSection(hdc, bitmapinfo, DIB_RGB_COLORS, (VOID **) &color_bits, NULL, 0);
229     if (!hbmColor)
230     {
231         WARN("failed to create DIB section for cursor color data\n");
232         goto cleanup;
233     }
234
235     bitmapinfo->bmiHeader.biBitCount = 1;
236     bitmapinfo->bmiColors[0].rgbRed      = 0;
237     bitmapinfo->bmiColors[0].rgbGreen    = 0;
238     bitmapinfo->bmiColors[0].rgbBlue     = 0;
239     bitmapinfo->bmiColors[0].rgbReserved = 0;
240     bitmapinfo->bmiColors[1].rgbRed      = 0xff;
241     bitmapinfo->bmiColors[1].rgbGreen    = 0xff;
242     bitmapinfo->bmiColors[1].rgbBlue     = 0xff;
243     bitmapinfo->bmiColors[1].rgbReserved = 0;
244     mask_size = ((width + 31) / 32 * 4) * height;
245     bitmapinfo->bmiHeader.biSizeImage = mask_size;
246     hbmMask = CreateDIBSection(hdc, bitmapinfo, DIB_RGB_COLORS, (VOID **) &mask_bits, NULL, 0);
247     if (!hbmMask)
248     {
249         WARN("failed to create DIB section for cursor mask data\n");
250         goto cleanup;
251     }
252
253     ret = create_cgimage_from_icon_bitmaps(hdc, icon, hbmColor, color_bits, color_size, hbmMask,
254                                            mask_bits, mask_size, width, height, 0);
255
256 cleanup:
257     if (hbmColor) DeleteObject(hbmColor);
258     if (hbmMask) DeleteObject(hbmMask);
259     DeleteDC(hdc);
260     return ret;
261 }
262
263
264 /***********************************************************************
265  *              get_first_resource
266  *
267  * Helper for create_app_icon_images().  Enum proc for EnumResourceNamesW()
268  * which just gets the handle for the first resource and stops further
269  * enumeration.
270  */
271 static BOOL CALLBACK get_first_resource(HMODULE module, LPCWSTR type, LPWSTR name, LONG_PTR lparam)
272 {
273     HRSRC *res_info = (HRSRC*)lparam;
274
275     *res_info = FindResourceW(module, name, (LPCWSTR)RT_GROUP_ICON);
276     return FALSE;
277 }
278
279
280 /***********************************************************************
281  *              create_app_icon_images
282  */
283 CFArrayRef create_app_icon_images(void)
284 {
285     HRSRC res_info;
286     HGLOBAL res_data;
287     GRPICONDIR *icon_dir;
288     CFMutableArrayRef images = NULL;
289     int i;
290
291     TRACE("()\n");
292
293     res_info = NULL;
294     EnumResourceNamesW(NULL, (LPCWSTR)RT_GROUP_ICON, get_first_resource, (LONG_PTR)&res_info);
295     if (!res_info)
296     {
297         WARN("found no RT_GROUP_ICON resource\n");
298         return NULL;
299     }
300
301     if (!(res_data = LoadResource(NULL, res_info)))
302     {
303         WARN("failed to load RT_GROUP_ICON resource\n");
304         return NULL;
305     }
306
307     if (!(icon_dir = LockResource(res_data)))
308     {
309         WARN("failed to lock RT_GROUP_ICON resource\n");
310         goto cleanup;
311     }
312
313     images = CFArrayCreateMutable(NULL, icon_dir->idCount, &kCFTypeArrayCallBacks);
314     if (!images)
315     {
316         WARN("failed to create images array\n");
317         goto cleanup;
318     }
319
320     for (i = 0; i < icon_dir->idCount; i++)
321     {
322         int width = icon_dir->idEntries[i].bWidth;
323         int height = icon_dir->idEntries[i].bHeight;
324         BOOL found_better_bpp = FALSE;
325         int j;
326         LPCWSTR name;
327         HGLOBAL icon_res_data;
328         BYTE *icon_bits;
329
330         if (!width) width = 256;
331         if (!height) height = 256;
332
333         /* If there's another icon at the same size but with better
334            color depth, skip this one.  We end up making CGImages that
335            are all 32 bits per pixel, so Cocoa doesn't get the original
336            color depth info to pick the best representation itself. */
337         for (j = 0; j < icon_dir->idCount; j++)
338         {
339             int jwidth = icon_dir->idEntries[j].bWidth;
340             int jheight = icon_dir->idEntries[j].bHeight;
341
342             if (!jwidth) jwidth = 256;
343             if (!jheight) jheight = 256;
344
345             if (j != i && jwidth == width && jheight == height &&
346                 icon_dir->idEntries[j].wBitCount > icon_dir->idEntries[i].wBitCount)
347             {
348                 found_better_bpp = TRUE;
349                 break;
350             }
351         }
352
353         if (found_better_bpp) continue;
354
355         name = MAKEINTRESOURCEW(icon_dir->idEntries[i].nID);
356         res_info = FindResourceW(NULL, name, (LPCWSTR)RT_ICON);
357         if (!res_info)
358         {
359             WARN("failed to find RT_ICON resource %d with ID %hd\n", i, icon_dir->idEntries[i].nID);
360             continue;
361         }
362
363         icon_res_data = LoadResource(NULL, res_info);
364         if (!icon_res_data)
365         {
366             WARN("failed to load icon %d with ID %hd\n", i, icon_dir->idEntries[i].nID);
367             continue;
368         }
369
370         icon_bits = LockResource(icon_res_data);
371         if (icon_bits)
372         {
373             static const BYTE png_magic[] = { 0x89, 0x50, 0x4e, 0x47 };
374             CGImageRef cgimage = NULL;
375
376             if (!memcmp(icon_bits, png_magic, sizeof(png_magic)))
377             {
378                 CFDataRef data = CFDataCreate(NULL, (UInt8*)icon_bits, icon_dir->idEntries[i].dwBytesInRes);
379                 if (data)
380                 {
381                     CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
382                     CFRelease(data);
383                     if (provider)
384                     {
385                         cgimage = CGImageCreateWithPNGDataProvider(provider, NULL, FALSE,
386                                                                    kCGRenderingIntentDefault);
387                         CGDataProviderRelease(provider);
388                     }
389                 }
390             }
391
392             if (!cgimage)
393             {
394                 HICON icon;
395                 icon = CreateIconFromResourceEx(icon_bits, icon_dir->idEntries[i].dwBytesInRes,
396                                                 TRUE, 0x00030000, width, height, 0);
397                 if (icon)
398                 {
399                     cgimage = create_cgimage_from_icon(icon, width, height);
400                     DestroyIcon(icon);
401                 }
402                 else
403                     WARN("failed to create icon %d from resource with ID %hd\n", i, icon_dir->idEntries[i].nID);
404             }
405
406             if (cgimage)
407             {
408                 CFArrayAppendValue(images, cgimage);
409                 CGImageRelease(cgimage);
410             }
411         }
412         else
413             WARN("failed to lock RT_ICON resource %d with ID %hd\n", i, icon_dir->idEntries[i].nID);
414
415         FreeResource(icon_res_data);
416     }
417
418 cleanup:
419     if (images && !CFArrayGetCount(images))
420     {
421         CFRelease(images);
422         images = NULL;
423     }
424     FreeResource(res_data);
425
426     return images;
427 }