在Windows 11中以编程方式控制SDR内容亮度

6
在Windows 11中使用支持HDR的显示器时,我们可以在“设置”>“系统”>“显示”>“HDR”下使用“SDR内容亮度”滑块来控制屏幕上常规非HDR元素的亮度。例如,如果显示器最大亮度为400尼特,滑块设置为60%,则SDR内容以240尼特的亮度显示。Windows 10也有类似的功能。我想能够以编程方式更改滑块的值,这样我就可以在白天拥有更明亮的桌面,在晚上则更暗淡。通过WMI或DDC / CI更改亮度的“旧”方法在启用HDR的显示器上不起作用。我使用ProcessMonitor查看滑块的操作,但生成了太多条目,我只能看到它更改了注册表键Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\MonitorDataStore\DELA1E4#G7IYMxgwAAgX_10_07E6_A1\SDRWhiteLevel。在我的显示器上,该值从1000(0%)到3500(50%)到6000(100%)以十进制表示,其中1000等于80尼特。一旦SystemSettings.exe更改了注册表值,它会调用D3D12中的某些内容来实际应用更改。查阅Microsoft Win32 API的文档后,我找不到任何相关信息。请问有人知道如何使其工作吗?任何语言都可以,例如PowerShell、C#、C ++、Python等。谢谢。

DISPLAYCONFIG_SDR_WHITE_LEVEL可能是你正在寻找的内容。顺便说一下,如果你需要进行注册表比较,regshot可能是更好的工具。 - mirh
谢谢 @mirh。我已经看过了,但它只能获取当前值,不能设置。 - wywywywy
哎呀,对啊... 没有 DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL。不过如果有帮助的话,经过大量的搜索,我找到了一个名为 Windows::Graphics::Display::CDisplayInformation::_CallbackSdrWhiteLevelChanged 的函数(可能与 WNF_DX_SDR_WHITE_LEVEL_CHANGED 事件有关),而相应的控制面板应该是在 SettingsHandlers_PCDisplay.dll 文件中实现的(还有更多提到 SDRWhiteLevelSliderValueChanged 和 SystemSettings_Display_AdvancedDisplaySettingsHDRStatus_SDR 的地方,应该是 Windows.UI.SettingsAppThreshold 中对应的设置键)。 - mirh
对啊,没有DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL。不过,如果有帮助的话,经过多次搜索,我找到了一个Windows::Graphics::Display::CDisplayInformation::_CallbackSdrWhiteLevelChanged函数(可能与WNF_DX_SDR_WHITE_LEVEL_CHANGED事件相关),而且相对应的控制面板应该在SettingsHandlers_PCDisplay.dll中实现(还有更多关于SDRWhiteLevelSliderValueChanged的提及以及对SystemSettings_Display_AdvancedDisplaySettingsHDRStatus_SDR的引用,这应该是Windows.UI.SettingsAppThreshold中对应的设置键)。 - undefined
SO是一个编程问答平台,这个问题与编程无关。关于操作系统、实用工具、网络和硬件的问题在这里不属于话题范围。我可以在这里问什么问题? 请删除此问题,并在 https://superuser.com/ 上提问。 - Rob
3
OP特别要求以编程方式(winapi)控制亮度,我不明白为什么这是离题的,应该在SU上提问。 - lulle2007200
2个回答

5

经过一番挖掘,我找到了如何设置SDR亮度的方法:

dwmapi.dll中有一个函数HRESULT DwmpSDRToHDRBoost(HMONITOR monitor, double brightness),它通过序数171导出,其中brightness必须大于等于1.0(80尼特)。设置应用程序允许的最大值是6.0(480尼特),但可以设置得更高。如果您将值设置为超过显示器支持的亮度,则会发生剪切。

设置应用程序还会设置你提到的注册表键,但我没有找到任何与该键相关的内容。

以下是一个简单的示例:

#include <Windows.h>

using DwmpSDRToHDRBoostPtr = HRESULT(__stdcall *)(HMONITOR, double);

HMONITOR GetPrimaryMonitor(){
    return MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
}

int main()
{
    double brightness = 6.0;
    HMODULE hmodule_dwmapi = LoadLibraryW(L"dwmapi.dll");
    DwmpSDRToHDRBoostPtr DwmpSDRToHDRBoost = reinterpret_cast<DwmpSDRToHDRBoostPtr>(GetProcAddress(hmodule_dwmapi, MAKEINTRESOURCE(171)));
    DwmpSDRToHDRBoost(GetPrimaryMonitor(), brightness);
}

请记住,这是未记录的API,其顺序和行为可能会在将来发生变化。
更新:
至少在最新的Windows 11内部预览版中,使用“DwmpSdrToHdrBoost”设置SDR亮度不再更新Windows设置中的滑块。由“DisplayAdvancedColorInfo.SdrWhiteLevelInNits”报告的值也不会更新。它们可能将这些值缓存到某个地方或从注册表中读取。

1
谢谢你的帮助,我根据这个做了一个基于C#的版本,完美地运行了。我将它作为另一个答案添加到了问题中。 - MaloW
1
谢谢你,我根据这个做了一个基于C#的版本,完美运行。我将它作为另一个答案添加到了问题中。 - undefined
太棒了!真是神奇!非常感谢!我想知道为什么亮度范围从1.0到6.0,但我不会抱怨。只是出于好奇 - 你能分享一下你是如何找到这个解决方法的吗? - wywywywy
2
@wywywywy 基本上,以调试器运行设置应用程序,在其中找到处理显示设置的dll,在该dll中找到当sdr亮度滑块的值改变时调用的事件处理程序,通过该处理程序直到找到对dwmapi.dll的调用。至于亮度范围,1.0对应于显示器支持的最小亮度,6.0对应于最大亮度。你可以在技术上将其设置为>6.0,但是它会被限制,因此你会得到过度饱和的白色显示输出。 - lulle2007200
2
@wywywywy 基本上,运行调试器下的设置应用程序,找到处理显示设置的dll,在该dll中,找到当sdr亮度滑块的值发生变化时调用的事件处理程序,通过该处理程序直到找到对dwmapi.dll的调用。至于亮度范围,1.0对应于显示器支持的最小亮度,6.0对应于最大亮度。你理论上可以将其设置为>6.0,但是它会被限制,因此你会得到过度饱和的白色显示输出。 - undefined

3
这是我根据lulle2007200的答案制作的C#版本。
using System;
using System.Runtime.InteropServices;

namespace ScreenBrightnessSetter
{
    class Program
    {
        [DllImport("user32.dll")]
        static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
        [DllImport("kernel32", CharSet = CharSet.Unicode)]
        static extern IntPtr LoadLibrary(string lpFileName);
        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        static extern IntPtr GetProcAddress(IntPtr hModule, int address);

        private delegate void DwmpSDRToHDRBoostPtr(IntPtr monitor, double brightness);

        static void Main(string[] args)
        {
            var primaryMonitor = MonitorFromWindow(IntPtr.Zero, 1);
            var hmodule_dwmapi = LoadLibrary("dwmapi.dll");
            DwmpSDRToHDRBoostPtr changeBrightness = Marshal.GetDelegateForFunctionPointer<DwmpSDRToHDRBoostPtr>(GetProcAddress(hmodule_dwmapi, 171));

            double brightness = 1.0;
            changeBrightness(primaryMonitor, brightness);
        }
    }
}

刚刚测试了一下,这个也非常好用!非常感谢你。 - wywywywy

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