Windows 音量混合器图标尺寸过大。

10

在Windows音量混合器中,当您的应用程序播放声音时,它会添加您应用程序的图标和定制的音量滑块,以调整特定应用程序的音量...很棒!但是,当您为应用程序使用大尺寸图标时(特别是在高DPI下,当Windows为任务栏等缩放您的图标时),音量混合器中的图标无法正确缩放。具体来说,以下代码是我用来设置应用程序图标的:

// set icons the normal way
cWnd.SetIcon( theApp.LoadIcon( res_id ), FALSE );
cWnd.SetIcon( theApp.LoadIcon( res_id ), TRUE );

// set hi-res if available
OSVERSIONINFO osv;
osv.dwOSVersionInfoSize = sizeof( osv );
if ( GetVersionEx( &osv ) ) {
    // if we're Vista or more recent, use hi-def icons
    if ( osv.dwMajorVersion >= 6 ) {
        HICON hIcon = (HICON)::LoadImage( theApp.m_hInstance, MAKEINTRESOURCE( res_id ), IMAGE_ICON, 256, 256, LR_SHARED );
        if ( hIcon ) {
            cWnd.SetIcon( hIcon, TRUE );
        }
    }
}

问题在于“如果可用,使用高分辨率”部分。如果包括它,任务栏图标看起来很好,但音量混合器没有缩放并且看起来很糟糕。如果不包括它,任务栏图标看起来不好(缩放很糟糕),但是至少音量混合器的大小是正确的:

Desktop Scaling 125% with 256x256 icon set Desktop Scaling 125% with regular icons

有人找到了使得两个图标都好看的解决方案吗?

编辑:在我的图标文件中,我有以下分辨率:256x256、48x48、32x32、24x24和16x16,全部是32位。256x256的被压缩成PNG格式,其他的是RAW格式。所有尺寸在它们在文件中所在的分辨率下看起来都很棒(我试图将ICO放在这里或imgur中,但显然两者都不允许图标)。此外,我尝试过包含一些8位图像,但那似乎没有改变任何东西。

编辑:我正在使用 GetDeviceCaps( hdc, LOGPIXELSX )(和Y)来确定桌面缩放。通常桌面缩放为100%,我获得正常的96结果。但越来越多的电脑默认为125%。这可以通过右键单击桌面,个性化,其他:显示… 在那里有一个滑块(需要注销/登录才能更改)。

编辑:我还想指出,在高DPI模式下(即使用 Shell_NotifyIcon 时),托盘图标也会遇到类似的缩放问题。在这种情况下,我可以使用 GetDeviceCaps( hdc, LOGPIXELSX ) 来确定Windows想要什么...如果我有大小,直接提供它,否则提供256x256的那个,而Windows会正确地缩放它。

编辑:悲伤降临。这个问题可能是一个Windows问题。在捕获用于演示目的的图像时,我注意到音量混合器图标本身看起来很差。作为比较: Volume Mixer Comparison

最终编辑:如下所述,解决此问题的方法是缩放图标。因此,最终的可行代码是加载指向 Comctl32.dll 中的 LoadIconWithScaleDown 函数的指针(未显示),并在可用时使用它,或退回到“常规/旧”方式:

HICON hIcon = 0;
if ( FAILED( comctl32Loader.LoadIconWithScaleDown( theApp.m_hInstance, MAKEINTRESOURCE( res_id ), GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), &hIcon ) ) ) {
    hIcon = theApp.LoadIcon( res_id );
}
cWnd.SetIcon( hIcon, FALSE );
if ( FAILED( comctl32Loader.LoadIconWithScaleDown( theApp.m_hInstance, MAKEINTRESOURCE( res_id ), GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), &hIcon ) ) ) {
    hIcon = theApp.LoadIcon( res_id );
}
cWnd.SetIcon( hIcon, TRUE );
3个回答

3
是的,我可以重现您描述的问题,当我使用与问题中显示的相同的代码时。正如我们后来确定的那样,该问题仅在高DPI设置下表现出来。在100%缩放(~96 dpi)下,即使在Windows Vista及以后版本中,您只需要为窗口使用“大”图标版本(SM_CXICONSM_CYICON;通常为32x32像素),而不是256x256像素版本。这就是Windows捆绑应用程序所做的,包括您正在测试的音量混音器应用程序。
问题出现在您使用高DPI设置时,这会使“大”尺寸变大:
╔════════════╦═════════════════╦═════════════════╗
║    DPI     ║ Large Icon Size ║ Small Icon Size ║
║            ║   (SM_C?ICON)   ║  (SM_C?SMICON)  ║
╠════════════╬═════════════════╬═════════════════╣
║  96 (100%) ║     32x32       ║     16x16       ║
║ 120 (125%) ║     40x40       ║     20x20       ║
║ 144 (150%) ║     48x48       ║     24x24       ║
║ 192 (200%) ║     64x64       ║     32x32       ║
╚════════════╩═════════════════╩═════════════════╝

无论 DPI 如何,当您加载 256x256 像素的图标时,事情都可以正常工作,因为 Windows 自动将其缩小到所需大小。这会生成比尝试缩放 32x32 像素图标更好质量的图标(没有所有的锯齿和其他伪像)。因此,您的猜测是正确的,问题确实与缩放有关。
我假设在使用 256x256 像素图标时,在“音量混合器”应用程序中看到的是一个错误,它应该将那个大图标缩小到它期望的大小,即“大”图标(SM_C?ICON)。可能,它正在使用 DrawIconEx 函数调用 cxWidthcxHeight 参数均设置为 0 并且未传递 DI_DEFAULTSIZE 标志。这会导致图标使用其实际大小进行绘制 - 巨大的。
你需要手动解决这个问题,通过自己缩放图标。幸运的是,Windows Vista引入了一些专门为此目的设计的函数。在这种情况下,最容易使用的一个是LoadIconWithScaleDown。正如其名称所示,它的工作方式类似于旧版本的LoadIcon/LoadImage函数,但它不是放大太小的图标,而是缩小较大的图标-非常适合当您的ICO文件中有一个巨大的高质量256x256像素图标时。
不幸的是,这些功能在旧版的Windows上不可用,在更高的DPI设置下使用时可能会遇到同样的问题。你需要在那里找到替代方案,或者只能在这些旧操作系统上使用锯齿状的缩放图标。
示例代码:
#include <CommCtrl.h>                  // include Common Controls header
#pragma comment(lib, "comctl32.lib")   // link to Common Controls library

// Embed a standard manifest to use Common Controls v6
#pragma comment(linker, "/manifestdependency:\"type='win32' "                         \
                        "name='Microsoft.Windows.Common-Controls' version='6.0.0.0' " \
                        "processorArchitecture='*' "                                  \
                        "publicKeyToken='6595b64144ccf1df' "                          \
                        "language='*'\"")

// Load and set "large" icon (typically 32x32 pixels, but not necessarily)
HICON hIconLg;
if (SUCCEEDED(LoadIconWithScaleDown(g_hInstance,
                                    MAKEINTRESOURCE(IDI_ICON),
                                    GetSystemMetrics(SM_CXICON),
                                    GetSystemMetrics(SM_CYICON),
                                    &hIconLg)))
{
   SendMessage(hWnd, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIconLg));
}

// Load and set "small" icon (typically 16x16 pixels, but not necessarily)
HICON hIconSm;
if (SUCCEEDED(LoadIconWithScaleDown(g_hInstance,
                                    MAKEINTRESOURCE(IDI_ICON),
                                    GetSystemMetrics(SM_CXSMICON),
                                    GetSystemMetrics(SM_CYSMICON),
                                    &hIconSm)))
{
   SendMessage(hWnd, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIconSm));
}

请注意,要使用这些新功能,您需要链接到通用控件库的版本6。这需要您指示编译器链接 comctl32.lib 并在您的应用程序中嵌入清单。以上示例代码中显示了 MSVC 特定的 #pragmas 可以完成其中任意一个操作,或者可以在项目属性中进行配置。如果您未执行其中任何一项操作,则在首次启动应用程序时将出现链接时间错误或 "未找到序数" 错误。

哇,非常感谢您提供的所有细节和分析。在我的ICO文件中,所有分辨率在我提供的分辨率下看起来都很好(请参见上面的编辑)。我的底部示例“jaggy”图像确实被Windows缩放了(我的ICO文件中没有这样的ICO)。当处于更高DPI模式时,Windows会这样做。针对您的观点,在普通桌面缩放(100%)中进行GetDeviceCaps时,我获得96...但是如果我将桌面设置为125%缩放或200%缩放(右键单击桌面,个性化,其他:显示),我会获得不同的DPI,Windows需要更大的图像开始以这种“糟糕”的方式缩放图标。 - mark
以上的净结果是:如果我让Windows决定在高DPI模式下使用哪个ICO(即没有LoadImage或通过GetSystemMetrics(SM_CXSMICON)),那么它会选择一个低分辨率的图标并进行缩放(而且效果很差)。 - mark
为了确保我没有疯掉,我使用了你的代码代替我的...在100%桌面缩放下看起来很好。我将我的桌面缩放到125%,音量混合器和任务栏图标都显得很差(锯齿状缩放伪影)。 - mark
@mark,我完全忘记在高DPI设置下进行测试了。我应该从你的截图中注意到你使用的不是96 dpi。抱歉,我正在更新我的答案。 - Cody Gray
LoadIconWithScaleDown函数是个好东西。我的应用程序被编译成XP兼容的,但由于我正在检测Vista,所以我刚刚组合了一个类来查询从Comctl32.dll中获取的函数指针……非常好用!值得一提的是,我验证过它可以在100%、125%、150%和138%(只是为了尝试某些自定义)的缩放比例下正常工作。 - mark

1
我曾在一个C#/WPF程序中遇到类似的问题。
在我的情况下,问题似乎是由于ico文件中缺少尺寸引起的。我的应用程序的ico文件最小尺寸为64x64。一旦我在该文件中添加了更小的尺寸,问题就解决了。

0
非常感谢大家。我已经在我们的Wx应用程序中使其工作,如果有人想要一些预先编写好的代码放入他们的Wx应用程序中,下面是我的代码:
#ifdef __WXMSW__
#include <Windows.h>
#include <CommCtrl.h>
#include <wx/msw/private.h>
typedef int (WINAPI *func_LoadIconWithScaleDown)(HINSTANCE, LPCWSTR, int, int, HICON*);
#endif

void MainFrame::BindAppIcon() {
#ifdef __WXMSW__
    wxDynamicLibrary comctl32("comctl32", wxDL_DEFAULT | wxDL_QUIET);
    func_LoadIconWithScaleDown load_icon_scaled = reinterpret_cast<func_LoadIconWithScaleDown>(comctl32.GetSymbol("LoadIconWithScaleDown"));
    int icon_set_count = 0;

    HICON hIconLg;
    if (load_icon_scaled && SUCCEEDED(load_icon_scaled(wxGetInstance(), _T("AAAAA_MAINICON"), ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), &hIconLg))) {
        ::SendMessage(GetHandle(), WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIconLg));
        ++icon_set_count;
    }
    HICON hIconSm;
    if (load_icon_scaled && SUCCEEDED(load_icon_scaled(wxGetInstance(), _T("AAAAA_MAINICON"), ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), &hIconSm))) {
        ::SendMessage(GetHandle(), WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIconSm));
        ++icon_set_count;
    }

    if (icon_set_count == 2) return;
    // otherwise fall back to Wx method of setting icon
#endif
    wxIcon icon = wxXmlResource::Get()->LoadIcon(wxT("MainIcon"));

    if (!icon.IsOk()) {
        wxLogInfo(_("Main icon not found"));
        icon = wxICON(wxvbam);
    }

    SetIcon(icon);
}

您的 app.rc 中的第一行将是这样的:
AAAAA_MAINICON ICON "icons/app.ico"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接