diff --git a/dll/3rdparty/libpng/libpng.spec b/dll/3rdparty/libpng/libpng.spec index b1b008b8ffc..98fd0456635 100644 --- a/dll/3rdparty/libpng/libpng.spec +++ b/dll/3rdparty/libpng/libpng.spec @@ -7,18 +7,26 @@ @ cdecl png_destroy_read_struct(ptr ptr ptr) @ cdecl png_destroy_write_struct(ptr ptr) @ cdecl png_error(ptr ptr) +@ cdecl png_free(ptr ptr) @ cdecl png_get_bit_depth(ptr ptr) +@ cdecl png_get_channels(ptr ptr) @ cdecl png_get_color_type(ptr ptr) @ cdecl png_get_error_ptr(ptr) @ cdecl png_get_iCCP(ptr ptr ptr ptr ptr ptr) +@ cdecl png_get_IHDR(ptr ptr ptr ptr ptr ptr ptr ptr ptr) @ cdecl png_get_image_height(ptr ptr) @ cdecl png_get_image_width(ptr ptr) @ cdecl png_get_io_ptr(ptr) @ cdecl png_get_pHYs(ptr ptr ptr ptr) @ cdecl png_get_PLTE(ptr ptr ptr ptr) +@ cdecl png_get_rowbytes(ptr ptr) +@ cdecl png_get_rows(ptr ptr) @ cdecl png_get_tRNS(ptr ptr ptr ptr ptr) +@ cdecl png_malloc(ptr long) +@ cdecl png_read_end(ptr ptr) @ cdecl png_read_image(ptr ptr) @ cdecl png_read_info(ptr ptr) +@ cdecl png_read_update_info(ptr ptr) @ cdecl png_set_bgr(ptr) @ cdecl png_set_crc_action(ptr long long) @ cdecl png_set_filter(ptr long long) @@ -27,14 +35,19 @@ @ cdecl png_set_gray_to_rgb(ptr) @ cdecl png_set_IHDR(ptr ptr long long long long long long long) @ cdecl png_set_interlace_handling(ptr) +@ cdecl png_set_palette_to_rgb(ptr) @ cdecl png_set_pHYs(ptr ptr ptr long long long) @ cdecl png_set_PLTE(ptr ptr ptr long) @ cdecl png_set_read_fn(ptr ptr ptr) +@ cdecl png_set_rows(ptr ptr ptr) +@ cdecl png_set_scale_16(ptr) +@ cdecl png_set_sig_bytes(ptr long) @ cdecl png_set_swap(ptr) @ cdecl png_set_tRNS(ptr ptr ptr long ptr) @ cdecl png_set_tRNS_to_alpha(ptr) @ cdecl png_set_write_fn(ptr ptr ptr ptr) @ cdecl png_set_chunk_malloc_max(ptr long) +@ cdecl png_sig_cmp(ptr long long) @ cdecl png_write_end(ptr) @ cdecl png_write_info(ptr ptr) @ cdecl png_write_rows(ptr ptr long) diff --git a/win32ss/user/user32/CMakeLists.txt b/win32ss/user/user32/CMakeLists.txt index 2e7ac16ac0b..bb9513dfdbe 100644 --- a/win32ss/user/user32/CMakeLists.txt +++ b/win32ss/user/user32/CMakeLists.txt @@ -5,7 +5,8 @@ include_directories( ${REACTOS_SOURCE_DIR}/sdk/include/reactos/subsys include ${REACTOS_SOURCE_DIR}/sdk/include/reactos/wine - ${REACTOS_SOURCE_DIR}/win32ss/include) + ${REACTOS_SOURCE_DIR}/win32ss/include + ${REACTOS_SOURCE_DIR}/sdk/include/reactos/libs/libpng) list(APPEND SOURCE controls/appswitch.c @@ -88,7 +89,7 @@ if(MSVC AND (ARCH STREQUAL "i386")) target_sources(user32 PRIVATE $) endif() -add_delay_importlibs(user32 usp10) +add_delay_importlibs(user32 usp10 libpng) add_importlibs(user32 gdi32 advapi32 kernel32 ntdll) add_pch(user32 include/user32.h SOURCE) add_cd_file(TARGET user32 DESTINATION reactos/system32 FOR all) diff --git a/win32ss/user/user32/windows/cursoricon.c b/win32ss/user/user32/windows/cursoricon.c index b1d8bc7e69a..af7a5808e79 100644 --- a/win32ss/user/user32/windows/cursoricon.c +++ b/win32ss/user/user32/windows/cursoricon.c @@ -1,17 +1,266 @@ /* - * PROJECT: ReactOS user32.dll - * COPYRIGHT: GPL - See COPYING in the top level directory - * FILE: win32ss/user/user32/windows/cursoricon.c - * PURPOSE: cursor and icons implementation - * PROGRAMMER: Jérôme Gardou (jerome.gardou@reactos.org) + * PROJECT: ReactOS user32.dll + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: cursor and icons implementation + * COPYRIGHT: Copyright Jérôme Gardou + * Copyright 2022-2025 Doug Lyons + * Copyright 2025 Katayama Hirofumi MZ */ #include +#include "png.h" WINE_DEFAULT_DEBUG_CHANNEL(cursor); WINE_DECLARE_DEBUG_CHANNEL(icon); //WINE_DECLARE_DEBUG_CHANNEL(resource); +#include +typedef struct _CURSORICONFILEDIRENTRY +{ + BYTE bWidth; + BYTE bHeight; + BYTE bColorCount; + BYTE bReserved; + union + { + WORD wPlanes; /* For icons */ + WORD xHotspot; /* For cursors */ + }; + union + { + WORD wBitCount; /* For icons */ + WORD yHotspot; /* For cursors */ + }; + DWORD dwDIBSize; + DWORD dwDIBOffset; +} CURSORICONFILEDIRENTRY; + +typedef struct _CURSORICONFILEDIR +{ + WORD idReserved; + WORD idType; + WORD idCount; + CURSORICONFILEDIRENTRY idEntries[1]; +} CURSORICONFILEDIR; +#include + +#define PNG_CHECK_SIG_SIZE 8 /* Check signature size */ + +/* libpng helpers */ +typedef struct _PNG_READER_STATE +{ + png_bytep buffer; + size_t bufSize; + size_t currentPos; +} PNG_READER_STATE; + +/* This function is used for reading png data from memory */ +static VOID +ReadMemoryPng( + _Inout_ png_structp png_ptr, + _Out_ png_bytep data, + _In_ size_t length) +{ + PNG_READER_STATE *state = png_get_io_ptr(png_ptr); + if ((state->currentPos + length) > state->bufSize) + { + ERR("png read error\n"); + png_error(png_ptr, "read error in ReadMemoryPng"); + return; + } + RtlCopyMemory(data, state->buffer + state->currentPos, length); + state->currentPos += length; +} + +/* libpng.dll is delay-loaded. If no libpng.dll exists, we have to avoid exception */ +static BOOL +LibPngExists(VOID) +{ + static BOOL bLibPngFound = -1; + if (bLibPngFound == -1) + { + HINSTANCE hLibPng = LoadLibraryExW(L"libpng.dll", NULL, LOAD_LIBRARY_AS_DATAFILE); + bLibPngFound = !!hLibPng; + FreeLibrary(hLibPng); + } + return bLibPngFound; +} + +static int get_dib_image_size(int width, int height, int depth); + +/* Convert PNG raw data to BMP icon data */ +static PBYTE +CURSORICON_ConvertPngToBmpIcon( + _In_ PBYTE pngBits, + _In_ DWORD fileSize, + _Out_ PDWORD pBmpIconSize) +{ + TRACE("pngBits %p fileSize %d\n", pngBits, fileSize); + + if (!LibPngExists()) + { + ERR("No libpng.dll\n"); + return NULL; + } + + if (!pngBits || fileSize < PNG_CHECK_SIG_SIZE || !png_check_sig(pngBits, PNG_CHECK_SIG_SIZE)) + return NULL; + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + { + ERR("png_create_read_struct error\n"); + return NULL; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + ERR("png_create_info_struct error\n"); + png_destroy_read_struct(&png_ptr, NULL, NULL); + return NULL; + } + + /* Set our own read_function */ + PNG_READER_STATE readerState = { pngBits, fileSize, PNG_CHECK_SIG_SIZE }; + png_set_read_fn(png_ptr, &readerState, ReadMemoryPng); + png_set_sig_bytes(png_ptr, PNG_CHECK_SIG_SIZE); + + /* Read png info */ + png_read_info(png_ptr, info_ptr); + + /* Add translation of some PNG formats and update info */ + int colorType = png_get_color_type(png_ptr, info_ptr); + if (colorType == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + else if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + png_set_scale_16(png_ptr); /* Convert 16-bit channels to 8-bit */ + png_read_update_info(png_ptr, info_ptr); + + /* Get updated png info */ + png_uint_32 width, height; + int bitDepth; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth, &colorType, NULL, NULL, NULL); + TRACE("width %d, height %d, bitDepth %d, colorType %d\n", + width, height, bitDepth, colorType); + + int channels = png_get_channels(png_ptr, info_ptr); + int rowbytes = png_get_rowbytes(png_ptr, info_ptr); + int imageSize = height * rowbytes; + TRACE("rowbytes %d, channels %d, imageSize %d\n", rowbytes, channels, imageSize); + + /* Allocate rows data */ + png_bytepp rows = png_malloc(png_ptr, sizeof(png_bytep) * height); + if (!rows) + { + ERR("png_malloc failed\n"); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + return NULL; + } + + for (int i = 0; i < (int)height; i++) + { + rows[i] = png_malloc(png_ptr, rowbytes); + if (!rows[i]) + { + ERR("png_malloc failed\n"); + + /* Clean up */ + while (--i >= 0) + png_free(png_ptr, rows[i]); + png_free(png_ptr, rows); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + return NULL; + } + } + + /* Read png image data */ + png_set_rows(png_ptr, info_ptr, rows); + png_read_image(png_ptr, rows); + png_read_end(png_ptr, info_ptr); + + /* After reading the image, you can deal with row pointers */ + PBYTE imageBytes = HeapAlloc(GetProcessHeap(), 0, imageSize); + if (imageBytes) + { + PBYTE pb = imageBytes; + for (int i = height - 1; i >= 0; i--) + { + png_bytep row = rows[i]; + for (int j = 0; j < channels * width; j += channels) + { + *pb++ = row[j + 2]; /* Red */ + *pb++ = row[j + 1]; /* Green */ + *pb++ = row[j + 0]; /* Blue */ + if (channels == 4) + *pb++ = row[j + 3]; /* Alpha */ + } + pb += (channels * width) % 4; + } + } + + /* Clean up */ + for (int i = 0; i < (int)height; i++) + png_free(png_ptr, rows[i]); + png_free(png_ptr, rows); + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + + if (!imageBytes) + { + ERR("HeapAlloc failed\n"); + return NULL; + } + + /* BPP (Bits Per Pixel) */ + WORD bpp = (WORD)(bitDepth * channels); + + /* The byte size of mask bits */ + DWORD maskSize = get_dib_image_size(width, height, 1); + + /* Build BITMAPINFOHEADER */ + BITMAPINFOHEADER info = { sizeof(info) }; + info.biWidth = width; + info.biHeight = 2 * height; + info.biPlanes = 1; + info.biBitCount = bpp; + info.biCompression = BI_RGB; + + /* Build CURSORICONFILEDIR */ + CURSORICONFILEDIR cifd = { 0, 1, 1 }; + cifd.idEntries[0].bWidth = (BYTE)width; + cifd.idEntries[0].bHeight = (BYTE)height; + cifd.idEntries[0].bColorCount = 0; /* No color palette */ + cifd.idEntries[0].wPlanes = 1; /* Must be 1 */ + cifd.idEntries[0].wBitCount = bpp; + cifd.idEntries[0].dwDIBSize = (DWORD)(sizeof(info) + imageSize + maskSize); + cifd.idEntries[0].dwDIBOffset = (DWORD)sizeof(cifd); + + /* Allocate BMP icon data */ + *pBmpIconSize = (DWORD)(sizeof(cifd) + sizeof(info) + imageSize + maskSize); + PBYTE pbBmpIcon = HeapAlloc(GetProcessHeap(), 0, *pBmpIconSize); + if (!pbBmpIcon) + { + ERR("HeapAlloc failed\n"); + HeapFree(GetProcessHeap(), 0, imageBytes); + return NULL; + } + + /* Store data to pbBmpIcon */ + PBYTE pb = pbBmpIcon; + RtlCopyMemory(pb, &cifd, sizeof(cifd)); + pb += sizeof(cifd); + RtlCopyMemory(pb, &info, sizeof(info)); + pb += sizeof(info); + RtlCopyMemory(pb, imageBytes, imageSize); + pb += imageSize; + RtlFillMemory(pb, maskSize, 0xFF); /* Mask bits for AND operation */ + + HeapFree(GetProcessHeap(), 0, imageBytes); + return pbBmpIcon; +} + /* We only use Wide string functions */ #undef MAKEINTRESOURCE #define MAKEINTRESOURCE MAKEINTRESOURCEW @@ -221,7 +470,7 @@ static int DIB_GetBitmapInfo( const BITMAPINFOHEADER *header, LONG *width, } if (memcmp(&header->biSize, png_sig_pattern, sizeof(png_sig_pattern)) == 0) { - ERR("Cannot yet display PNG icons\n"); + TRACE("We have a PNG icon\n"); /* for PNG format details see https://en.wikipedia.org/wiki/PNG */ } else @@ -469,29 +718,6 @@ done: return alpha; } -#include "pshpack1.h" - -typedef struct { - BYTE bWidth; - BYTE bHeight; - BYTE bColorCount; - BYTE bReserved; - WORD xHotspot; - WORD yHotspot; - DWORD dwDIBSize; - DWORD dwDIBOffset; -} CURSORICONFILEDIRENTRY; - -typedef struct -{ - WORD idReserved; - WORD idType; - WORD idCount; - CURSORICONFILEDIRENTRY idEntries[1]; -} CURSORICONFILEDIR; - -#include "poppack.h" - const CURSORICONFILEDIRENTRY* get_best_icon_file_entry( _In_ const CURSORICONFILEDIR* dir, @@ -1155,6 +1381,14 @@ BITMAP_LoadImageW( ResSize = SizeofResource(hinst, hrsrc); } + if (pbmi->bmiHeader.biCompression == BI_BITFIELDS && pbmi->bmiHeader.biBitCount == 32) + { + SIZE_T totalSize = pbmi->bmiHeader.biSize + (3 * sizeof(DWORD)) + + pbmi->bmiHeader.biSizeImage; + if (pbmi->bmiHeader.biSizeImage != 0 && totalSize != ResSize) + WARN("Possibly bad resource size provided\n"); + } + /* Fix up values */ if(DIB_GetBitmapInfo(&pbmi->bmiHeader, &width, &height, &bpp, &compr) == -1) goto end; @@ -1385,8 +1619,8 @@ CURSORICON_LoadFromFileW( { const CURSORICONFILEDIRENTRY *entry; const CURSORICONFILEDIR *dir; - DWORD filesize = 0; - LPBYTE bits; + DWORD filesize = 0, BmpIconSize; + PBYTE bits, pbBmpIcon = NULL; HANDLE hCurIcon = NULL; CURSORDATA cursorData; @@ -1418,15 +1652,35 @@ CURSORICON_LoadFromFileW( cursorData.xHotspot = entry->xHotspot; cursorData.yHotspot = entry->yHotspot; } - cursorData.rt = (USHORT)((ULONG_PTR)(bIcon ? RT_ICON : RT_CURSOR)); + cursorData.rt = LOWORD(bIcon ? RT_ICON : RT_CURSOR); - /* Do the dance */ - if(!CURSORICON_GetCursorDataFromBMI(&cursorData, (BITMAPINFO*)(&bits[entry->dwDIBOffset]))) + /* Try to load BMP icon */ + if (!CURSORICON_GetCursorDataFromBMI(&cursorData, (PBITMAPINFO)(&bits[entry->dwDIBOffset]))) + { + /* Convert PNG raw data to BMP icon if the icon was PNG icon */ + PBYTE pngBits = &bits[entry->dwDIBOffset]; + pbBmpIcon = CURSORICON_ConvertPngToBmpIcon(pngBits, filesize, &BmpIconSize); + if (!pbBmpIcon) + goto end; /* Not PNG icon or failed */ + + /* Find icon entry from BMP icon */ + dir = (CURSORICONFILEDIR *)pbBmpIcon; + entry = &dir->idEntries[0]; /* Only one entry */ + + /* A bit of preparation */ + ZeroMemory(&cursorData, sizeof(cursorData)); + cursorData.rt = LOWORD(bIcon ? RT_ICON : RT_CURSOR); + + /* Can we load this BMP icon? */ + if (!CURSORICON_GetCursorDataFromBMI(&cursorData, (PBITMAPINFO)(&pbBmpIcon[entry->dwDIBOffset]))) { - ERR("Failing File is \n '%S'.\n", lpszName); + ERR("Failing file: '%S'.\n", lpszName); goto end; } + TRACE("Processing PNG/Vista icon: '%S'\n", lpszName); + } + hCurIcon = NtUserxCreateEmptyCurObject(FALSE); if(!hCurIcon) goto end; @@ -1435,21 +1689,19 @@ CURSORICON_LoadFromFileW( if(!NtUserSetCursorIconData(hCurIcon, NULL, NULL, &cursorData)) { NtUserDestroyCursor(hCurIcon, TRUE); - goto end_error; + hCurIcon = NULL; } end: + if (!hCurIcon) + { + if (cursorData.hbmMask) DeleteObject(cursorData.hbmMask); + if (cursorData.hbmColor) DeleteObject(cursorData.hbmColor); + if (cursorData.hbmAlpha) DeleteObject(cursorData.hbmAlpha); + } + HeapFree(GetProcessHeap(), 0, pbBmpIcon); UnmapViewOfFile(bits); return hCurIcon; - - /* Clean up */ -end_error: - DeleteObject(cursorData.hbmMask); - if(cursorData.hbmColor) DeleteObject(cursorData.hbmColor); - if(cursorData.hbmAlpha) DeleteObject(cursorData.hbmAlpha); - UnmapViewOfFile(bits); - - return NULL; } static @@ -1514,7 +1766,7 @@ CURSORICON_LoadImageW( SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } - ustrModule.Length = wsprintfW(ustrModule.Buffer, fakeNameFmt, hinst) * sizeof(WCHAR); + ustrModule.Length = (USHORT)wsprintfW(ustrModule.Buffer, fakeNameFmt, hinst) * sizeof(WCHAR); } else if(hinst) { @@ -1545,8 +1797,8 @@ CURSORICON_LoadImageW( } ustrModule.Buffer[ret] = UNICODE_NULL; - ustrModule.Length = ret * sizeof(WCHAR); - ustrModule.MaximumLength = size * sizeof(WCHAR); + ustrModule.Length = (USHORT)(ret * sizeof(WCHAR)); + ustrModule.MaximumLength = (USHORT)(size * sizeof(WCHAR)); break; } } @@ -2539,9 +2791,17 @@ HICON WINAPI CreateIconFromResourceEx( CURSORDATA cursorData; HICON hIcon; BOOL isAnimated; + PBYTE pbBmpIcon = NULL; + DWORD BmpIconSize; TRACE("%p, %lu, %lu, %lu, %i, %i, %lu.\n", pbIconBits, cbIconBits, fIcon, dwVersion, cxDesired, cyDesired, uFlags); + if (!pbIconBits || cbIconBits < 2 * sizeof(DWORD)) + { + ERR("Invalid IconBits array\n"); + return NULL; + } + if(uFlags & LR_DEFAULTSIZE) { if(!cxDesired) cxDesired = GetSystemMetrics(fIcon ? SM_CXICON : SM_CXCURSOR); @@ -2551,7 +2811,7 @@ HICON WINAPI CreateIconFromResourceEx( ZeroMemory(&cursorData, sizeof(cursorData)); cursorData.cx = cxDesired; cursorData.cy = cyDesired; - cursorData.rt = (USHORT)((ULONG_PTR)(fIcon ? RT_ICON : RT_CURSOR)); + cursorData.rt = LOWORD(fIcon ? RT_ICON : RT_CURSOR); /* Convert to win32k-ready data */ if(!memcmp(pbIconBits, "RIFF", 4)) @@ -2622,16 +2882,31 @@ HICON WINAPI CreateIconFromResourceEx( pbIconBits = (PBYTE)pt; } - if (!CURSORICON_GetCursorDataFromBMI(&cursorData, (BITMAPINFO*)pbIconBits)) - { - ERR("Couldn't fill the CURSORDATA structure.\n"); - if (ResHandle) - FreeResource(ResHandle); - return NULL; - } - if (ResHandle) - FreeResource(ResHandle); isAnimated = FALSE; + + /* Try to load BMP icon */ + if (!CURSORICON_GetCursorDataFromBMI(&cursorData, (PBITMAPINFO)pbIconBits)) + { + /* Convert PNG raw data to BMP icon if the icon was PNG icon */ + pbBmpIcon = CURSORICON_ConvertPngToBmpIcon(pbIconBits, cbIconBits, &BmpIconSize); + if (!pbBmpIcon) + return NULL; /* Not PNG icon or failed */ + + /* Find icon entry from BMP icon */ + CURSORICONFILEDIR *dir = (CURSORICONFILEDIR *)pbBmpIcon; + CURSORICONFILEDIRENTRY *entry = &dir->idEntries[0]; /* Only one entry */ + + /* A bit of preparation */ + RtlZeroMemory(&cursorData, sizeof(cursorData)); + cursorData.rt = LOWORD(fIcon ? RT_ICON : RT_CURSOR); + + /* Can we load this BMP icon? */ + if (!CURSORICON_GetCursorDataFromBMI(&cursorData, (PBITMAPINFO)&pbBmpIcon[entry->dwDIBOffset])) + { + ERR("Couldn't get cursor/icon data\n"); + goto end_error; + } + } } if (uFlags & LR_SHARED) @@ -2651,15 +2926,17 @@ HICON WINAPI CreateIconFromResourceEx( if(isAnimated) HeapFree(GetProcessHeap(), 0, cursorData.aspcur); + HeapFree(GetProcessHeap(), 0, pbBmpIcon); return hIcon; /* Clean up */ end_error: + HeapFree(GetProcessHeap(), 0, pbBmpIcon); if(isAnimated) HeapFree(GetProcessHeap(), 0, cursorData.aspcur); - DeleteObject(cursorData.hbmMask); - if(cursorData.hbmColor) DeleteObject(cursorData.hbmColor); - if(cursorData.hbmAlpha) DeleteObject(cursorData.hbmAlpha); + if (cursorData.hbmMask) DeleteObject(cursorData.hbmMask); + if (cursorData.hbmColor) DeleteObject(cursorData.hbmColor); + if (cursorData.hbmAlpha) DeleteObject(cursorData.hbmAlpha); return NULL; }