dylanraga / win11hdr-srgb-to-gamma2.2-icm

Transform Windows 11's virtual SDR-in-HDR curve from piecewise sRGB to Gamma 2.2
373 stars 11 forks source link

Autohotkey script for this #7

Open mspeedo opened 12 months ago

mspeedo commented 12 months ago

If anyone is interested, I created this AutoHotkey script to easily apply and test these LUT transformation curves realtime without need of profile usage/creation and with ability to dynamically change Whitepoint and Gamma values, anytime even during gameplay.

Credits goes to @dylanraga, I just rewrote his formulas to Autohotkey script.

Prerequisities:

How to use: 1) Run the autohotkey script 2) Hotkey WIN+F1 will reset display LUT table and remove any display calibration 3) Hotkey WIN+F2 will apply LUT transformation curve with default parameters Whitepoint = 200, Gamma = 2.2 (default profile) 4) Hotkey WIN+INS will increase Whitepoint parameter by +50 and apply updated profile immediately 5) Hotkey WIN+DEL will decrease Whitepoint parameter by -50 and apply updated profile immediately 6) Hotkey WIN+PgUp will increase Gamma parameter by +0.1 and apply updated profile immediately 7) Hotkey WIN+PgDn will decrease Gamma parameter by -0.1 and apply updated profile immediately

You can use this script anytime even during gameplay to see the effect immediately in realtime without need of profile creation. Changes are not permanent, you need to reapply them after restart or video driver reset.

How it works:

dylanraga commented 12 months ago

Very useful! Applying the vcgt straight with dispwin is neat. I'll give this a mention on the readme.

pduchnovsky commented 11 months ago

@mspeedo does this affect HDR peak brightness reporting in any way ? Compared to the icc profiles.

mspeedo commented 11 months ago

@pduchnovsky No it does not.

WilliamLeGod commented 11 months ago

Very nice! For games that has its native HDR black crushed, what gamma should I adjust to from 2.2 default (To make it the same as the HDR calibration windows app so such games wont have black crushed anymore).

Example: Many Playstation titles such as God of War, Horizon Zero Dawn ...\

Thanks!

Jason-GitH commented 11 months ago

Is there any way to adjust the maxTML and maxFFTML specified in these profiles?

acherubino commented 9 months ago

Hi, thanks a ton for this script it has been very useful and has fixed the few remaining issues I've had with leaving Windows HDR mode on all the time, especially for SDR desktop content.

I have two HDR monitors but dispwin only affects a single monitor at a time, although which monitor is affected can be specified with the -d \<index> command line argument. I modified the script to iterate through each of my displays so that when the hotkey is pressed it will affect all of them at once. You can then close this version of the script and use the original one to toggle off the adjustment to just your main screen when using a game/app with native HDR while still having it applied to your other monitors. I also made it so that the calibration is applied automatically when the script is first launched. This way adding a shortcut to the script to Window's startup folder allows for it to automatically be applied when the computer is launched. Here is a link to my version of the script: https://pastebin.com/fDVhKhwj

As I currently wrote it it just interates through the first 5 monitors connected to the computer and applies the calibration to all of them. 5 here is just my total amount of monitors and can be changed to be more or less depending on how many you have/want the script to affect. Having fewer monitors connected than this number does not cause any errors when it runs. This has the side effect of also applying it to any monitors including non-HDR monitors as well. You may or may not want this, but I find it doesn't bother me. I often turn some of my monitors on or off depending on what I'm doing which changes the order they are listed in dispwin, but if you always have the same monitors connected you could probably hardcode their indices into the script to only affect those specific HDR monitors.

ensingerphilipp commented 8 months ago

@mspeedo

It seems like this LUT Transformation is only working correctly for NVIDIA Gpus - can this be right? I was using a RTX 3090 and everything was great, now i switched to 7900 xtx and the blacks are completely crushed at 2.2 while the whitebalance is off aswell and colors look weird. Black levels look equal when i go down to 1.55 gamma in the script but it overall just does not look correct.

VoltKraken5555 commented 5 months ago

It was working for me before when I was using a displayport cable, I switched to a HDMI 2.1 cable and this script changes my second monitor instead of my primary.
Thinking of it, another thing I changed is primary monitor is on GPU and second monitor is on integrated graphics (because for some reason rtx hdr does not support multiple monitors on the same GPU)

edit: I looked at https://www.argyllcms.com/doc/dispwin.html and need to add -d n (n = display number) to line #86 like Run, dispwin.exe -d 2 %path%, , Hide

acherubino commented 5 months ago

It was working for me before when I was using a displayport cable, I switched to a HDMI 2.1 cable and this script changes my second monitor instead of my primary.

Thinking of it, another thing I changed is primary monitor is on GPU and second monitor is on integrated graphics (because for some reason rtx hdr does not support multiple monitors on the same GPU)

You can control what monitor is affected with the -d <number> dispwin command line argument, where <number> is replaced with the number monitor you want it to affect. Numbers start at 1 and count up. I forget if it's the same order as shown in Windows but if not you should be able to figure out what number you want with some trial and error, but the number can change if you connect/disconnect monitors from your computer so be aware of that.

So for monitor 2 for instance you'd want to add to the line

Run, dispwin.exe %path%, , Hide

from the AutoHotkey script to be

Run, dispwin.exe -d 2 %path%, , Hide

In my previous comment I linked to a script I modified to iterate through up to 5 monitors and apply it to all of them if you want to have it affect multiple monitors. Not sure how having monitors connected across both iGPU and Gfx Card could affect this but hopefully it will still work the same.

ylor commented 4 months ago

Here's a barebones AHKv2 compatible version. I've done some light refactoring to increase clarity, reduce function calls, and file operations. Many thanks to @mspeedo and @dylanraga for their work!

#Requires AutoHotkey v2.0
#SingleInstance Force
#Warn

; VARIABLES

global whitePoint := 120
global blackPoint := 0
global gammaCurve := 2.2

lutFile := EnvGet("TEMP") "\lut.cal"

; FUNCTIONS

LoadCalibrationCurve() {
  createLutFile()
  Run("bin\dispwin.exe " lutFile, , "Hide")
}

ResetCalibrationCurve() {
  FileExist(lutFile) && FileDelete(lutFile)
  Run("bin\dispwin.exe -c", , "Hide")
}

createLutFile() {
  calCurve := "
(
CAL    

DESCRIPTOR "w=" whitePoint " b=" blackPoint " g=" gammaCurve ""
ORIGINATOR "vcgt"
CREATED "Thu Jun 01 01:41:55 2023"
DEVICE_CLASS "DISPLAY"
COLOR_REP "RGB"

NUMBER_OF_FIELDS 4
BEGIN_DATA_FORMAT
RGB_I RGB_R RGB_G RGB_B
END_DATA_FORMAT

NUMBER_OF_SETS 1024
BEGIN_DATA
0.00000000000000    0.00000000000000    0.00000000000000    0.00000000000000
)"

  Loop 1023
{
  b := (A_Index / 1023)
  c := PQ_EOTF(b)
  d := SRGB_INV_EOTF(c, whitePoint, blackPoint)
  e := blackPoint + (whitePoint - blackPoint) * (d ** gammaCurve)
  f := PQ_INV_EOTF(Max(0, e))
  x := f + Min(1, (c / whitePoint)) * (b - f)

  calCurve := calCurve "`n" b " " x "   " x "   " x
}

calCurve := calCurve "`n" "END_DATA"

FileExist(lutFile) && FileDelete(lutFile)
FileAppend(calCurve, lutFile)
Return
}

m1 := (2610 / 4096) / 4
m2 := (2523 / 4096) * 128
c1 := (3424 / 4096)
c2 := (2413 / 4096) * 32
c3 := (2392 / 4096) * 32

PQ_EOTF(V) {
  x := 10000 * (Max(V ** (1 / m2) - c1, 0) / (c2 - c3 * V ** (1 / m2))) ** (1 / m1)
  return x
}

PQ_INV_EOTF(L) {
  x := ((c1 + c2 * (L / 10000) ** m1) / (1 + c3 * (L / 10000) ** m1)) ** m2
  return x
}

SRGB_INV_EOTF(L, whitePoint, blackPoint) {
  X1 := "0.0404482362771082"
  X2 := "0.00313066844250063"

  x := (L - blackPoint) / (whitePoint - blackPoint)

  If (x > 1) {
  x := 1
} Else If (x < 0) {
  x := 0
} Else If (x <= X2) {
  x := x * 12.92
} Else {
  x := 1.055 * (x ** (1 / 2.4)) - 0.055
}

return x
}

;---------------------------------- HOTKEYS -----------------------------------

!Esc::
{
  global whiteLuminance := 80
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!+1::
{
  global whiteLuminance := 120
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!+2::
{
  global whiteLuminance := 250
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!Delete::
{
  global whiteLuminance := 480
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!+Delete:: ; Alt Shift
{
  ResetCalibrationCurve()
}
WilliamLeGod commented 4 months ago

Here's a barebones AHKv2 compatible version. I've done some light refactoring to increase clarity, reduce function calls, and file operations. Many thanks to @mspeedo and @dylanraga for their work!

#Requires AutoHotkey v2.0
#SingleInstance Force
#Warn

; VARIABLES

global whitePoint := 120
global blackPoint := 0
global gammaCurve := 2.2

lutFile := EnvGet("TEMP") "\lut.cal"

; FUNCTIONS

LoadCalibrationCurve() {
  createLutFile()
  Run("bin\dispwin.exe " lutFile, , "Hide")
}

ResetCalibrationCurve() {
  FileExist(lutFile) && FileDelete(lutFile)
  Run("bin\dispwin.exe -c", , "Hide")
}

createLutFile() {
  calCurve := "
(
CAL    

DESCRIPTOR "w=" whitePoint " b=" blackPoint " g=" gammaCurve ""
ORIGINATOR "vcgt"
CREATED "Thu Jun 01 01:41:55 2023"
DEVICE_CLASS "DISPLAY"
COLOR_REP "RGB"

NUMBER_OF_FIELDS 4
BEGIN_DATA_FORMAT
RGB_I RGB_R RGB_G RGB_B
END_DATA_FORMAT

NUMBER_OF_SETS 1024
BEGIN_DATA
0.00000000000000  0.00000000000000    0.00000000000000    0.00000000000000
)"

  Loop 1023
{
  b := (A_Index / 1023)
  c := PQ_EOTF(b)
  d := SRGB_INV_EOTF(c, whitePoint, blackPoint)
  e := blackPoint + (whitePoint - blackPoint) * (d ** gammaCurve)
  f := PQ_INV_EOTF(Max(0, e))
  x := f + Min(1, (c / whitePoint)) * (b - f)

  calCurve := calCurve "`n" b "   " x "   " x "   " x
}

calCurve := calCurve "`n" "END_DATA"

FileExist(lutFile) && FileDelete(lutFile)
FileAppend(calCurve, lutFile)
Return
}

m1 := (2610 / 4096) / 4
m2 := (2523 / 4096) * 128
c1 := (3424 / 4096)
c2 := (2413 / 4096) * 32
c3 := (2392 / 4096) * 32

PQ_EOTF(V) {
  x := 10000 * (Max(V ** (1 / m2) - c1, 0) / (c2 - c3 * V ** (1 / m2))) ** (1 / m1)
  return x
}

PQ_INV_EOTF(L) {
  x := ((c1 + c2 * (L / 10000) ** m1) / (1 + c3 * (L / 10000) ** m1)) ** m2
  return x
}

SRGB_INV_EOTF(L, whitePoint, blackPoint) {
  X1 := "0.0404482362771082"
  X2 := "0.00313066844250063"

  x := (L - blackPoint) / (whitePoint - blackPoint)

  If (x > 1) {
  x := 1
} Else If (x < 0) {
  x := 0
} Else If (x <= X2) {
  x := x * 12.92
} Else {
  x := 1.055 * (x ** (1 / 2.4)) - 0.055
}

return x
}

;---------------------------------- HOTKEYS -----------------------------------

!Esc::
{
  global whiteLuminance := 80
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!+1::
{
  global whiteLuminance := 120
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!+2::
{
  global whiteLuminance := 250
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!Delete::
{
  global whiteLuminance := 480
  ; global blackLuminance := 0
  ; global gammaCurve := 2.2
  LoadCalibrationCurve()
}
!+Delete:: ; Alt Shift
{
  ResetCalibrationCurve()
}

Nice bro! Just curious, is it possible to change the script as follow: Whenever a specific game starts (that has native HDR built in): Auto reverse back to normal default lut and whnever those games quit: Auto change to 2.2 gamma. This will fix all native HDR games and also we can use desktop 2.2 gamma all the time too!

Many thanks!

ylor commented 4 months ago

@WilliamLeGod here's a naive way and inefficient way to do that:

#Requires AutoHotkey v2.0
#SingleInstance Force

; Set up an array of target processes
TargetProcesses := ["example.exe", "example2.exe"]

; Loop to monitor the processes
Loop {
    ; Iterate through the list of target processes
    for Each, Process in TargetProcesses {
        ; Check if the process is running
        if (PID := ProcessExist(Process)) {
            ResetCalibrationCurve()

            ; Wait for the process to close
            ProcessWaitClose(PID)
            LoadCalibrationCurve()
        }
    }
    Sleep(5000)
}
WilliamLeGod commented 4 months ago

@ylor Thanks for the help bro.

So I add this new script below the script u posted before or a brand new ahk?