Commit 728255cf authored by Dmitry Timoshkov's avatar Dmitry Timoshkov Committed by Alexandre Julliard

user32: Add support for PNG icons.

parent f80b1f45
......@@ -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 = \
......
......@@ -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 <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,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')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment