https://git.reactos.org/?p=reactos.git;a=commitdiff;h=329a41458416f633fb6ec016dc434b063cd92b9b

commit 329a41458416f633fb6ec016dc434b063cd92b9b
Author:     Whindmar Saksit <whinds...@proton.me>
AuthorDate: Wed Feb 12 20:57:51 2025 +0100
Commit:     GitHub <nore...@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 <whinds...@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 (dmi...@reactos.org)
  *              Copyright 2018-2023 Katayama Hirofumi MZ 
(katayama.hirofumi...@gmail.com)
+ *              Copyright 2025 Whindmar Saksit <whinds...@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;
+}

Reply via email to