https://git.reactos.org/?p=reactos.git;a=commitdiff;h=329a41458416f633fb6ec…
commit 329a41458416f633fb6ec016dc434b063cd92b9b
Author: Whindmar Saksit <whindsaks(a)proton.me>
AuthorDate: Wed Feb 12 20:57:51 2025 +0100
Commit: GitHub <noreply(a)github.com>
CommitDate: Wed Feb 12 20:57:51 2025 +0100
[SHIMGVW] Choose a better icon image and also support .cur files (#7706)
- Choose an icon image based on size and color and place it first so GDI+ will pick
it
- Change .cur files to .ico so GDI+ can open it
CORE-19945
---
dll/win32/shimgvw/CMakeLists.txt | 1 +
dll/win32/shimgvw/loader.cpp | 283 +++++++++++++++++++++++++++++++++++++++
dll/win32/shimgvw/shimgvw.c | 81 ++++-------
dll/win32/shimgvw/shimgvw.h | 43 ++++++
4 files changed, 350 insertions(+), 58 deletions(-)
diff --git a/dll/win32/shimgvw/CMakeLists.txt b/dll/win32/shimgvw/CMakeLists.txt
index 1a4589abd43..15983aff150 100644
--- a/dll/win32/shimgvw/CMakeLists.txt
+++ b/dll/win32/shimgvw/CMakeLists.txt
@@ -3,6 +3,7 @@ spec2def(shimgvw.dll shimgvw.spec)
list(APPEND SOURCE
anime.c
+ loader.cpp
shimgvw.c
comsup.c
shimgvw.rc
diff --git a/dll/win32/shimgvw/loader.cpp b/dll/win32/shimgvw/loader.cpp
new file mode 100644
index 00000000000..50112f6c486
--- /dev/null
+++ b/dll/win32/shimgvw/loader.cpp
@@ -0,0 +1,283 @@
+/*
+ * PROJECT: ReactOS Picture and Fax Viewer
+ * LICENSE: GPL-2.0 (
https://spdx.org/licenses/GPL-2.0)
+ * PURPOSE: Image file browsing and manipulation
+ * COPYRIGHT: Copyright 2025 Whindmar Saksit <whindsaks(a)proton.me>
+ */
+
+#include <windows.h>
+#include <objbase.h>
+#include <gdiplus.h>
+using namespace Gdiplus;
+#include "shimgvw.h"
+
+#define HResultFromWin32 SHIMGVW_HResultFromWin32
+
+static HRESULT Read(HANDLE hFile, void* Buffer, DWORD Size)
+{
+ DWORD Transferred;
+ if (!ReadFile(hFile, Buffer, Size, &Transferred, NULL))
+ return HResultFromWin32(GetLastError());
+ return Size == Transferred ? S_OK : HResultFromWin32(ERROR_HANDLE_EOF);
+}
+
+struct IMAGEINFO
+{
+ UINT w, h;
+ BYTE bpp;
+};
+
+class BitmapInfoHeader : public BITMAPINFOHEADER
+{
+public:
+ BitmapInfoHeader() {}
+ BitmapInfoHeader(const void* pbmiHeader) { Initialize(pbmiHeader); }
+
+ void Initialize(const void* pbmiHeader)
+ {
+ BITMAPINFOHEADER& bih = *(BITMAPINFOHEADER*)pbmiHeader;
+ if (bih.biSize >= sizeof(BITMAPINFOHEADER))
+ {
+ CopyMemory(this, &bih, min(bih.biSize, sizeof(*this)));
+ }
+ else
+ {
+ ZeroMemory(this, sizeof(*this));
+ BITMAPCOREHEADER& bch = *(BITMAPCOREHEADER*)pbmiHeader;
+ if (bih.biSize >= sizeof(BITMAPCOREHEADER))
+ {
+ biSize = bch.bcSize;
+ biWidth = bch.bcWidth;
+ biHeight = bch.bcHeight;
+ biPlanes = bch.bcPlanes;
+ biBitCount = bch.bcBitCount;
+ biCompression = BI_RGB;
+ }
+ }
+ }
+};
+
+#include <pshpack1.h>
+union PNGSIGNATURE { UINT64 number; BYTE bytes[8]; };
+struct PNGCHUNKHEADER { UINT length, type; };
+struct PNGCHUNKFOOTER { UINT crc; };
+struct PNGIHDR { UINT w, h; BYTE depth, type, compression, filter, interlace; };
+struct PNGSIGANDIHDR
+{
+ PNGSIGNATURE sig;
+ PNGCHUNKHEADER chunkheader;
+ PNGIHDR ihdr;
+ PNGCHUNKFOOTER chunkfooter;
+};
+struct PNGFOOTER { PNGCHUNKHEADER chunkheader; PNGCHUNKFOOTER footer; };
+#include <poppack.h>
+
+static inline bool IsPngSignature(const void* buffer)
+{
+ const BYTE* p = (BYTE*)buffer;
+ return p[0] == 0x89 && p[1] == 'P' && p[2] == 'N'
&& p[3] == 'G' &&
+ p[4] == 0x0D && p[5] == 0x0A && p[6] == 0x1A && p[7]
== 0x0A;
+}
+
+static inline bool IsPngSignature(const void* buffer, SIZE_T size)
+{
+ return size >= sizeof(PNGSIGNATURE) && IsPngSignature(buffer);
+}
+
+static BYTE GetPngBppFromIHDRData(const void* buffer)
+{
+ static const BYTE channels[] = { 1, 0, 3, 1, 2, 0, 4 };
+ const BYTE* p = (BYTE*)buffer, depth = p[8], type = p[8 + 1];
+ return (depth <= 16 && type <= 6) ? channels[type] * depth : 0;
+}
+
+static bool GetInfoFromPng(const void* file, SIZE_T size, IMAGEINFO& info)
+{
+ C_ASSERT(sizeof(PNGSIGNATURE) == 8);
+ C_ASSERT(sizeof(PNGSIGANDIHDR) == 8 + (4 + 4 + (4 + 4 + 5) + 4));
+
+ if (size > sizeof(PNGSIGANDIHDR) + sizeof(PNGFOOTER) &&
IsPngSignature(file))
+ {
+ const UINT PNGIHDRSIG = 0x52444849; // Note: Big endian
+ const UINT* chunkhdr = (UINT*)((char*)file + sizeof(PNGSIGNATURE));
+ if (BigToHost32(chunkhdr[0]) >= sizeof(PNGIHDR) && chunkhdr[1] ==
PNGIHDRSIG)
+ {
+ info.w = BigToHost32(chunkhdr[2]);
+ info.h = BigToHost32(chunkhdr[3]);
+ info.bpp = GetPngBppFromIHDRData(&chunkhdr[2]);
+ return info.bpp != 0;
+ }
+ }
+ return false;
+}
+
+static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGEINFO& info)
+{
+ BitmapInfoHeader bih(pBitmapInfo);
+ info.w = bih.biWidth;
+ info.h = abs((int)bih.biHeight);
+ UINT bpp = bih.biBitCount * bih.biPlanes;
+ info.bpp = LOBYTE(bpp);
+ return info.w && bpp == info.bpp;
+}
+
+static bool GetInfoFromIcoBmp(const void* pBitmapInfo, IMAGEINFO& stat)
+{
+ bool ret = GetInfoFromBmp(pBitmapInfo, stat);
+ stat.h /= 2; // Don't include mask
+ return ret && stat.h;
+}
+
+EXTERN_C PCWSTR GetExtraExtensionsGdipList(VOID)
+{
+ return L"*.CUR"; // "*.FOO;*.BAR" etc.
+}
+
+static void OverrideFileContent(HGLOBAL& hMem, DWORD& Size)
+{
+ PBYTE buffer = (PBYTE)GlobalLock(hMem);
+ if (!buffer)
+ return;
+
+ // TODO: We could try to load an ICO/PNG/BMP resource from a PE file here into
buffer
+
+ // ICO/CUR
+ struct ICOHDR { WORD Sig, Type, Count; };
+ ICOHDR* pIcoHdr = (ICOHDR*)buffer;
+ if (Size > sizeof(ICOHDR) && !pIcoHdr->Sig && pIcoHdr->Type
> 0 && pIcoHdr->Type < 3 && pIcoHdr->Count)
+ {
+ const UINT minbmp = sizeof(BITMAPCOREHEADER) + 1, minpng =
sizeof(PNGSIGANDIHDR);
+ const UINT minfile = min(minbmp, minpng), count = pIcoHdr->Count;
+ struct ICOENTRY { BYTE w, h, pal, null; WORD planes, bpp; UINT size, offset; };
+ ICOENTRY* entries = (ICOENTRY*)&pIcoHdr[1];
+ if (Size - sizeof(ICOHDR) > (sizeof(ICOENTRY) + minfile) * count)
+ {
+ UINT64 best = 0;
+ int bestindex = -1;
+ // Inspect all the images and find the "best" image
+ for (UINT i = 0; i < count; ++i)
+ {
+ BOOL valid = FALSE;
+ IMAGEINFO info;
+ const BYTE* data = buffer + entries[i].offset;
+ if (IsPngSignature(data, entries[i].size))
+ valid = GetInfoFromPng(data, entries[i].size, info);
+ else
+ valid = GetInfoFromIcoBmp(data, info);
+
+ if (valid)
+ {
+ // Note: This treats bpp as more important compared to
LookupIconIdFromDirectoryEx
+ UINT64 score = UINT64(info.w) * info.h * info.bpp;
+ if (score > best)
+ {
+ best = score;
+ bestindex = i;
+ }
+ }
+ }
+ if (bestindex >= 0)
+ {
+ if (pIcoHdr->Type == 2)
+ {
+ // GDI+ does not support .cur files, convert to .ico
+ pIcoHdr->Type = 1;
+#if 0 // Because we are already overriding the order, we don't need to
correct the ICOENTRY lookup info
+ for (UINT i = 0; i < count; ++i)
+ {
+ BitmapInfoHeader bih;
+ const BYTE* data = buffer + entries[i].offset;
+ if (IsPngSignature(data, entries[i].size))
+ {
+ IMAGEINFO info;
+ if (!GetInfoFromPng(data, entries[i].size, info))
+ continue;
+ bih.biPlanes = 1;
+ bih.biBitCount = info.bpp;
+ entries[i].pal = 0;
+ }
+ else
+ {
+ bih.Initialize(data);
+ entries[i].pal = bih.biPlanes * bih.biBitCount <= 8 ?
bih.biClrUsed : 0;
+ }
+ entries[i].planes = (WORD)bih.biPlanes;
+ entries[i].bpp = (WORD)bih.biBitCount;
+ }
+#endif
+ }
+#if 0
+ // Convert to a .ico with a single image
+ pIcoHdr->Count = 1;
+ const BYTE* data = buffer + entries[bestindex].offset;
+ entries[0] = entries[bestindex];
+ entries[0].offset = (UINT)UINT_PTR((PBYTE)&entries[1] - buffer);
+ MoveMemory(buffer + entries[0].offset, data, entries[0].size);
+ Size = entries[0].offset + entries[0].size;
+#else
+ // Place the best image first, GDI+ will return the first image
+ ICOENTRY temp = entries[0];
+ entries[0] = entries[bestindex];
+ entries[bestindex] = temp;
+#endif
+ }
+ }
+ }
+
+ GlobalUnlock(hMem);
+}
+
+static HRESULT LoadImageFromStream(IStream* pStream, GpImage** ppImage)
+{
+ Status status = DllExports::GdipLoadImageFromStream(pStream, ppImage);
+ return HResultFromGdiplus(status);
+}
+
+static HRESULT LoadImageFromFileHandle(HANDLE hFile, GpImage** ppImage)
+{
+ DWORD size = GetFileSize(hFile, NULL);
+ if (!size || size == INVALID_FILE_SIZE)
+ return HResultFromWin32(ERROR_NOT_SUPPORTED);
+
+ HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size);
+ if (!hMem)
+ return HResultFromWin32(ERROR_OUTOFMEMORY);
+ HRESULT hr = E_FAIL;
+ void* buffer = GlobalLock(hMem);
+ if (buffer)
+ {
+ hr = Read(hFile, buffer, size);
+ GlobalUnlock(hMem);
+ if (SUCCEEDED(hr))
+ {
+ OverrideFileContent(hMem, size);
+ IStream* pStream;
+ if (SUCCEEDED(hr = CreateStreamOnHGlobal(hMem, TRUE, &pStream)))
+ {
+ // CreateStreamOnHGlobal does not know the real size, we do
+ pStream->SetSize(MakeULargeInteger(size));
+ hr = LoadImageFromStream(pStream, ppImage);
+ pStream->Release(); // Calls GlobalFree
+ return hr;
+ }
+ }
+ }
+ GlobalFree(hMem);
+ return hr;
+}
+
+EXTERN_C HRESULT LoadImageFromPath(LPCWSTR Path, GpImage** ppImage)
+{
+ // NOTE: GdipLoadImageFromFile locks the file.
+ // Avoid file locking by using GdipLoadImageFromStream and memory stream.
+
+ HANDLE hFile = CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ HRESULT hr = LoadImageFromFileHandle(hFile, ppImage);
+ CloseHandle(hFile);
+ return hr;
+ }
+ return HResultFromWin32(GetLastError());
+}
diff --git a/dll/win32/shimgvw/shimgvw.c b/dll/win32/shimgvw/shimgvw.c
index c0ce4de2d39..7284266b94a 100644
--- a/dll/win32/shimgvw/shimgvw.c
+++ b/dll/win32/shimgvw/shimgvw.c
@@ -4,6 +4,7 @@
* PURPOSE: Image file browsing and manipulation
* COPYRIGHT: Copyright Dmitry Chapyshev (dmitry(a)reactos.org)
* Copyright 2018-2023 Katayama Hirofumi MZ
(katayama.hirofumi.mz(a)gmail.com)
+ * Copyright 2025 Whindmar Saksit <whindsaks(a)proton.me>
*/
#include "shimgvw.h"
@@ -13,6 +14,9 @@
#include <shlobj.h>
#include <shellapi.h>
+EXTERN_C PCWSTR GetExtraExtensionsGdipList(VOID);
+EXTERN_C HRESULT LoadImageFromPath(LPCWSTR Path, GpImage** ppImage);
+
/* Toolbar image size */
#define TB_IMAGE_WIDTH 16
#define TB_IMAGE_HEIGHT 16
@@ -114,7 +118,6 @@ typedef struct tagPREVIEW_DATA
UINT m_nTimerInterval;
BOOL m_bHideCursor;
POINT m_ptOrigin;
- IStream *m_pMemStream;
WCHAR m_szFile[MAX_PATH];
} PREVIEW_DATA, *PPREVIEW_DATA;
@@ -371,67 +374,20 @@ Preview_pFreeImage(PPREVIEW_DATA pData)
g_pImage = NULL;
}
- if (pData->m_pMemStream)
- {
- pData->m_pMemStream->lpVtbl->Release(pData->m_pMemStream);
- pData->m_pMemStream = NULL;
- }
-
pData->m_szFile[0] = UNICODE_NULL;
}
-IStream* MemStreamFromFile(LPCWSTR pszFileName)
-{
- HANDLE hFile;
- DWORD dwFileSize, dwRead;
- LPBYTE pbMemFile = NULL;
- IStream *pStream;
-
- hFile = CreateFileW(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
- OPEN_EXISTING, 0, NULL);
- if (hFile == INVALID_HANDLE_VALUE)
- return NULL;
-
- dwFileSize = GetFileSize(hFile, NULL);
- pbMemFile = QuickAlloc(dwFileSize, FALSE);
- if (!dwFileSize || (dwFileSize == INVALID_FILE_SIZE) || !pbMemFile)
- {
- CloseHandle(hFile);
- return NULL;
- }
-
- if (!ReadFile(hFile, pbMemFile, dwFileSize, &dwRead, NULL) || (dwRead !=
dwFileSize))
- {
- QuickFree(pbMemFile);
- CloseHandle(hFile);
- return NULL;
- }
-
- CloseHandle(hFile);
- pStream = SHCreateMemStream(pbMemFile, dwFileSize);
- QuickFree(pbMemFile);
- return pStream;
-}
-
static VOID
Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
{
+ HRESULT hr;
Preview_pFreeImage(pData);
+ InvalidateRect(pData->m_hwnd, NULL, FALSE); /* Schedule redraw in case we change
to "No preview" */
- pData->m_pMemStream = MemStreamFromFile(szOpenFileName);
- if (!pData->m_pMemStream)
- {
- DPRINT1("MemStreamFromFile() failed\n");
- Preview_UpdateTitle(pData, NULL);
- return;
- }
-
- /* NOTE: GdipLoadImageFromFile locks the file.
- Avoid file locking by using GdipLoadImageFromStream and memory stream. */
- GdipLoadImageFromStream(pData->m_pMemStream, &g_pImage);
- if (!g_pImage)
+ hr = LoadImageFromPath(szOpenFileName, &g_pImage);
+ if (FAILED(hr))
{
- DPRINT1("GdipLoadImageFromStream() failed\n");
+ DPRINT1("GdipLoadImageFromStream() failed, %d\n", hr);
Preview_pFreeImage(pData);
Preview_UpdateTitle(pData, NULL);
return;
@@ -439,8 +395,8 @@ Preview_pLoadImage(PPREVIEW_DATA pData, LPCWSTR szOpenFileName)
Anime_LoadInfo(&pData->m_Anime);
- SHAddToRecentDocs(SHARD_PATHW, szOpenFileName);
GetFullPathNameW(szOpenFileName, _countof(pData->m_szFile), pData->m_szFile,
NULL);
+ SHAddToRecentDocs(SHARD_PATHW, pData->m_szFile);
/* Reset zoom and redraw display */
Preview_ResetZoom(pData);
@@ -625,7 +581,7 @@ static SHIMGVW_FILENODE*
pBuildFileList(LPCWSTR szFirstFile)
{
HANDLE hFindHandle;
- WCHAR *extension;
+ WCHAR *extension, *buffer;
WCHAR szSearchPath[MAX_PATH];
WCHAR szSearchMask[MAX_PATH];
WCHAR szFileTypes[MAX_PATH];
@@ -634,15 +590,19 @@ pBuildFileList(LPCWSTR szFirstFile)
SHIMGVW_FILENODE *root = NULL;
SHIMGVW_FILENODE *conductor = NULL;
ImageCodecInfo *codecInfo;
- UINT num;
- UINT size;
+ UINT num = 0, size = 0, ExtraSize = 0;
UINT j;
+ const PCWSTR ExtraExtensions = GetExtraExtensionsGdipList();
+ const UINT ExtraCount = ExtraExtensions[0] ? 1 : 0;
+ if (ExtraCount)
+ ExtraSize += sizeof(*codecInfo) + (wcslen(ExtraExtensions) + 1) * sizeof(WCHAR);
+
StringCbCopyW(szSearchPath, sizeof(szSearchPath), szFirstFile);
PathRemoveFileSpecW(szSearchPath);
GdipGetImageDecodersSize(&num, &size);
- codecInfo = QuickAlloc(size, FALSE);
+ codecInfo = QuickAlloc(size + ExtraSize, FALSE);
if (!codecInfo)
{
DPRINT1("QuickAlloc() failed in pLoadFileList()\n");
@@ -650,6 +610,10 @@ pBuildFileList(LPCWSTR szFirstFile)
}
GdipGetImageDecoders(num, size, codecInfo);
+ buffer = (PWSTR)((UINT_PTR)codecInfo + size + (sizeof(*codecInfo) * ExtraCount));
+ if (ExtraCount)
+ codecInfo[num].FilenameExtension = wcscpy(buffer, ExtraExtensions);
+ num += ExtraCount;
root = QuickAlloc(sizeof(SHIMGVW_FILENODE), FALSE);
if (!root)
@@ -663,6 +627,7 @@ pBuildFileList(LPCWSTR szFirstFile)
for (j = 0; j < num; ++j)
{
+ // FIXME: Parse each FilenameExtension list to bypass szFileTypes limit
StringCbCopyW(szFileTypes, sizeof(szFileTypes), codecInfo[j].FilenameExtension);
extension = wcstok(szFileTypes, L";");
diff --git a/dll/win32/shimgvw/shimgvw.h b/dll/win32/shimgvw/shimgvw.h
index 776da7f849c..c17a15bb8f2 100644
--- a/dll/win32/shimgvw/shimgvw.h
+++ b/dll/win32/shimgvw/shimgvw.h
@@ -78,3 +78,46 @@ static inline VOID QuickFree(LPVOID ptr)
{
HeapFree(GetProcessHeap(), 0, ptr);
}
+
+static inline WORD Swap16(WORD v)
+{
+ return MAKEWORD(HIBYTE(v), LOBYTE(v));
+}
+
+static inline UINT Swap32(UINT v)
+{
+ return MAKELONG(Swap16(HIWORD(v)), Swap16(LOWORD(v)));
+}
+
+#ifdef _WIN32
+#define BigToHost32 Swap32
+#endif
+
+static inline ULARGE_INTEGER MakeULargeInteger(UINT64 value)
+{
+ ULARGE_INTEGER ret;
+ ret.QuadPart = value;
+ return ret;
+}
+
+static inline HRESULT SHIMGVW_HResultFromWin32(DWORD hr)
+{
+ // HRESULT_FROM_WIN32 will evaluate its parameter twice, this function will not.
+ return HRESULT_FROM_WIN32(hr);
+}
+
+static inline HRESULT HResultFromGdiplus(Status status)
+{
+ switch ((UINT)status)
+ {
+ case Ok: return S_OK;
+ case InvalidParameter: return E_INVALIDARG;
+ case OutOfMemory: return E_OUTOFMEMORY;
+ case NotImplemented: return HRESULT_FROM_WIN32(ERROR_CALL_NOT_IMPLEMENTED);
+ case Win32Error: return SHIMGVW_HResultFromWin32(GetLastError());
+ case FileNotFound: return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+ case AccessDenied: return HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
+ case UnknownImageFormat: return HRESULT_FROM_WIN32(ERROR_BAD_FORMAT);
+ }
+ return E_FAIL;
+}