Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/user32/Makefile.in | 1 + dlls/user32/cursoricon.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 334 insertions(+), 4 deletions(-)
diff --git a/dlls/user32/Makefile.in b/dlls/user32/Makefile.in index 246c424155..931a715763 100644 --- a/dlls/user32/Makefile.in +++ b/dlls/user32/Makefile.in @@ -2,6 +2,7 @@ EXTRADEFS = -D_USER32_ -D_WINABLE_ MODULE = user32.dll IMPORTLIB = user32 IMPORTS = gdi32 version advapi32 +EXTRAINCL = $(PNG_CFLAGS) DELAYIMPORTS = imm32 usp10
C_SRCS = \ diff --git a/dlls/user32/cursoricon.c b/dlls/user32/cursoricon.c index 8e272f09ce..1cb46050d4 100644 --- a/dlls/user32/cursoricon.c +++ b/dlls/user32/cursoricon.c @@ -6,6 +6,8 @@ * 1997 Alex Korobka * 1998 Turchanov Sergey * 2007 Henri Verbeet + * Copyright 2009 Vincent Povirk for CodeWeavers + * Copyright 2016 Dmitry Timoshkov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,6 +31,9 @@ #include <stdarg.h> #include <string.h> #include <stdlib.h> +#ifdef HAVE_PNG_H +#include <png.h> +#endif
#include "windef.h" #include "winbase.h" @@ -43,11 +48,17 @@ #include "wine/list.h" #include "wine/unicode.h" #include "wine/debug.h" +#include "wine/library.h"
WINE_DEFAULT_DEBUG_CHANNEL(cursor); WINE_DECLARE_DEBUG_CHANNEL(icon); WINE_DECLARE_DEBUG_CHANNEL(resource);
+#define RIFF_FOURCC( c0, c1, c2, c3 ) \ + ( (DWORD)(BYTE)(c0) | ( (DWORD)(BYTE)(c1) << 8 ) | \ + ( (DWORD)(BYTE)(c2) << 16 ) | ( (DWORD)(BYTE)(c3) << 24 ) ) +#define PNG_SIGN RIFF_FOURCC(0x89,'P','N','G') + static struct list icon_cache = LIST_INIT( icon_cache );
/********************************************************************** @@ -108,6 +119,307 @@ static int get_display_bpp(void) return ret; }
+#ifdef SONAME_LIBPNG + +static void *libpng_handle; +#define MAKE_FUNCPTR(f) static typeof(f) * p##f +MAKE_FUNCPTR(png_create_read_struct); +MAKE_FUNCPTR(png_create_info_struct); +MAKE_FUNCPTR(png_destroy_read_struct); +MAKE_FUNCPTR(png_error); +MAKE_FUNCPTR(png_get_bit_depth); +MAKE_FUNCPTR(png_get_color_type); +MAKE_FUNCPTR(png_get_error_ptr); +MAKE_FUNCPTR(png_get_image_height); +MAKE_FUNCPTR(png_get_image_width); +MAKE_FUNCPTR(png_get_io_ptr); +MAKE_FUNCPTR(png_read_image); +MAKE_FUNCPTR(png_read_info); +MAKE_FUNCPTR(png_read_update_info); +MAKE_FUNCPTR(png_set_bgr); +MAKE_FUNCPTR(png_set_crc_action); +MAKE_FUNCPTR(png_set_error_fn); +MAKE_FUNCPTR(png_set_expand); +MAKE_FUNCPTR(png_set_gray_to_rgb); +MAKE_FUNCPTR(png_set_read_fn); +#undef MAKE_FUNCPTR + +static BOOL load_libpng(void) +{ + USER_Lock(); + + if (!libpng_handle && (libpng_handle = wine_dlopen(SONAME_LIBPNG, RTLD_NOW, NULL, 0)) != NULL) + { +#define LOAD_FUNCPTR(f) \ + if ((p##f = wine_dlsym(libpng_handle, #f, NULL, 0)) == NULL) \ + { \ + libpng_handle = NULL; \ + USER_Unlock(); \ + return FALSE; \ + } + LOAD_FUNCPTR(png_create_read_struct); + LOAD_FUNCPTR(png_create_info_struct); + LOAD_FUNCPTR(png_destroy_read_struct); + LOAD_FUNCPTR(png_error); + LOAD_FUNCPTR(png_get_bit_depth); + LOAD_FUNCPTR(png_get_color_type); + LOAD_FUNCPTR(png_get_error_ptr); + LOAD_FUNCPTR(png_get_image_height); + LOAD_FUNCPTR(png_get_image_width); + LOAD_FUNCPTR(png_get_io_ptr); + LOAD_FUNCPTR(png_read_image); + LOAD_FUNCPTR(png_read_info); + LOAD_FUNCPTR(png_read_update_info); + LOAD_FUNCPTR(png_set_bgr); + LOAD_FUNCPTR(png_set_crc_action); + LOAD_FUNCPTR(png_set_error_fn); + LOAD_FUNCPTR(png_set_expand); + LOAD_FUNCPTR(png_set_gray_to_rgb); + LOAD_FUNCPTR(png_set_read_fn); +#undef LOAD_FUNCPTR + } + + USER_Unlock(); + return TRUE; +} + +static void user_error_fn(png_structp png_ptr, png_const_charp error_message) +{ + jmp_buf *pjmpbuf; + + /* This uses setjmp/longjmp just like the default. We can't use the + * default because there's no way to access the jmp buffer in the png_struct + * that works in 1.2 and 1.4 and allows us to dynamically load libpng. */ + WARN("PNG error: %s\n", debugstr_a(error_message)); + pjmpbuf = ppng_get_error_ptr(png_ptr); + longjmp(*pjmpbuf, 1); +} + +static void user_warning_fn(png_structp png_ptr, png_const_charp warning_message) +{ + WARN("PNG warning: %s\n", debugstr_a(warning_message)); +} + +struct png_wrapper +{ + png_structp png_ptr; + png_infop info_ptr; + int width, height, bpp; + const char *buffer; + int size, pos; +}; + +static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + struct png_wrapper *png = ppng_get_io_ptr(png_ptr); + + if (png->size - png->pos >= length) + { + memcpy(data, png->buffer + png->pos, length); + png->pos += length; + } + else + { + ppng_error(png->png_ptr, "failed to read PNG data"); + } +} + +static BOOL create_png_decoder(struct png_wrapper *png) +{ + jmp_buf jmpbuf; + int color_type, bit_depth; + + if (!load_libpng()) return FALSE; + + /* initialize libpng */ + png->png_ptr = ppng_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png->png_ptr) return FALSE; + + png->info_ptr = ppng_create_info_struct(png->png_ptr); + if (!png->info_ptr) + { + ppng_destroy_read_struct(&png->png_ptr, NULL, NULL); + return FALSE; + } + + /* set up setjmp/longjmp error handling */ + if (setjmp(jmpbuf)) + { + ppng_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL); + return FALSE; + } + + ppng_set_error_fn(png->png_ptr, jmpbuf, user_error_fn, user_warning_fn); + ppng_set_crc_action(png->png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + + /* set up custom i/o handling */ + ppng_set_read_fn(png->png_ptr, png, user_read_data); + + /* read the header */ + ppng_read_info(png->png_ptr, png->info_ptr); + + color_type = ppng_get_color_type(png->png_ptr, png->info_ptr); + bit_depth = ppng_get_bit_depth(png->png_ptr, png->info_ptr); + + /* expand grayscale image data to rgb */ + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + ppng_set_gray_to_rgb(png->png_ptr); + + /* expand palette image data to rgb */ + if (color_type == PNG_COLOR_TYPE_PALETTE || bit_depth < 8) + ppng_set_expand(png->png_ptr); + + /* update color type information */ + ppng_read_update_info(png->png_ptr, png->info_ptr); + + color_type = ppng_get_color_type(png->png_ptr, png->info_ptr); + bit_depth = ppng_get_bit_depth(png->png_ptr, png->info_ptr); + + png->bpp = 0; + + switch (color_type) + { + case PNG_COLOR_TYPE_RGB: + if (bit_depth == 8) + png->bpp = 24; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + if (bit_depth == 8) + { + ppng_set_bgr(png->png_ptr); + png->bpp = 32; + } + break; + + default: + break; + } + + if (!png->bpp) + { + FIXME("unsupported PNG color format %d, %d bpp\n", color_type, bit_depth); + ppng_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL); + return FALSE; + } + + png->width = ppng_get_image_width(png->png_ptr, png->info_ptr); + png->height = ppng_get_image_height(png->png_ptr, png->info_ptr); + + return TRUE; +} + +static void destroy_png_decoder(struct png_wrapper *png) +{ + ppng_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL); +} + +static BOOL get_png_info(const void *png_data, DWORD size, int *width, int *height, int *bpp) +{ + static const char png_sig[8] = { 0x89,'P','N','G',0x0d,0x0a,0x1a,0x0a }; + struct png_wrapper png; + + if (size < sizeof(png_sig) || memcmp(png_data, png_sig, sizeof(png_sig)) != 0) + return FALSE; + + png.buffer = png_data; + png.size = size; + png.pos = 0; + + if (!create_png_decoder(&png)) return FALSE; + + *width = png.width; + *height = png.height; + *bpp = png.bpp; + + destroy_png_decoder(&png); + return TRUE; +} + +static BITMAPINFO *load_png(const char *png_data, DWORD *size) +{ + static const char png_sig[8] = { 0x89,'P','N','G',0x0d,0x0a,0x1a,0x0a }; + struct png_wrapper png; + png_bytep *row_pointers; + int rowbytes, image_size, mask_size = 0, i; + BITMAPINFO *info; + unsigned char *image_data; + + if (*size < sizeof(png_sig) || memcmp(png_data, png_sig, sizeof(png_sig)) != 0) + return NULL; + + png.buffer = png_data; + png.size = *size; + png.pos = 0; + + if (!create_png_decoder(&png)) return NULL; + + rowbytes = (png.width * png.bpp + 7) / 8; + image_size = png.height * rowbytes; + if (png.bpp != 32) /* add a mask if there is no alpha */ + mask_size = (png.width + 7) / 8 * png.height; + + info = HeapAlloc(GetProcessHeap(), 0, sizeof(BITMAPINFOHEADER) + image_size + mask_size); + if (!info) + { + destroy_png_decoder(&png); + return NULL; + } + + image_data = (unsigned char *)info + sizeof(BITMAPINFOHEADER); + memset(image_data + image_size, 0, mask_size); + + row_pointers = HeapAlloc(GetProcessHeap(), 0, png.height * sizeof(png_bytep)); + if (!row_pointers) + { + HeapFree(GetProcessHeap(), 0, info); + destroy_png_decoder(&png); + return NULL; + } + + /* upside down */ + for (i = 0; i < png.height; i++) + row_pointers[i] = image_data + (png.height - i - 1) * rowbytes; + + ppng_read_image(png.png_ptr, row_pointers); + + HeapFree(GetProcessHeap(), 0, row_pointers); + + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info->bmiHeader.biWidth = png.width; + info->bmiHeader.biHeight = png.height * 2; + info->bmiHeader.biPlanes = 1; + info->bmiHeader.biBitCount = png.bpp; + info->bmiHeader.biCompression = BI_RGB; + info->bmiHeader.biSizeImage = image_size; + info->bmiHeader.biXPelsPerMeter = 0; + info->bmiHeader.biYPelsPerMeter = 0; + info->bmiHeader.biClrUsed = 0; + info->bmiHeader.biClrImportant = 0; + + *size = sizeof(BITMAPINFOHEADER) + image_size + mask_size; + + destroy_png_decoder(&png); + + return info; +} + +#else /* SONAME_LIBPNG */ + +static BOOL get_png_info(const void *png_data, DWORD size, int *width, int *height, int *bpp) +{ + ERR("Trying to load PNG icon, but PNG support is not compiled in.\n"); + return FALSE; +} + +static BITMAPINFO *load_png( const char *png, DWORD *max_size ) +{ + ERR("Trying to load PNG icon, but PNG support is not compiled in.\n"); + return NULL; +} + +#endif + static HICON alloc_icon_handle( BOOL is_ani, UINT num_steps ) { struct cursoricon_object *obj; @@ -523,6 +835,8 @@ static int CURSORICON_FindBestIcon( LPCVOID dir, DWORD size, fnGetCIEntry get_en /* Find Best Colors for Best Fit */ for ( i = 0; get_entry( dir, size, i, &cx, &cy, &bits ); i++ ) { + TRACE("entry %d: %d x %d, %d bpp\n", i, cx, cy, bits); + if(abs(width - cx) == iXDiff && abs(height - cy) == iYDiff) { iTempColorDiff = abs(depth - bits); @@ -680,6 +994,10 @@ static BOOL CURSORICON_GetFileEntry( LPCVOID dir, DWORD size, int n, entry = &filedir->idEntries[n]; if (entry->dwDIBOffset > size - sizeof(info->biSize)) return FALSE; info = (const BITMAPINFOHEADER *)((const char *)dir + entry->dwDIBOffset); + + if (info->biSize == PNG_SIGN) + return get_png_info(info, size, width, height, bits); + if (info->biSize != sizeof(BITMAPCOREHEADER)) { if ((const char *)(info + 1) - (const char *)dir > size) return FALSE; @@ -824,6 +1142,21 @@ static HICON create_icon_from_bmi( const BITMAPINFO *bmi, DWORD maxsize, HMODULE
/* Check bitmap header */
+ if (bmi->bmiHeader.biSize == PNG_SIGN) + { + BITMAPINFO *bmi_png; + + bmi_png = load_png( (const char *)bmi, &maxsize ); + if (bmi_png) + { + hObj = create_icon_from_bmi( bmi_png, maxsize, module, resname, + rsrc, hotspot, bIcon, width, height, cFlag ); + HeapFree( GetProcessHeap(), 0, bmi_png ); + return hObj; + } + return 0; + } + if (maxsize < sizeof(BITMAPCOREHEADER)) { WARN( "invalid size %u\n", maxsize ); @@ -1001,10 +1334,6 @@ done: /********************************************************************** * .ANI cursor support */ -#define RIFF_FOURCC( c0, c1, c2, c3 ) \ - ( (DWORD)(BYTE)(c0) | ( (DWORD)(BYTE)(c1) << 8 ) | \ - ( (DWORD)(BYTE)(c2) << 16 ) | ( (DWORD)(BYTE)(c3) << 24 ) ) - #define ANI_RIFF_ID RIFF_FOURCC('R', 'I', 'F', 'F') #define ANI_LIST_ID RIFF_FOURCC('L', 'I', 'S', 'T') #define ANI_ACON_ID RIFF_FOURCC('A', 'C', 'O', 'N')