Skip to content

Commit 329a414

Browse files
authored
[SHIMGVW] Choose a better icon image and also support .cur files (reactos#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
1 parent 166d83b commit 329a414

File tree

4 files changed

+350
-58
lines changed

4 files changed

+350
-58
lines changed

dll/win32/shimgvw/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ spec2def(shimgvw.dll shimgvw.spec)
33

44
list(APPEND SOURCE
55
anime.c
6+
loader.cpp
67
shimgvw.c
78
comsup.c
89
shimgvw.rc

dll/win32/shimgvw/loader.cpp

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
/*
2+
* PROJECT: ReactOS Picture and Fax Viewer
3+
* LICENSE: GPL-2.0 (https://spdx.org/licenses/GPL-2.0)
4+
* PURPOSE: Image file browsing and manipulation
5+
* COPYRIGHT: Copyright 2025 Whindmar Saksit <[email protected]>
6+
*/
7+
8+
#include <windows.h>
9+
#include <objbase.h>
10+
#include <gdiplus.h>
11+
using namespace Gdiplus;
12+
#include "shimgvw.h"
13+
14+
#define HResultFromWin32 SHIMGVW_HResultFromWin32
15+
16+
static HRESULT Read(HANDLE hFile, void* Buffer, DWORD Size)
17+
{
18+
DWORD Transferred;
19+
if (!ReadFile(hFile, Buffer, Size, &Transferred, NULL))
20+
return HResultFromWin32(GetLastError());
21+
return Size == Transferred ? S_OK : HResultFromWin32(ERROR_HANDLE_EOF);
22+
}
23+
24+
struct IMAGEINFO
25+
{
26+
UINT w, h;
27+
BYTE bpp;
28+
};
29+
30+
class BitmapInfoHeader : public BITMAPINFOHEADER
31+
{
32+
public:
33+
BitmapInfoHeader() {}
34+
BitmapInfoHeader(const void* pbmiHeader) { Initialize(pbmiHeader); }
35+
36+
void Initialize(const void* pbmiHeader)
37+
{
38+
BITMAPINFOHEADER& bih = *(BITMAPINFOHEADER*)pbmiHeader;
39+
if (bih.biSize >= sizeof(BITMAPINFOHEADER))
40+
{
41+
CopyMemory(this, &bih, min(bih.biSize, sizeof(*this)));
42+
}
43+
else
44+
{
45+
ZeroMemory(this, sizeof(*this));
46+
BITMAPCOREHEADER& bch = *(BITMAPCOREHEADER*)pbmiHeader;
47+
if (bih.biSize >= sizeof(BITMAPCOREHEADER))
48+
{
49+
biSize = bch.bcSize;
50+
biWidth = bch.bcWidth;
51+
biHeight = bch.bcHeight;
52+
biPlanes = bch.bcPlanes;
53+
biBitCount = bch.bcBitCount;
54+
biCompression = BI_RGB;
55+
}
56+
}
57+
}
58+
};
59+
60+
#include <pshpack1.h>
61+
union PNGSIGNATURE { UINT64 number; BYTE bytes[8]; };
62+
struct PNGCHUNKHEADER { UINT length, type; };
63+
struct PNGCHUNKFOOTER { UINT crc; };
64+
struct PNGIHDR { UINT w, h; BYTE depth, type, compression, filter, interlace; };
65+
struct PNGSIGANDIHDR
66+
{
67+
PNGSIGNATURE sig;
68+
PNGCHUNKHEADER chunkheader;
69+
PNGIHDR ihdr;
70+
PNGCHUNKFOOTER chunkfooter;
71+
};
72+
struct PNGFOOTER { PNGCHUNKHEADER chunkheader; PNGCHUNKFOOTER footer; };
73+
#include <poppack.h>
74+
75+
static inline bool IsPngSignature(const void* buffer)
76+
{
77+
const BYTE* p = (BYTE*)buffer;
78+
return p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G' &&
79+
p[4] == 0x0D && p[5] == 0x0A && p[6] == 0x1A && p[7] == 0x0A;
80+
}
81+
82+
static inline bool IsPngSignature(const void* buffer, SIZE_T size)
83+
{
84+
return size >= sizeof(PNGSIGNATURE) && IsPngSignature(buffer);
85+
}
86+
87+
static BYTE GetPngBppFromIHDRData(const void* buffer)
88+
{
89+
static const BYTE channels[] = { 1, 0, 3, 1, 2, 0, 4 };
90+
const BYTE* p = (BYTE*)buffer, depth = p[8], type = p[8 + 1];
91+
return (depth <= 16 && type <= 6) ? channels[type] * depth : 0;
92+
}
93+
94+
static bool GetInfoFromPng(const void* file, SIZE_T size, IMAGEINFO& info)
95+
{
96+
C_ASSERT(sizeof(PNGSIGNATURE) == 8);
97+
C_ASSERT(sizeof(PNGSIGANDIHDR) == 8 + (4 + 4 + (4 + 4 + 5) + 4));
98+
99+
if (size > sizeof(PNGSIGANDIHDR) + sizeof(PNGFOOTER) && IsPngSignature(file))
100+
{
101+
const UINT PNGIHDRSIG = 0x52444849; // Note: Big endian
102+
const UINT* chunkhdr = (UINT*)((char*)file + sizeof(PNGSIGNATURE));
103+
if (BigToHost32(chunkhdr[0]) >= sizeof(PNGIHDR) && chunkhdr[1] == PNGIHDRSIG)
104+
{
105+
info.w = BigToHost32(chunkhdr[2]);
106+
info.h = BigToHost32(chunkhdr[3]);
107+
info.bpp = GetPngBppFromIHDRData(&chunkhdr[2]);
108+
return info.bpp != 0;
109+
}
110+
}
111+
return false;
112+
}
113+
114+
static bool GetInfoFromBmp(const void* pBitmapInfo, IMAGEINFO& info)
115+
{
116+
BitmapInfoHeader bih(pBitmapInfo);
117+
info.w = bih.biWidth;
118+
info.h = abs((int)bih.biHeight);
119+
UINT bpp = bih.biBitCount * bih.biPlanes;
120+
info.bpp = LOBYTE(bpp);
121+
return info.w && bpp == info.bpp;
122+
}
123+
124+
static bool GetInfoFromIcoBmp(const void* pBitmapInfo, IMAGEINFO& stat)
125+
{
126+
bool ret = GetInfoFromBmp(pBitmapInfo, stat);
127+
stat.h /= 2; // Don't include mask
128+
return ret && stat.h;
129+
}
130+
131+
EXTERN_C PCWSTR GetExtraExtensionsGdipList(VOID)
132+
{
133+
return L"*.CUR"; // "*.FOO;*.BAR" etc.
134+
}
135+
136+
static void OverrideFileContent(HGLOBAL& hMem, DWORD& Size)
137+
{
138+
PBYTE buffer = (PBYTE)GlobalLock(hMem);
139+
if (!buffer)
140+
return;
141+
142+
// TODO: We could try to load an ICO/PNG/BMP resource from a PE file here into buffer
143+
144+
// ICO/CUR
145+
struct ICOHDR { WORD Sig, Type, Count; };
146+
ICOHDR* pIcoHdr = (ICOHDR*)buffer;
147+
if (Size > sizeof(ICOHDR) && !pIcoHdr->Sig && pIcoHdr->Type > 0 && pIcoHdr->Type < 3 && pIcoHdr->Count)
148+
{
149+
const UINT minbmp = sizeof(BITMAPCOREHEADER) + 1, minpng = sizeof(PNGSIGANDIHDR);
150+
const UINT minfile = min(minbmp, minpng), count = pIcoHdr->Count;
151+
struct ICOENTRY { BYTE w, h, pal, null; WORD planes, bpp; UINT size, offset; };
152+
ICOENTRY* entries = (ICOENTRY*)&pIcoHdr[1];
153+
if (Size - sizeof(ICOHDR) > (sizeof(ICOENTRY) + minfile) * count)
154+
{
155+
UINT64 best = 0;
156+
int bestindex = -1;
157+
// Inspect all the images and find the "best" image
158+
for (UINT i = 0; i < count; ++i)
159+
{
160+
BOOL valid = FALSE;
161+
IMAGEINFO info;
162+
const BYTE* data = buffer + entries[i].offset;
163+
if (IsPngSignature(data, entries[i].size))
164+
valid = GetInfoFromPng(data, entries[i].size, info);
165+
else
166+
valid = GetInfoFromIcoBmp(data, info);
167+
168+
if (valid)
169+
{
170+
// Note: This treats bpp as more important compared to LookupIconIdFromDirectoryEx
171+
UINT64 score = UINT64(info.w) * info.h * info.bpp;
172+
if (score > best)
173+
{
174+
best = score;
175+
bestindex = i;
176+
}
177+
}
178+
}
179+
if (bestindex >= 0)
180+
{
181+
if (pIcoHdr->Type == 2)
182+
{
183+
// GDI+ does not support .cur files, convert to .ico
184+
pIcoHdr->Type = 1;
185+
#if 0 // Because we are already overriding the order, we don't need to correct the ICOENTRY lookup info
186+
for (UINT i = 0; i < count; ++i)
187+
{
188+
BitmapInfoHeader bih;
189+
const BYTE* data = buffer + entries[i].offset;
190+
if (IsPngSignature(data, entries[i].size))
191+
{
192+
IMAGEINFO info;
193+
if (!GetInfoFromPng(data, entries[i].size, info))
194+
continue;
195+
bih.biPlanes = 1;
196+
bih.biBitCount = info.bpp;
197+
entries[i].pal = 0;
198+
}
199+
else
200+
{
201+
bih.Initialize(data);
202+
entries[i].pal = bih.biPlanes * bih.biBitCount <= 8 ? bih.biClrUsed : 0;
203+
}
204+
entries[i].planes = (WORD)bih.biPlanes;
205+
entries[i].bpp = (WORD)bih.biBitCount;
206+
}
207+
#endif
208+
}
209+
#if 0
210+
// Convert to a .ico with a single image
211+
pIcoHdr->Count = 1;
212+
const BYTE* data = buffer + entries[bestindex].offset;
213+
entries[0] = entries[bestindex];
214+
entries[0].offset = (UINT)UINT_PTR((PBYTE)&entries[1] - buffer);
215+
MoveMemory(buffer + entries[0].offset, data, entries[0].size);
216+
Size = entries[0].offset + entries[0].size;
217+
#else
218+
// Place the best image first, GDI+ will return the first image
219+
ICOENTRY temp = entries[0];
220+
entries[0] = entries[bestindex];
221+
entries[bestindex] = temp;
222+
#endif
223+
}
224+
}
225+
}
226+
227+
GlobalUnlock(hMem);
228+
}
229+
230+
static HRESULT LoadImageFromStream(IStream* pStream, GpImage** ppImage)
231+
{
232+
Status status = DllExports::GdipLoadImageFromStream(pStream, ppImage);
233+
return HResultFromGdiplus(status);
234+
}
235+
236+
static HRESULT LoadImageFromFileHandle(HANDLE hFile, GpImage** ppImage)
237+
{
238+
DWORD size = GetFileSize(hFile, NULL);
239+
if (!size || size == INVALID_FILE_SIZE)
240+
return HResultFromWin32(ERROR_NOT_SUPPORTED);
241+
242+
HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, size);
243+
if (!hMem)
244+
return HResultFromWin32(ERROR_OUTOFMEMORY);
245+
HRESULT hr = E_FAIL;
246+
void* buffer = GlobalLock(hMem);
247+
if (buffer)
248+
{
249+
hr = Read(hFile, buffer, size);
250+
GlobalUnlock(hMem);
251+
if (SUCCEEDED(hr))
252+
{
253+
OverrideFileContent(hMem, size);
254+
IStream* pStream;
255+
if (SUCCEEDED(hr = CreateStreamOnHGlobal(hMem, TRUE, &pStream)))
256+
{
257+
// CreateStreamOnHGlobal does not know the real size, we do
258+
pStream->SetSize(MakeULargeInteger(size));
259+
hr = LoadImageFromStream(pStream, ppImage);
260+
pStream->Release(); // Calls GlobalFree
261+
return hr;
262+
}
263+
}
264+
}
265+
GlobalFree(hMem);
266+
return hr;
267+
}
268+
269+
EXTERN_C HRESULT LoadImageFromPath(LPCWSTR Path, GpImage** ppImage)
270+
{
271+
// NOTE: GdipLoadImageFromFile locks the file.
272+
// Avoid file locking by using GdipLoadImageFromStream and memory stream.
273+
274+
HANDLE hFile = CreateFileW(Path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE,
275+
NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
276+
if (hFile != INVALID_HANDLE_VALUE)
277+
{
278+
HRESULT hr = LoadImageFromFileHandle(hFile, ppImage);
279+
CloseHandle(hFile);
280+
return hr;
281+
}
282+
return HResultFromWin32(GetLastError());
283+
}

0 commit comments

Comments
 (0)