Skip to content

Commit da93dd6

Browse files
authored
Merge pull request #660 from TheJoeFin/hdr-capture-wgc
Support HDR Capture
2 parents 865556e + 6a67612 commit da93dd6

12 files changed

Lines changed: 979 additions & 7 deletions

.claude/settings.local.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
"PowerShell(dotnet test *)",
2727
"PowerShell(Get-ChildItem *)",
2828
"WebFetch(domain:raw.githubusercontent.com)",
29-
"Bash(gh issue view *)"
29+
"Bash(gh issue view *)",
30+
"Bash(git log *)",
31+
"Bash(gh pr view *)",
32+
"Bash(gh pr diff *)"
3033
],
3134
"deny": []
3235
}

Tests/HdrToneMapperTests.cs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using Text_Grab.Utilities.Hdr;
2+
3+
namespace Tests;
4+
5+
public class HdrToneMapperTests
6+
{
7+
[Fact]
8+
public void SdrWhiteScaleFromNits_ReferenceWhite_IsOne()
9+
{
10+
Assert.Equal(1.0, HdrToneMapper.SdrWhiteScaleFromNits(80.0), 5);
11+
}
12+
13+
[Theory]
14+
[InlineData(200.0, 2.5)]
15+
[InlineData(160.0, 2.0)]
16+
[InlineData(480.0, 6.0)]
17+
public void SdrWhiteScaleFromNits_ScalesRelativeTo80Nits(double nits, double expectedScale)
18+
{
19+
Assert.Equal(expectedScale, HdrToneMapper.SdrWhiteScaleFromNits(nits), 5);
20+
}
21+
22+
[Theory]
23+
[InlineData(0.0)]
24+
[InlineData(-5.0)]
25+
[InlineData(40.0)]
26+
public void SdrWhiteScaleFromNits_NeverBrightens(double nits)
27+
{
28+
// Values at or below the 80-nit reference must not produce a scale below 1.0,
29+
// which would brighten the image instead of correcting the HDR boost.
30+
Assert.Equal(1.0, HdrToneMapper.SdrWhiteScaleFromNits(nits), 5);
31+
}
32+
33+
[Fact]
34+
public void LinearToSrgb_Endpoints()
35+
{
36+
Assert.Equal(0.0, HdrToneMapper.LinearToSrgb(0.0), 5);
37+
Assert.Equal(1.0, HdrToneMapper.LinearToSrgb(1.0), 5);
38+
}
39+
40+
[Fact]
41+
public void ScRgbChannelToSrgbByte_SdrWhiteMapsToFullWhite()
42+
{
43+
// On a display with SDR white at 200 nits, SDR white sits at scRGB 2.5.
44+
double scale = HdrToneMapper.SdrWhiteScaleFromNits(200.0);
45+
46+
Assert.Equal(255, HdrToneMapper.ScRgbChannelToSrgbByte(2.5, scale));
47+
}
48+
49+
[Fact]
50+
public void ScRgbChannelToSrgbByte_UndoesHdrBrightnessBoost()
51+
{
52+
// The washout bug: SDR content lands above scRGB 1.0 on HDR displays. Normalizing by
53+
// the SDR white level must pull mid-gray back to a mid sRGB value rather than near-white.
54+
double scale = HdrToneMapper.SdrWhiteScaleFromNits(200.0);
55+
56+
// Half of SDR white in linear light -> sRGB ~0.735 -> ~188.
57+
byte midGray = HdrToneMapper.ScRgbChannelToSrgbByte(1.25, scale);
58+
Assert.InRange(midGray, 186, 190);
59+
}
60+
61+
[Fact]
62+
public void ScRgbChannelToSrgbByte_HighlightsAboveSdrWhiteClipToWhite()
63+
{
64+
double scale = HdrToneMapper.SdrWhiteScaleFromNits(200.0);
65+
66+
// A specular highlight well above SDR white clamps to white rather than overflowing.
67+
Assert.Equal(255, HdrToneMapper.ScRgbChannelToSrgbByte(10.0, scale));
68+
}
69+
70+
[Fact]
71+
public void ScRgbChannelToSrgbByte_NegativeWideGamutClampsToBlack()
72+
{
73+
double scale = HdrToneMapper.SdrWhiteScaleFromNits(200.0);
74+
75+
Assert.Equal(0, HdrToneMapper.ScRgbChannelToSrgbByte(-0.5, scale));
76+
}
77+
78+
[Fact]
79+
public void ScRgbChannelToSrgbByte_IsMonotonic()
80+
{
81+
double scale = HdrToneMapper.SdrWhiteScaleFromNits(200.0);
82+
int previous = -1;
83+
84+
for (double channel = 0.0; channel <= 2.5; channel += 0.05)
85+
{
86+
int value = HdrToneMapper.ScRgbChannelToSrgbByte(channel, scale);
87+
Assert.True(value >= previous, $"Value dropped at channel {channel}");
88+
previous = value;
89+
}
90+
}
91+
}

Text-Grab/App.config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,12 @@
283283
<setting name="GrabFrameBorderColor" serializeAs="String">
284284
<value>#2A767E</value>
285285
</setting>
286+
<setting name="HdrCaptureCorrection" serializeAs="String">
287+
<value>False</value>
288+
</setting>
289+
<setting name="HdrBorderlessGranted" serializeAs="String">
290+
<value>False</value>
291+
</setting>
286292
</Text_Grab.Properties.Settings>
287293
</userSettings>
288294
</configuration>

Text-Grab/Pages/GeneralSettings.xaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,35 @@
277277
Disabling may speed up results
278278
</TextBlock>
279279

280+
<TextBlock
281+
Margin="0,16,0,4"
282+
FontSize="16"
283+
Style="{StaticResource TextHeader}"
284+
Text="HDR Displays" />
285+
<StackPanel Orientation="Horizontal">
286+
<ui:ToggleSwitch
287+
Name="HdrCaptureCorrectionToggle"
288+
VerticalAlignment="Center"
289+
Checked="HdrCaptureCorrectionToggle_Checked"
290+
Unchecked="HdrCaptureCorrectionToggle_Unchecked">
291+
<TextBlock Style="{StaticResource TextBodyNormal}">
292+
Correct washed-out captures on HDR displays
293+
</TextBlock>
294+
</ui:ToggleSwitch>
295+
<ui:Button
296+
Name="CheckHdrPermissionButton"
297+
Margin="16,0,0,0"
298+
VerticalAlignment="Center"
299+
Click="CheckHdrPermissionButton_Click"
300+
Content="Check permissions" />
301+
</StackPanel>
302+
<TextBlock Style="{StaticResource TextBodyNormal}">
303+
Captures HDR screens at full range and tone-maps them back to SDR so text isn't overly bright. Only affects monitors with HDR turned on.
304+
</TextBlock>
305+
<TextBlock Style="{StaticResource TextBodyNormal}">
306+
Removing the yellow capture border needs Windows "borderless capture" permission. Use Check permissions to grant it.
307+
</TextBlock>
308+
280309
<TextBlock
281310
Margin="0,16,0,4"
282311
FontSize="16"

Text-Grab/Pages/GeneralSettings.xaml.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ private async void Page_Loaded(object sender, RoutedEventArgs e)
132132

133133
RunInBackgroundChkBx.IsChecked = DefaultSettings.RunInTheBackground;
134134
ReadBarcodesBarcode.IsChecked = DefaultSettings.TryToReadBarcodes;
135+
HdrCaptureCorrectionToggle.IsChecked = DefaultSettings.HdrCaptureCorrection;
135136
HistorySwitch.IsChecked = DefaultSettings.UseHistory;
136137
ErrorCorrectBox.IsChecked = DefaultSettings.CorrectErrors;
137138
CorrectToLatin.IsChecked = DefaultSettings.CorrectToLatin;
@@ -269,6 +270,54 @@ private void ReadBarcodesBarcode_Unchecked(object sender, RoutedEventArgs e)
269270
DefaultSettings.TryToReadBarcodes = false;
270271
}
271272

273+
private void HdrCaptureCorrectionToggle_Checked(object sender, RoutedEventArgs e)
274+
{
275+
if (!settingsSet)
276+
return;
277+
278+
DefaultSettings.HdrCaptureCorrection = true;
279+
}
280+
281+
private void HdrCaptureCorrectionToggle_Unchecked(object sender, RoutedEventArgs e)
282+
{
283+
if (!settingsSet)
284+
return;
285+
286+
DefaultSettings.HdrCaptureCorrection = false;
287+
}
288+
289+
private async void CheckHdrPermissionButton_Click(object sender, RoutedEventArgs e)
290+
{
291+
CheckHdrPermissionButton.IsEnabled = false;
292+
293+
Windows.Security.Authorization.AppCapabilityAccess.AppCapabilityAccessStatus status =
294+
await Utilities.Hdr.HdrScreenCapture.RequestBorderlessAccessAsync();
295+
296+
CheckHdrPermissionButton.IsEnabled = true;
297+
298+
bool allowed = status == Windows.Security.Authorization.AppCapabilityAccess.AppCapabilityAccessStatus.Allowed;
299+
DefaultSettings.HdrBorderlessGranted = allowed;
300+
DefaultSettings.Save();
301+
302+
string message = status switch
303+
{
304+
Windows.Security.Authorization.AppCapabilityAccess.AppCapabilityAccessStatus.Allowed
305+
=> "Borderless capture is allowed. The yellow capture border will no longer appear.",
306+
Windows.Security.Authorization.AppCapabilityAccess.AppCapabilityAccessStatus.DeniedByUser
307+
=> "Borderless capture was denied. You can allow it in Windows Settings under Privacy & security → Graphics capture (or the consent prompt).",
308+
Windows.Security.Authorization.AppCapabilityAccess.AppCapabilityAccessStatus.DeniedBySystem
309+
=> "Borderless capture is blocked on this system, so the capture border can't be removed.",
310+
_ => "Borderless capture permission is currently unavailable.",
311+
};
312+
313+
await new Wpf.Ui.Controls.MessageBox
314+
{
315+
Title = "HDR Capture Permission",
316+
Content = message,
317+
CloseButtonText = "OK"
318+
}.ShowDialogAsync();
319+
}
320+
272321
private void HistorySwitch_Checked(object sender, RoutedEventArgs e)
273322
{
274323
if (!settingsSet)

Text-Grab/Properties/Settings.Designer.cs

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Text-Grab/Properties/Settings.settings

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,5 +278,11 @@
278278
<Setting Name="GrabFrameBorderColor" Type="System.String" Scope="User">
279279
<Value Profile="(Default)">#2A767E</Value>
280280
</Setting>
281+
<Setting Name="HdrCaptureCorrection" Type="System.Boolean" Scope="User">
282+
<Value Profile="(Default)">False</Value>
283+
</Setting>
284+
<Setting Name="HdrBorderlessGranted" Type="System.Boolean" Scope="User">
285+
<Value Profile="(Default)">False</Value>
286+
</Setting>
281287
</Settings>
282288
</SettingsFile>

Text-Grab/Text-Grab.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@
8585
<PackageReference Include="NCalcAsync" Version="6.2.0" />
8686
<PackageReference Include="PdfPig" Version="0.1.14" />
8787
<PackageReference Include="UnitsNet" Version="5.75.0" />
88+
<PackageReference Include="Vortice.Direct3D11" Version="3.8.3" />
89+
<PackageReference Include="Vortice.DXGI" Version="3.8.3" />
8890
<PackageReference Include="WPF-UI" Version="4.3.0" />
8991
<PackageReference Include="WPF-UI.Tray" Version="4.3.0" />
9092
<PackageReference Include="ZXing.Net" Version="0.16.11" />

0 commit comments

Comments
 (0)