如何通过WINAPI获取带覆盖层标识的jumbo超大尺寸文件图标
我需要获取带覆盖层(比如快捷方式的链接图标)的超大"jumbo"文件图标(256x256 px),效果和Windows文件资源管理器中设置为超大图标查看时的显示完全一致。
例如我有一个指向文本文件的快捷方式"a.txt.lnk",它在资源管理器中的图标如下(左下角的箭头就是我所说的覆盖层):
我经过大量搜索尝试寻找实现方案,使用Qt框架源码中QtWinExtras库的iconextractor示例代码做测试,但是该示例存在问题无法正常运行:目前我只能获取到带快捷方式覆盖层的小尺寸(32x32 px)图标,或者不带覆盖层的"jumbo"尺寸图标,效果如下:
或者:
我将Qt示例代码简化后得到如下main.cpp:
#include <QtWin> #include <QDebug> #include <QDir> #include <QFileInfo> #include <QGuiApplication> #include <QImage> #include <QPixmap> #include <comdef.h> #include <CommCtrl.h> #include <commoncontrols.h> struct PixmapEntry { QString name; QPixmap pixmap; }; using PixmapEntryList = QVector<PixmapEntry>; static QString formatSize(const QSize &size) { return QString::number(size.width()) + u'x' + QString::number(size.height()); } static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info) { QPixmap result; static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}}; IImageList *imageList = nullptr; if (FAILED(SHGetImageList(iImageList, iID_IImageList, reinterpret_cast<void **>(&imageList)))) return result; HICON hIcon = nullptr; // NOTE: the following was missing in original Qt code // and therefore returned no jumbo icons at all when // overlays were requested. // Highest 8 bits are reverved for overlay index. int iconIndex = info.iIcon & 0xFFFFFF; if (SUCCEEDED(imageList->GetIcon(iconIndex, ILD_TRANSPARENT, &hIcon))) { result = QtWin::fromHICON(hIcon); DestroyIcon(hIcon); } return result; } static PixmapEntryList extractShellIcons(const QString &sourceFile, bool addOverlays) { enum { // Shell image list ids sHIL_EXTRALARGE = 0x2, // 48x48 or user-defined sHIL_JUMBO = 0x4 // 256x256 (Vista or later) }; struct FlagEntry { QString name; unsigned flags; }; const QString nativeName = QDir::toNativeSeparators(sourceFile); const auto *sourceFileC = reinterpret_cast<const wchar_t *>(nativeName.utf16()); SHFILEINFO info; unsigned int flags = SHGFI_ICON | SHGFI_SYSICONINDEX | SHGFI_SHELLICONSIZE; if (addOverlays) flags |= SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX; // What do these flags do? How to utilize the overlay index? PixmapEntryList result; const QString prefix = "sh_"; ZeroMemory(&info, sizeof(SHFILEINFO)); SHGetFileInfo(sourceFileC, 0, &info, sizeof(SHFILEINFO), flags); // extract standard icon PixmapEntry entry; entry.pixmap = QtWin::fromHICON(info.hIcon); DestroyIcon(info.hIcon); if (entry.pixmap.isNull()) { qWarning() << "Error converting icons."; return PixmapEntryList(); } entry.name = prefix + formatSize(entry.pixmap.size()); const int iconIndex = info.iIcon & 0xFFFFFF; const int overlayIconIndex = info.iIcon >> 24; // Note: some resources suggest there should be -1 to adjust for 0-based index. Really? qDebug() << "Obtained icon #" << iconIndex << "overlay #" << overlayIconIndex; result.append(entry); // extract jumbo icon const QPixmap jumbo = pixmapFromShellImageList(sHIL_JUMBO, info); if (!jumbo.isNull()) { PixmapEntry entry; entry.pixmap = jumbo; entry.name = QLatin1String("jumbo_") + formatSize(jumbo.size()); result.append(entry); } return result; } int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); const QString &sourceFile = "C:/my/path/to/some/link/a.txt.lnk"; QString imageFileRoot = QDir::currentPath() + u'/'; // Note: Settings true or false in the following line has no effect of overlays. // Small icons have always overlays, jumbo icons have never overlays. const PixmapEntryList pixmaps = extractShellIcons(sourceFile, true); for (const auto &entry : pixmaps) { const QString fileName = imageFileRoot + entry.name + QLatin1String(".png"); entry.pixmap.save(fileName); qDebug() << "Wrote " << QDir::toNativeSeparators(fileName); } return 0; }
iconextractor.pro配置文件内容如下:
TEMPLATE = app TARGET = iconextractor CONFIG += console QT = core gui winextras LIBS += -lshell32 -luser32 SOURCES += main.cpp
我的问题是:我不理解flags |= SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;这行代码的作用,它对标准尺寸图标没有影响(不加这行也自带覆盖层),对jumbo尺寸图标也没有影响(无论加不加都没有覆盖层)。我可以获取到图标覆盖层的索引,但不知道该怎么使用这个索引,请问如何才能获取到带覆盖层的jumbo尺寸图标?
两个flag的作用说明
SHGFI_ADDOVERLAYS:仅作用于SHGetFileInfo直接返回的HICON对象,告知系统将对应覆盖层预先绘制到返回的小尺寸图标上。你不加该flag也能拿到带覆盖层小图标的原因是:系统默认会对返回的普通尺寸图标自动添加常用覆盖层。SHGFI_OVERLAYINDEX:告知系统将覆盖层的1-based索引存储到SHFILEINFO结构体的iIcon字段的高8位中,你代码中通过info.iIcon >> 24拿到的就是这个索引值,只有加了这个flag才能拿到准确的覆盖层索引用于大尺寸图标的手动绘制。
无法获取带覆盖层jumbo图标的原因
系统的jumbo尺寸图标列表中仅存储无覆盖层的原始图标,覆盖层是独立的资源,不会自动合并到jumbo图标中,需要你手动利用拿到的覆盖层索引完成绘制。
具体代码修改方案
仅需要对现有代码做3处小修改即可实现需求:
- 修改
pixmapFromShellImageList函数的签名,新增接收覆盖层索引的参数:
// 新增overlayIndex参数,默认值为0表示无覆盖层 static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info, int overlayIndex = 0)
- 修改
imageList->GetIcon的调用参数,用INDEXTOOVERLAYMASK宏将覆盖层索引合并到绘制参数中:
// 原来的参数是ILD_TRANSPARENT,新增覆盖层mask if (SUCCEEDED(imageList->GetIcon(iconIndex, ILD_TRANSPARENT | INDEXTOOVERLAYMASK(overlayIndex), &hIcon))) {
- 在调用
pixmapFromShellImageList获取jumbo图标时,传入之前拿到的覆盖层索引:
// 原来的调用没有传overlayIconIndex,现在加上 const QPixmap jumbo = pixmapFromShellImageList(sHIL_JUMBO, info, overlayIconIndex);
修改完成后重新编译运行,就能拿到带完整覆盖层的256x256 jumbo尺寸图标,和资源管理器显示效果完全一致。
内容的提问来源于stack exchange,提问作者HiFile.app - best file manager

