From 728255cf2ceca073e0818a310b62864d1b3725a8 Mon Sep 17 00:00:00 2001 From: Dmitry Timoshkov Date: Tue, 3 Apr 2018 13:50:03 +0800 Subject: [PATCH] user32: Add support for PNG icons. Signed-off-by: Dmitry Timoshkov Signed-off-by: Alexandre Julliard --- dlls/user32/Makefile.in | 1 + dlls/user32/cursoricon.c | 341 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 334 insertions(+), 8 deletions(-) diff --git a/dlls/user32/Makefile.in b/dlls/user32/Makefile.in index 246c4241554..931a715763c 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 8e272f09ce9..6304192131c 100644 --- a/dlls/user32/cursoricon.c +++ b/dlls/user32/cursoricon.c @@ -2,10 +2,12 @@ * Cursor and icon support * * Copyright 1995 Alexandre Julliard - * 1996 Martin Von Loewis - * 1997 Alex Korobka - * 1998 Turchanov Sergey - * 2007 Henri Verbeet + * Copyright 1996 Martin Von Loewis + * Copyright 1997 Alex Korobka + * Copyright 1998 Turchanov Sergey + * Copyright 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 #include #include +#ifdef HAVE_PNG_H +#include +#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,303 @@ 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 +{ + const char *buffer; + size_t 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_ptr, "failed to read PNG data"); + } +} + +static unsigned be_uint(unsigned val) +{ + union + { + unsigned val; + unsigned char c[4]; + } u; + + u.val = val; + return (u.c[0] << 24) | (u.c[1] << 16) | (u.c[2] << 8) | u.c[3]; +} + +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 }; + static const char png_IHDR[8] = { 0,0,0,0x0d,'I','H','D','R' }; + const struct + { + char png_sig[8]; + char ihdr_sig[8]; + unsigned width, height; + char bit_depth, color_type, compression, filter, interlace; + } *png = png_data; + + if (size < sizeof(*png)) return FALSE; + if (memcmp(png->png_sig, png_sig, sizeof(png_sig)) != 0) return FALSE; + if (memcmp(png->ihdr_sig, png_IHDR, sizeof(png_IHDR)) != 0) return FALSE; + + *bpp = (png->color_type == PNG_COLOR_TYPE_RGB_ALPHA) ? 32 : 24; + *width = be_uint(png->width); + *height = be_uint(png->height); + + return TRUE; +} + +static BITMAPINFO *load_png(const char *png_data, DWORD *size) +{ + struct png_wrapper png; + png_structp png_ptr; + png_infop info_ptr; + png_bytep *row_pointers = NULL; + jmp_buf jmpbuf; + int color_type, bit_depth, bpp, width, height; + int rowbytes, image_size, mask_size = 0, i; + BITMAPINFO *info = NULL; + unsigned char *image_data; + + if (!get_png_info(png_data, *size, &width, &height, &bpp)) + return NULL; + + if (!load_libpng()) return NULL; + + png.buffer = png_data; + png.size = *size; + png.pos = 0; + + /* initialize libpng */ + png_ptr = ppng_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) return NULL; + + info_ptr = ppng_create_info_struct(png_ptr); + if (!info_ptr) + { + ppng_destroy_read_struct(&png_ptr, NULL, NULL); + return NULL; + } + + /* set up setjmp/longjmp error handling */ + if (setjmp(jmpbuf)) + { + HeapFree(GetProcessHeap(), 0, row_pointers); + HeapFree(GetProcessHeap(), 0, info); + ppng_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return NULL; + } + + ppng_set_error_fn(png_ptr, jmpbuf, user_error_fn, user_warning_fn); + ppng_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + + /* set up custom i/o handling */ + ppng_set_read_fn(png_ptr, &png, user_read_data); + + /* read the header */ + ppng_read_info(png_ptr, info_ptr); + + color_type = ppng_get_color_type(png_ptr, info_ptr); + bit_depth = ppng_get_bit_depth(png_ptr, 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_ptr); + + /* expand palette image data to rgb */ + if (color_type == PNG_COLOR_TYPE_PALETTE || bit_depth < 8) + ppng_set_expand(png_ptr); + + /* update color type information */ + ppng_read_update_info(png_ptr, info_ptr); + + color_type = ppng_get_color_type(png_ptr, info_ptr); + bit_depth = ppng_get_bit_depth(png_ptr, info_ptr); + + bpp = 0; + + switch (color_type) + { + case PNG_COLOR_TYPE_RGB: + if (bit_depth == 8) + bpp = 24; + break; + + case PNG_COLOR_TYPE_RGB_ALPHA: + if (bit_depth == 8) + { + ppng_set_bgr(png_ptr); + bpp = 32; + } + break; + + default: + break; + } + + if (!bpp) + { + FIXME("unsupported PNG color format %d, %d bpp\n", color_type, bit_depth); + ppng_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return NULL; + } + + width = ppng_get_image_width(png_ptr, info_ptr); + height = ppng_get_image_height(png_ptr, info_ptr); + + rowbytes = (width * bpp + 7) / 8; + image_size = height * rowbytes; + if (bpp != 32) /* add a mask if there is no alpha */ + mask_size = (width + 7) / 8 * height; + + info = HeapAlloc(GetProcessHeap(), 0, sizeof(BITMAPINFOHEADER) + image_size + mask_size); + if (!info) + { + ppng_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return NULL; + } + + image_data = (unsigned char *)info + sizeof(BITMAPINFOHEADER); + memset(image_data + image_size, 0, mask_size); + + row_pointers = HeapAlloc(GetProcessHeap(), 0, height * sizeof(png_bytep)); + if (!row_pointers) + { + HeapFree(GetProcessHeap(), 0, info); + ppng_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return NULL; + } + + /* upside down */ + for (i = 0; i < height; i++) + row_pointers[i] = image_data + (height - i - 1) * rowbytes; + + ppng_read_image(png_ptr, row_pointers); + HeapFree(GetProcessHeap(), 0, row_pointers); + ppng_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info->bmiHeader.biWidth = width; + info->bmiHeader.biHeight = height * 2; + info->bmiHeader.biPlanes = 1; + info->bmiHeader.biBitCount = 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; + + return info; +} + +#else /* SONAME_LIBPNG */ + +static BOOL get_png_info(const void *png_data, DWORD size, int *width, int *height, int *bpp) +{ + return FALSE; +} + +static BITMAPINFO *load_png( const char *png, DWORD *max_size ) +{ + return NULL; +} + +#endif + static HICON alloc_icon_handle( BOOL is_ani, UINT num_steps ) { struct cursoricon_object *obj; @@ -523,6 +831,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 +990,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 +1138,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 +1330,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')