mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-22 00:22:18 -05:00
This commit is contained in:
1 parent
58e03313e0
commit
7543cca15d
40 files changed
+3344
-71
No files matched your search
@@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nspector", "Nspector\Nspect
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvAPIWrapper", "NvAPIWrapper\NvAPIWrapper.csproj", "{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "novideo_srgb", "novideo_srgb\novideo_srgb.csproj", "{A6A97834-7BE1-474A-B92F-A512DB1D5186}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -41,6 +43,14 @@ Global
|
||||
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{42E2B4A6-6857-4427-AEF1-DF4C7EF8CC7C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.ActiveCfg = Release|x64
|
||||
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
<PublisherName>Maassoft</PublisherName>
|
||||
<Company>Maassoft</Company>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>9.4.0.0</ApplicationVersion>
|
||||
<Version>9.4.0.0</Version>
|
||||
<ApplicationVersion>9.5.0.0</ApplicationVersion>
|
||||
<Version>9.5.0.0</Version>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
@@ -101,6 +101,7 @@
|
||||
<PackageReference Include="Vanara.PInvoke.IpHlpApi" Version="3.4.13" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\novideo_srgb\novideo_srgb.csproj" />
|
||||
<ProjectReference Include="..\Nspector\Nspector.csproj" />
|
||||
<ProjectReference Include="..\NvAPIWrapper\NvAPIWrapper.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -497,6 +497,21 @@ The best and suggested method to provide this is via a Windows Service. Only whe
|
||||
return null; // could also return string.Empty
|
||||
}
|
||||
|
||||
public static string RemoveFirstUnderscore(this string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
if (text[0] == '_')
|
||||
{
|
||||
return text.Substring(1);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
public static string GetEnumNameByDescription(Type enumType, string description)
|
||||
{
|
||||
if (enumType.BaseType == typeof(Enum))
|
||||
@@ -505,7 +520,7 @@ The best and suggested method to provide this is via a Windows Service. Only whe
|
||||
{
|
||||
if (description.Equals(GetDescription(enumType, enumValue as IConvertible), StringComparison.Ordinal))
|
||||
{
|
||||
return Enum.GetName(enumType, enumValue);
|
||||
return Enum.GetName(enumType, enumValue).RemoveFirstUnderscore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -879,7 +894,7 @@ The best and suggested method to provide this is via a Windows Service. Only whe
|
||||
|
||||
public static FileInfo SelectFile(string ext = "*.exe", string filter = "Application EXE Name|*.exe|Application Absolute Path|*.exe")
|
||||
{
|
||||
var openDialog = new OpenFileDialog();
|
||||
var openDialog = new System.Windows.Forms.OpenFileDialog();
|
||||
openDialog.DefaultExt = ext;
|
||||
openDialog.Filter = filter;
|
||||
|
||||
|
||||
@@ -150,21 +150,27 @@ namespace ColorControl.Forms
|
||||
var boxes = new List<Control>();
|
||||
var counter = 1;
|
||||
const int DefaultLeft = 20;
|
||||
var currentLeft = DefaultLeft;
|
||||
|
||||
var columns = fields.Count() > 10 ? 2 : 1;
|
||||
var currentColumn = 0;
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var lastTop = top;
|
||||
|
||||
var label = field.Label;
|
||||
|
||||
if (field.FieldType != FieldType.CheckBox)
|
||||
{
|
||||
var textLabel = new Label() { Left = DefaultLeft, Top = top, Text = label, AutoSize = true };
|
||||
var textLabel = new Label() { Left = currentLeft, Top = top, Text = label, AutoSize = true };
|
||||
top += 20;
|
||||
groupBox.Controls.Add(textLabel);
|
||||
}
|
||||
|
||||
if (field.SubLabel != null)
|
||||
{
|
||||
var textSubLabel = new Label() { Left = DefaultLeft, Top = top, Text = field.SubLabel, AutoSize = true };
|
||||
var textSubLabel = new Label() { Left = currentLeft, Top = top, Text = field.SubLabel, AutoSize = true };
|
||||
top += 20;
|
||||
groupBox.Controls.Add(textSubLabel);
|
||||
}
|
||||
@@ -174,7 +180,7 @@ namespace ColorControl.Forms
|
||||
switch (field.FieldType)
|
||||
{
|
||||
case FieldType.Text:
|
||||
var textBox = new MaskedTextBox() { Left = DefaultLeft, Top = top, Width = 400 };
|
||||
var textBox = new MaskedTextBox() { Left = currentLeft, Top = top, Width = 400 };
|
||||
|
||||
if (label.Contains("Ip-address"))
|
||||
{
|
||||
@@ -194,7 +200,7 @@ namespace ColorControl.Forms
|
||||
case FieldType.Shortcut:
|
||||
var shortcutTextBox = new TextBox()
|
||||
{
|
||||
Left = DefaultLeft,
|
||||
Left = currentLeft,
|
||||
Top = top,
|
||||
Width = 200,
|
||||
Name = $"edtShortcut_{counter}",
|
||||
@@ -213,7 +219,7 @@ namespace ColorControl.Forms
|
||||
break;
|
||||
|
||||
case FieldType.Numeric:
|
||||
var numericEdit = new NumericUpDown() { Left = DefaultLeft, Top = top, Width = 200 };
|
||||
var numericEdit = new NumericUpDown() { Left = currentLeft, Top = top, Width = 200 };
|
||||
|
||||
if (field.MinValue != field.MaxValue)
|
||||
{
|
||||
@@ -232,7 +238,7 @@ namespace ColorControl.Forms
|
||||
case FieldType.DropDown:
|
||||
var comboBox = new ComboBox
|
||||
{
|
||||
Left = DefaultLeft,
|
||||
Left = currentLeft,
|
||||
Top = top,
|
||||
Width = 400,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList
|
||||
@@ -258,7 +264,7 @@ namespace ColorControl.Forms
|
||||
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Left = DefaultLeft,
|
||||
Left = currentLeft,
|
||||
Top = top,
|
||||
AutoSize = true,
|
||||
Text = label
|
||||
@@ -272,7 +278,7 @@ namespace ColorControl.Forms
|
||||
case FieldType.Flags:
|
||||
var checkedListBox = new CheckedListBox
|
||||
{
|
||||
Left = DefaultLeft,
|
||||
Left = currentLeft,
|
||||
Top = top,
|
||||
Width = 400,
|
||||
Height = 100,
|
||||
@@ -300,7 +306,7 @@ namespace ColorControl.Forms
|
||||
break;
|
||||
|
||||
case FieldType.TrackBar:
|
||||
var trackBar = new TrackBar { Left = DefaultLeft, Top = top, Width = 300 };
|
||||
var trackBar = new TrackBar { Left = currentLeft, Top = top, Width = 300 };
|
||||
var trackBarEdit = new NumericUpDown() { Left = trackBar.Left + trackBar.Width + 10, Top = top + 10, Width = 90 };
|
||||
|
||||
trackBarEdit.ValueChanged += prompt.TrackBarEdit_ValueChanged;
|
||||
@@ -349,14 +355,26 @@ namespace ColorControl.Forms
|
||||
continue;
|
||||
}
|
||||
|
||||
top += 30;
|
||||
if (currentColumn + 1 < columns)
|
||||
{
|
||||
currentColumn++;
|
||||
top = lastTop;
|
||||
currentLeft = control.Left + control.Width + DefaultLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
top += 30;
|
||||
currentColumn = 0;
|
||||
currentLeft = DefaultLeft;
|
||||
}
|
||||
|
||||
groupBox.Controls.Add(control);
|
||||
boxes.Add(control);
|
||||
control.Tag = field;
|
||||
counter++;
|
||||
}
|
||||
|
||||
var maxWidth = boxes.Max(c => c.Width + c.Left) + 20;
|
||||
var maxWidth = boxes.Max(c => c.Width + c.Left) + 36;
|
||||
|
||||
if (groupBox.Width < maxWidth)
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using ColorControl.Services.NVIDIA;
|
||||
using ColorControl.Svc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NLog;
|
||||
using novideo_srgb;
|
||||
using NvAPIWrapper.Display;
|
||||
using NWin32;
|
||||
using System;
|
||||
@@ -48,6 +49,7 @@ namespace ColorControl
|
||||
private LgPanel _lgPanel;
|
||||
|
||||
private ToolStripMenuItem _nvTrayMenu;
|
||||
private ToolStripMenuItem _novideoTrayMenu;
|
||||
private ToolStripMenuItem _amdTrayMenu;
|
||||
private ToolStripMenuItem _lgTrayMenu;
|
||||
private ToolStripMenuItem _gameTrayMenu;
|
||||
@@ -89,6 +91,7 @@ namespace ColorControl
|
||||
MessageForms.MainForm = this;
|
||||
|
||||
_nvTrayMenu = new ToolStripMenuItem("NVIDIA presets");
|
||||
_novideoTrayMenu = new ToolStripMenuItem("Novideo sRGB");
|
||||
_amdTrayMenu = new ToolStripMenuItem("AMD presets");
|
||||
_lgTrayMenu = new ToolStripMenuItem("LG presets");
|
||||
_gameTrayMenu = new ToolStripMenuItem("Game Launcher");
|
||||
@@ -102,6 +105,7 @@ namespace ColorControl
|
||||
|
||||
_trayIcon.ContextMenuStrip.Items.AddRange(new ToolStripItem[] {
|
||||
_nvTrayMenu,
|
||||
_novideoTrayMenu,
|
||||
_amdTrayMenu,
|
||||
_lgTrayMenu,
|
||||
_gameTrayMenu,
|
||||
@@ -355,7 +359,7 @@ namespace ColorControl
|
||||
|
||||
private void InitInfo()
|
||||
{
|
||||
_currentVersionInfo = FileVersionInfo.GetVersionInfo(Path.GetFileName(Application.ExecutablePath));
|
||||
_currentVersionInfo = FileVersionInfo.GetVersionInfo(Application.ExecutablePath);
|
||||
|
||||
Text = Application.ProductName + " " + Application.ProductVersion;
|
||||
|
||||
@@ -374,6 +378,7 @@ namespace ColorControl
|
||||
lbPlugins.Items.Add("TaskScheduler by David Hall");
|
||||
lbPlugins.Items.Add("NVIDIA Profile Inspector by Orbmu2k");
|
||||
lbPlugins.Items.Add("NvidiaML wrapper by LibreHardwareMonitor");
|
||||
lbPlugins.Items.Add("Novideo sRGB by ledoge");
|
||||
}
|
||||
|
||||
private void OpenForm(object sender, EventArgs e)
|
||||
@@ -844,6 +849,12 @@ NOTE: installing the service may cause a User Account Control popup.");
|
||||
UpdateTrayMenu(_nvTrayMenu, presets, TrayMenuItemNv_Click);
|
||||
}
|
||||
|
||||
_novideoTrayMenu.Visible = _serviceManager.NvService != null && MainWindow.IsInitialized();
|
||||
if (_novideoTrayMenu.Visible)
|
||||
{
|
||||
MainWindow.UpdateContextMenu(_novideoTrayMenu);
|
||||
}
|
||||
|
||||
_amdTrayMenu.Visible = _serviceManager.AmdService != null;
|
||||
if (_amdTrayMenu.Visible)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace ColorControl.Services.LG
|
||||
public bool Advanced { get; set; }
|
||||
public ModelYear FromModelYear { get; set; } = ModelYear.None;
|
||||
public ModelYear ToModelYear { get; set; } = ModelYear.None;
|
||||
public List<string> ValueLabels { get; set; }
|
||||
}
|
||||
|
||||
public class LgDevicePictureSettings
|
||||
@@ -70,6 +71,8 @@ namespace ColorControl.Services.LG
|
||||
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
public static List<string> DefaultActionsOnGameBar = new() { "backlight", "contrast", "brightness", "color" };
|
||||
|
||||
private static List<string> IreValueLabels = new() { "IRE 2.5", "IRE 5", "IRE 7.5", "IRE 10", "IRE 15", "IRE 20", "IRE 25", "IRE 30", "IRE 35", "IRE 40", "IRE 45", "IRE 50", "IRE 55", "IRE 60", "IRE 65", "IRE 70", "IRE 75", "IRE 80", "IRE 85", "IRE 90", "IRE 95", "IRE 100" };
|
||||
|
||||
public string Name { get; private set; }
|
||||
public string IpAddress { get; private set; }
|
||||
public string MacAddress { get; private set; }
|
||||
@@ -171,6 +174,7 @@ namespace ColorControl.Services.LG
|
||||
TriggersEnabled = true;
|
||||
|
||||
AddInvokableAction("WOL", WakeAction);
|
||||
AddInvokableAction("Reboot", RebootAction);
|
||||
AddGenericPictureAction("backlight", minValue: 0, maxValue: 100);
|
||||
AddGenericPictureAction("brightness", minValue: 0, maxValue: 100, title: "Brightness/Black Level");
|
||||
AddGenericPictureAction("contrast", minValue: 0, maxValue: 100);
|
||||
@@ -214,47 +218,35 @@ namespace ColorControl.Services.LG
|
||||
AddGenericPictureAction("motionPro", typeof(OffToOn), title: "Motion Pro");
|
||||
AddGenericPictureAction("realCinema", typeof(OffToOn), title: "Real Cinema");
|
||||
|
||||
AddGenericPictureAction("uhdDeepColorHDMI1", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("uhdDeepColorHDMI2", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("uhdDeepColorHDMI3", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("uhdDeepColorHDMI4", typeof(OffToOn), category: "other");
|
||||
AddHdmiPictureAction("uhdDeepColorHDMI", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("lowLevelAdjustment", minValue: -30, maxValue: 30, category: "other", title: "Fine Tune Dark Areas", fromModelYear: ModelYear.Series2019);
|
||||
AddGenericPictureAction("blackStabilizer", minValue: -30, maxValue: 30, category: "other", title: "Black Stabilizer", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("whiteStabilizer", minValue: -30, maxValue: 30, category: "other", title: "White Stabilizer", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("blueLight", typeof(BlueLight), category: "other", title: "Reduce Blue Light", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("gameMode_hdmi1", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI1", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("gameMode_hdmi2", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI2", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("gameMode_hdmi3", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI3", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("gameMode_hdmi4", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI4", fromModelYear: ModelYear.Series2021);
|
||||
|
||||
AddHdmiPictureAction("gameMode_hdmi", typeof(OffToOn), category: "other", title: "Game Optimizer HDMI1", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("gameOptimization", typeof(OffToOn), category: "other", title: "VRR & G-Sync", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("inputOptimization", typeof(InputOptimization), category: "other", title: "Prevent Input Delay(Input Lag)", fromModelYear: ModelYear.Series2021);
|
||||
AddGenericPictureAction("gameOptimizationHDMI1", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("gameOptimizationHDMI2", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("gameOptimizationHDMI3", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("gameOptimizationHDMI4", typeof(OffToOn), category: "other");
|
||||
AddHdmiPictureAction("gameOptimizationHDMI", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("freesync", typeof(OffToOn), category: "other", title: "AMD FreeSync Premium", fromModelYear: ModelYear.Series2020);
|
||||
AddGenericPictureAction("freesyncOLEDHDMI1", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddGenericPictureAction("freesyncOLEDHDMI2", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddGenericPictureAction("freesyncOLEDHDMI3", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddGenericPictureAction("freesyncOLEDHDMI4", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddHdmiPictureAction("freesyncOLEDHDMI", typeof(OffToOn), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
|
||||
AddGenericPictureAction("444BypassHDMI1", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMI1", fromModelYear: ModelYear.Series2023);
|
||||
AddGenericPictureAction("444BypassHDMI2", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMI2", fromModelYear: ModelYear.Series2023);
|
||||
AddGenericPictureAction("444BypassHDMI3", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMI3", fromModelYear: ModelYear.Series2023);
|
||||
AddGenericPictureAction("444BypassHDMI4", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMI4", fromModelYear: ModelYear.Series2023);
|
||||
AddHdmiPictureAction("444BypassHDMI", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through HDMIX", fromModelYear: ModelYear.Series2023);
|
||||
AddGenericPictureAction("444BypassHDMINone", typeof(OffToOn), category: "other", title: "4:4:4 Pass Through Non-HDMI", fromModelYear: ModelYear.Series2023);
|
||||
AddGenericPictureAction("qmsVrr", typeof(OffToOn), category: "other", title: "QMS-VRR", fromModelYear: ModelYear.Series2023);
|
||||
|
||||
//AddGenericPictureAction("freesyncSupport", typeof(OffToOn), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi1", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi2", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi3", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("hdmiPcMode_hdmi4", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
AddGenericPictureAction("whiteBalanceBlue", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
AddGenericPictureAction("whiteBalanceGreen", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
AddGenericPictureAction("whiteBalanceRed", minValue: -50, maxValue: 50, numberOfValues: 22);
|
||||
//AddGenericPictureAction("wb20PointsGammaValue", minValue: -50, maxValue: 50);
|
||||
AddGenericPictureAction("masterLuminanceLevel", typeof(MasterLuminanceLevel), category: "other");
|
||||
AddHdmiPictureAction("colorimetryHDMI", typeof(MasteringColor), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddHdmiPictureAction("masteringColorHDMI", typeof(MasteringColor), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddHdmiPictureAction("masteringPeakHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddHdmiPictureAction("maxCLLHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
AddHdmiPictureAction("maxFALLHDMI", typeof(MasteringNits), category: "other", fromModelYear: ModelYear.Series2020);
|
||||
|
||||
AddHdmiPictureAction("hdmiPcMode_hdmi", typeof(FalseToTrue), category: "other");
|
||||
AddGenericPictureAction("adjustingLuminance", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels);
|
||||
AddGenericPictureAction("whiteBalanceBlue", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels);
|
||||
AddGenericPictureAction("whiteBalanceGreen", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels);
|
||||
AddGenericPictureAction("whiteBalanceRed", minValue: -50, maxValue: 50, numberOfValues: 22, valueLabels: IreValueLabels);
|
||||
AddInvokableAction("turnScreenOff", TurnScreenOffAction);
|
||||
AddInvokableAction("turnScreenOn", TurnScreenOnAction);
|
||||
|
||||
@@ -307,7 +299,17 @@ namespace ColorControl.Services.LG
|
||||
_invokableActions.Add(action);
|
||||
}
|
||||
|
||||
private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1, ModelYear fromModelYear = ModelYear.None)
|
||||
private void AddHdmiPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1, ModelYear fromModelYear = ModelYear.None)
|
||||
{
|
||||
for (var i = 1; i <= 4; i++)
|
||||
{
|
||||
var replacedTitle = title?.Replace("HDMIX", $"HDMI{i}");
|
||||
|
||||
AddGenericPictureAction($"{name}{i}", type, minValue, maxValue, category, replacedTitle, numberOfValues, fromModelYear);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddGenericPictureAction(string name, Type type = null, decimal minValue = 0, decimal maxValue = 0, string category = "picture", string title = null, int numberOfValues = 1, ModelYear fromModelYear = ModelYear.None, List<string> valueLabels = null)
|
||||
{
|
||||
var action = new InvokableAction
|
||||
{
|
||||
@@ -319,7 +321,8 @@ namespace ColorControl.Services.LG
|
||||
NumberOfValues = numberOfValues,
|
||||
Category = category,
|
||||
Title = title == null ? Utils.FirstCharUpperCase(name) : title,
|
||||
FromModelYear = fromModelYear
|
||||
FromModelYear = fromModelYear,
|
||||
ValueLabels = valueLabels
|
||||
};
|
||||
|
||||
_invokableActions.Add(action);
|
||||
@@ -1080,6 +1083,15 @@ namespace ColorControl.Services.LG
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> RebootAction(Dictionary<string, object> _)
|
||||
{
|
||||
await CheckConnectionAsync();
|
||||
|
||||
await _lgTvApi.Reboot();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> GenericPictureAction(Dictionary<string, object> parameters)
|
||||
{
|
||||
await CheckConnectionAsync();
|
||||
|
||||
@@ -576,7 +576,7 @@ namespace ColorControl.Services.LG
|
||||
|
||||
fields.AddRange(array.Select(i => new MessageForms.FieldDefinition
|
||||
{
|
||||
Label = "Value for " + i,
|
||||
Label = "Value for " + (action.ValueLabels != null ? action.ValueLabels[i] : i.ToString()),
|
||||
FieldType = MessageForms.FieldType.Numeric,
|
||||
MinValue = action.MinValue,
|
||||
MaxValue = action.MaxValue,
|
||||
|
||||
+11
-3
@@ -89,6 +89,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
miNvProfileInspector = new System.Windows.Forms.ToolStripMenuItem();
|
||||
miNvSettings = new System.Windows.Forms.ToolStripMenuItem();
|
||||
lvNvPresetsToolTip = new System.Windows.Forms.ToolTip(components);
|
||||
miNvNovideo = new System.Windows.Forms.ToolStripMenuItem();
|
||||
mnuNvPresets.SuspendLayout();
|
||||
mnuNvSettings.SuspendLayout();
|
||||
SuspendLayout();
|
||||
@@ -601,9 +602,9 @@ namespace ColorControl.Services.NVIDIA
|
||||
// mnuNvSettings
|
||||
//
|
||||
mnuNvSettings.ImageScalingSize = new System.Drawing.Size(20, 20);
|
||||
mnuNvSettings.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvProfileInspector, miNvSettings });
|
||||
mnuNvSettings.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvProfileInspector, miNvNovideo, miNvSettings });
|
||||
mnuNvSettings.Name = "mnuLgButtons";
|
||||
mnuNvSettings.Size = new System.Drawing.Size(202, 48);
|
||||
mnuNvSettings.Size = new System.Drawing.Size(202, 92);
|
||||
//
|
||||
// miNvProfileInspector
|
||||
//
|
||||
@@ -619,6 +620,13 @@ namespace ColorControl.Services.NVIDIA
|
||||
miNvSettings.Text = "Settings";
|
||||
miNvSettings.Click += miNvSettings_Click;
|
||||
//
|
||||
// miNvNovideo
|
||||
//
|
||||
miNvNovideo.Name = "miNvNovideo";
|
||||
miNvNovideo.Size = new System.Drawing.Size(201, 22);
|
||||
miNvNovideo.Text = "Novideo sRGB";
|
||||
miNvNovideo.Click += miNvNovideo_Click;
|
||||
//
|
||||
// NvPanel
|
||||
//
|
||||
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
@@ -648,7 +656,6 @@ namespace ColorControl.Services.NVIDIA
|
||||
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
Name = "NvPanel";
|
||||
Size = new System.Drawing.Size(1114, 539);
|
||||
Load += RemoteControlPanel_Load;
|
||||
mnuNvPresets.ResumeLayout(false);
|
||||
mnuNvSettings.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
@@ -716,5 +723,6 @@ namespace ColorControl.Services.NVIDIA
|
||||
private System.Windows.Forms.TextBox edtNvOverclock;
|
||||
private System.Windows.Forms.ToolStripMenuItem mnuNvOverclocking;
|
||||
private System.Windows.Forms.ToolStripMenuItem miNvOverclockingIncluded;
|
||||
private System.Windows.Forms.ToolStripMenuItem miNvNovideo;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using ColorControl.Forms;
|
||||
using ColorControl.Native;
|
||||
using ColorControl.Services.Common;
|
||||
using novideo_srgb;
|
||||
using nspector;
|
||||
using nspector.Common;
|
||||
using nspector.Common.Meta;
|
||||
@@ -48,10 +49,13 @@ namespace ColorControl.Services.NVIDIA
|
||||
FormUtils.InitSortState(lvNvPresets, _config.NvPresetsSortState);
|
||||
|
||||
_nvService.AfterApplyPreset += NvServiceAfterApplyPreset;
|
||||
}
|
||||
|
||||
private void RemoteControlPanel_Load(object sender, EventArgs e)
|
||||
{
|
||||
MainViewModel.ConfigPath = _appContextProvider.GetAppContext().DataPath;
|
||||
|
||||
if (_nvService.Config.ApplyNovideoOnStartup)
|
||||
{
|
||||
MainWindow.CreateAndShow(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void lvLgPresets_ColumnClick(object sender, ColumnClickEventArgs e)
|
||||
@@ -562,7 +566,7 @@ namespace ColorControl.Services.NVIDIA
|
||||
var groupName = $"mnuNvDriverSettingGroup_{driverSetting.GroupName}";
|
||||
var groupItem = FormUtils.BuildMenuItem(contextMenuStrip?.Items ?? parent.DropDownItems, groupName, driverSetting.GroupName);
|
||||
|
||||
var itemName = $"mnuNvDriverSettingItem_{driverSetting.SettingText}";
|
||||
var itemName = $"mnuNvDriverSettingItem_{driverSetting.SettingId}";
|
||||
var item = FormUtils.BuildMenuItem(groupItem.DropDownItems, itemName, driverSetting.SettingText, driverSetting);
|
||||
var settingMeta = _nvService.GetSettingMeta(driverSetting.SettingId);
|
||||
|
||||
@@ -618,11 +622,18 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
if (settingMeta.SettingType == nspector.Native.NVAPI2.NVDRS_SETTING_TYPE.NVDRS_DWORD_TYPE)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
foreach (var settingValue in settingMeta.DwordValues)
|
||||
{
|
||||
var text = settingValue.ValueName;
|
||||
if (text.IndexOf(" (") == 10)
|
||||
{
|
||||
if (settingMeta.DwordValues.Take(index).Any(v => v.Value == settingValue.Value))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
text = text.Substring(0, 10);
|
||||
}
|
||||
|
||||
@@ -631,6 +642,8 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
subItem.Checked = presetSetting == settingValue.Value;
|
||||
subItem.Font = _menuItemFonts[subItem.Checked];
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1081,27 +1094,38 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
private void EditNvSettings()
|
||||
{
|
||||
var quickAccessField = new MessageForms.FieldDefinition
|
||||
{
|
||||
Label = "Quick Access shortcut",
|
||||
FieldType = MessageForms.FieldType.Shortcut,
|
||||
Value = _config.NvQuickAccessShortcut
|
||||
};
|
||||
|
||||
var enableOcField = new MessageForms.FieldDefinition
|
||||
{
|
||||
Label = "Enable overclocking",
|
||||
FieldType = MessageForms.FieldType.CheckBox,
|
||||
Value = _nvService.Config.ShowOverclocking
|
||||
};
|
||||
|
||||
var enableNovideoField = new MessageForms.FieldDefinition
|
||||
{
|
||||
Label = "Apply Novideo sRGB settings on startup",
|
||||
FieldType = MessageForms.FieldType.CheckBox,
|
||||
Value = _nvService.Config.ApplyNovideoOnStartup
|
||||
};
|
||||
|
||||
var values = MessageForms.ShowDialog("NVIDIA controller settings", new[] {
|
||||
new MessageForms.FieldDefinition
|
||||
{
|
||||
Label = "Quick Access shortcut",
|
||||
FieldType = MessageForms.FieldType.Shortcut,
|
||||
Value = _config.NvQuickAccessShortcut
|
||||
},
|
||||
new MessageForms.FieldDefinition
|
||||
{
|
||||
Label = "Enable overclocking",
|
||||
FieldType = MessageForms.FieldType.CheckBox,
|
||||
Value = _nvService.Config.ShowOverclocking
|
||||
}
|
||||
quickAccessField,
|
||||
enableOcField,
|
||||
enableNovideoField
|
||||
});
|
||||
if (!values.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var shortcut = values[0].Value.ToString();
|
||||
var enableOc = (bool)values[1].Value;
|
||||
var shortcut = (string)quickAccessField.Value;
|
||||
|
||||
var clear = !string.IsNullOrEmpty(_config.NvQuickAccessShortcut);
|
||||
|
||||
@@ -1109,7 +1133,8 @@ namespace ColorControl.Services.NVIDIA
|
||||
|
||||
Utils.RegisterShortcut(_mainHandle, SHORTCUTID_NVQA, _config.NvQuickAccessShortcut, clear);
|
||||
|
||||
_nvService.Config.ShowOverclocking = enableOc;
|
||||
_nvService.Config.ShowOverclocking = enableOcField.ValueAsBool;
|
||||
_nvService.Config.ApplyNovideoOnStartup = enableNovideoField.ValueAsBool;
|
||||
|
||||
UpdateInfo();
|
||||
}
|
||||
@@ -1499,5 +1524,10 @@ namespace ColorControl.Services.NVIDIA
|
||||
{
|
||||
UpdateDisplayInfoItems();
|
||||
}
|
||||
|
||||
private void miNvNovideo_Click(object sender, EventArgs e)
|
||||
{
|
||||
MainWindow.CreateAndShow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
class NvServiceConfig
|
||||
{
|
||||
public bool ShowOverclocking { get; set; }
|
||||
public bool ApplyNovideoOnStartup { get; set; }
|
||||
|
||||
public NvServiceConfig()
|
||||
{
|
||||
|
||||
@@ -269,6 +269,49 @@ namespace LgTv
|
||||
wisa_speaker,
|
||||
}
|
||||
|
||||
public enum MasterLuminanceLevel
|
||||
{
|
||||
[Description("540 nits")]
|
||||
_540nit,
|
||||
[Description("1000 nits")]
|
||||
_1000nit,
|
||||
[Description("4000 nits")]
|
||||
_4000nit
|
||||
}
|
||||
|
||||
public enum MasteringColor
|
||||
{
|
||||
auto,
|
||||
[Description("0")]
|
||||
_0,
|
||||
p3D65,
|
||||
bt2020D65,
|
||||
bt709D65,
|
||||
}
|
||||
|
||||
public enum MasteringNits
|
||||
{
|
||||
auto,
|
||||
[Description("0")]
|
||||
_0,
|
||||
[Description("400")]
|
||||
_400,
|
||||
[Description("540")]
|
||||
_540,
|
||||
[Description("700")]
|
||||
_700,
|
||||
[Description("1000")]
|
||||
_1000,
|
||||
[Description("2000")]
|
||||
_2000,
|
||||
[Description("3000")]
|
||||
_3000,
|
||||
[Description("4000")]
|
||||
_4000,
|
||||
[Description("10000")]
|
||||
_10000,
|
||||
}
|
||||
|
||||
public class LgTvApi : IDisposable
|
||||
{
|
||||
public bool ConnectionClosed { get => _connection?.ConnectionClosed ?? true; }
|
||||
@@ -435,6 +478,7 @@ namespace LgTv
|
||||
{
|
||||
await SetMute(!await IsMuted());
|
||||
}
|
||||
|
||||
public async Task<bool> IsMuted()
|
||||
{
|
||||
var requestMessage = new RequestMessage("status", "ssap://audio/getStatus");
|
||||
@@ -454,6 +498,7 @@ namespace LgTv
|
||||
{
|
||||
await _connection.SendCommandAsync(new RequestMessage("", "ssap://system/turnOff"));
|
||||
}
|
||||
|
||||
public async Task Reboot()
|
||||
{
|
||||
await ExecuteRequest("luna://com.webos.service.tv.power/reboot", new { reason = "" });
|
||||
@@ -704,6 +749,10 @@ namespace LgTv
|
||||
string jsonValue;
|
||||
var valueType = value.GetType();
|
||||
var intType = typeof(int);
|
||||
if (value.ToString().StartsWith("_"))
|
||||
{
|
||||
value = value.ToString().Substring(1);
|
||||
}
|
||||
if (value.ToString().StartsWith("bool_"))
|
||||
{
|
||||
value = bool.Parse(value.ToString().Substring(5));
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<Window x:Class="novideo_srgb.AboutWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
Title="About"
|
||||
SizeToContent="WidthAndHeight"
|
||||
ResizeMode="NoResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
SnapsToDevicePixels="True">
|
||||
<StackPanel Margin="6">
|
||||
<TextBlock FontSize="15" HorizontalAlignment="Center" TextAlignment="Center">
|
||||
novideo_srgb v4.0 by ledoge
|
||||
</TextBlock>
|
||||
<TextBlock HorizontalAlignment="Center" TextAlignment="Center">
|
||||
Licensed under GPLv3 <LineBreak /> <LineBreak />
|
||||
Source code and releases hosted at <LineBreak />
|
||||
<Hyperlink RequestNavigate="OnRequestNavigate" NavigateUri="https://github.com/ledoge/novideo_srgb">https://github.com/ledoge/novideo_srgb</Hyperlink>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public partial class AboutWindow : Window
|
||||
{
|
||||
public AboutWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
};
|
||||
System.Diagnostics.Process.Start(processStartInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
using EDIDParser;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class AdvancedViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private MonitorData _monitor;
|
||||
|
||||
private int _target;
|
||||
private bool _useIcc;
|
||||
private string _profilePath;
|
||||
private bool _calibrateGamma;
|
||||
private int _selectedGamma;
|
||||
private double _customGamma;
|
||||
private double _customPercentage;
|
||||
private bool _disableOptimization;
|
||||
|
||||
public AdvancedViewModel()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public AdvancedViewModel(MonitorData monitor)
|
||||
{
|
||||
_monitor = monitor;
|
||||
|
||||
_target = monitor.Target;
|
||||
_useIcc = monitor.UseIcc;
|
||||
_profilePath = monitor.ProfilePath;
|
||||
_calibrateGamma = monitor.CalibrateGamma;
|
||||
_selectedGamma = monitor.SelectedGamma;
|
||||
_customGamma = monitor.CustomGamma;
|
||||
_customPercentage = monitor.CustomPercentage;
|
||||
_disableOptimization = monitor.DisableOptimization;
|
||||
}
|
||||
|
||||
public void ApplyChanges()
|
||||
{
|
||||
ChangedCalibration |= _monitor.Target != _target;
|
||||
_monitor.Target = _target;
|
||||
ChangedCalibration |= _monitor.UseIcc != _useIcc;
|
||||
_monitor.UseIcc = _useIcc;
|
||||
ChangedCalibration |= _monitor.ProfilePath != _profilePath;
|
||||
_monitor.ProfilePath = _profilePath;
|
||||
ChangedCalibration |= _monitor.CalibrateGamma != _calibrateGamma;
|
||||
_monitor.CalibrateGamma = _calibrateGamma;
|
||||
ChangedCalibration |= _monitor.SelectedGamma != _selectedGamma;
|
||||
_monitor.SelectedGamma = _selectedGamma;
|
||||
ChangedCalibration |= _monitor.CustomGamma != _customGamma;
|
||||
_monitor.CustomGamma = _customGamma;
|
||||
ChangedCalibration |= _monitor.CustomPercentage != _customPercentage;
|
||||
_monitor.CustomPercentage = _customPercentage;
|
||||
ChangedCalibration |= _monitor.DisableOptimization != _disableOptimization;
|
||||
_monitor.DisableOptimization = _disableOptimization;
|
||||
}
|
||||
|
||||
public ChromaticityCoordinates Coords => _monitor.Edid.DisplayParameters.ChromaticityCoordinates;
|
||||
|
||||
public bool UseEdid
|
||||
{
|
||||
set
|
||||
{
|
||||
if (!value == _useIcc) return;
|
||||
_useIcc = !value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(UseIcc));
|
||||
OnPropertyChanged(nameof(EdidWarning));
|
||||
}
|
||||
get => !_useIcc;
|
||||
}
|
||||
|
||||
public bool UseIcc
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _useIcc) return;
|
||||
_useIcc = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(UseEdid));
|
||||
OnPropertyChanged(nameof(EdidWarning));
|
||||
}
|
||||
get => _useIcc;
|
||||
}
|
||||
|
||||
public string ProfilePath
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _profilePath) return;
|
||||
_profilePath = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ProfileName));
|
||||
}
|
||||
get => _profilePath;
|
||||
}
|
||||
|
||||
public string ProfileName => Path.GetFileName(ProfilePath);
|
||||
|
||||
public bool CalibrateGamma
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _calibrateGamma) return;
|
||||
_calibrateGamma = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
get => _calibrateGamma;
|
||||
}
|
||||
|
||||
public int SelectedGamma
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _selectedGamma) return;
|
||||
_selectedGamma = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(UseCustomGamma));
|
||||
}
|
||||
get => _selectedGamma;
|
||||
}
|
||||
|
||||
public Visibility UseCustomGamma =>
|
||||
SelectedGamma == 2 || SelectedGamma == 3 ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public double CustomGamma
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _customGamma) return;
|
||||
_customGamma = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
get => _customGamma;
|
||||
}
|
||||
|
||||
public int Target
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _target) return;
|
||||
_target = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(EdidWarning));
|
||||
}
|
||||
get => _target;
|
||||
}
|
||||
|
||||
public Visibility HdrWarning => _monitor.HdrActive ? Visibility.Visible : Visibility.Collapsed;
|
||||
public Visibility EdidWarning => HdrWarning != Visibility.Visible && UseEdid && Colorimetry.ColorSpaces[_target].Equals(_monitor.EdidColorSpace)
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
public double CustomPercentage
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _customPercentage) return;
|
||||
_customPercentage = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
get => _customPercentage;
|
||||
}
|
||||
|
||||
public bool DisableOptimization
|
||||
{
|
||||
set
|
||||
{
|
||||
if (value == _disableOptimization) return;
|
||||
_disableOptimization = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
get => _disableOptimization;
|
||||
}
|
||||
|
||||
public bool ChangedCalibration { get; set; }
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string name = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<Window x:Class="novideo_srgb.AdvancedWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
xmlns:local="clr-namespace:novideo_srgb"
|
||||
Title="Advanced"
|
||||
ResizeMode="NoResize"
|
||||
SizeToContent="WidthAndHeight"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<d:Window.DataContext>
|
||||
<local:AdvancedViewModel />
|
||||
</d:Window.DataContext>
|
||||
<Window.Resources>
|
||||
<Style x:Key="TextBoxInError" TargetType="{x:Type TextBox}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Validation.HasError" Value="true">
|
||||
<Setter Property="ToolTip"
|
||||
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
|
||||
Path=(Validation.Errors)/ErrorContent}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<StackPanel Margin="6,0,6,6">
|
||||
<GroupBox Header="Calibration" Padding="6">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
|
||||
<TextBlock VerticalAlignment="Center" Text="Target" />
|
||||
<ComboBox SelectedIndex="{Binding Target}" Margin="6,0,0,0">
|
||||
<ComboBoxItem>sRGB/BT.709</ComboBoxItem>
|
||||
<ComboBoxItem>Display P3</ComboBoxItem>
|
||||
<ComboBoxItem>Adobe RGB</ComboBoxItem>
|
||||
<ComboBoxItem>BT.2020</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<RadioButton IsChecked="{Binding UseEdid}" Margin="0,0,0, 6">
|
||||
<StackPanel IsEnabled="{Binding UseEdid}">
|
||||
<TextBlock Text="Use EDID primaries" Margin="0,0,0,0" />
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Label HorizontalAlignment="Right" Grid.Row="0" Grid.Column="0">Red </Label>
|
||||
<TextBox IsEnabled="False"
|
||||
Text="{Binding Coords.RedX, Mode=OneWay, StringFormat={}{0:#,0.000}}"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="0" Grid.Column="1"
|
||||
VerticalAlignment="Center" />
|
||||
<Label Grid.Row="0" Grid.Column="2">x</Label>
|
||||
<TextBox IsEnabled="False"
|
||||
Text="{Binding Coords.RedY, Mode=OneWay, StringFormat={}{0:#,0.000}}"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="0" Grid.Column="3"
|
||||
VerticalAlignment="Center" />
|
||||
<Label Grid.Row="0" Grid.Column="4">y</Label>
|
||||
<Label HorizontalAlignment="Right" Grid.Row="1" Grid.Column="0">Green </Label>
|
||||
<TextBox IsEnabled="False"
|
||||
Text="{Binding Coords.GreenX, Mode=OneWay, StringFormat={}{0:#,0.000}}"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="1" Grid.Column="1"
|
||||
VerticalAlignment="Center" />
|
||||
<Label Grid.Row="1" Grid.Column="2">x</Label>
|
||||
<TextBox IsEnabled="False"
|
||||
Text="{Binding Coords.GreenY, Mode=OneWay, StringFormat={}{0:#,0.000}}"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="1" Grid.Column="3"
|
||||
VerticalAlignment="Center" />
|
||||
<Label Grid.Row="1" Grid.Column="4">y</Label>
|
||||
<Label HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0">Blue </Label>
|
||||
<TextBox IsEnabled="False"
|
||||
Text="{Binding Coords.BlueX, Mode=OneWay, StringFormat={}{0:#,0.000}}"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="2" Grid.Column="1"
|
||||
VerticalAlignment="Center" />
|
||||
<Label Grid.Row="2" Grid.Column="2">x</Label>
|
||||
<TextBox IsEnabled="False"
|
||||
Text="{Binding Coords.BlueY, Mode=OneWay, StringFormat={}{0:#,0.000}}"
|
||||
IsReadOnly="True"
|
||||
Grid.Row="2" Grid.Column="3"
|
||||
VerticalAlignment="Center" />
|
||||
<Label Grid.Row="2" Grid.Column="4">y</Label>
|
||||
</Grid>
|
||||
<TextBlock TextWrapping="Wrap" Visibility="{Binding EdidWarning}" Foreground="Red">Primaries match target – cannot clamp</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Visibility="{Binding HdrWarning}" Foreground="Red">HDR is active – cannot clamp</TextBlock>
|
||||
</StackPanel>
|
||||
</RadioButton>
|
||||
<RadioButton IsChecked="{Binding UseIcc}" HorizontalContentAlignment="Stretch">
|
||||
<StackPanel IsEnabled="{Binding UseIcc}">
|
||||
<TextBlock Text="Use ICC profile" Margin="0,0,0,6" />
|
||||
<Grid Margin="0,0,0, 6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox IsEnabled="False" Grid.Column="0" IsReadOnly="True"
|
||||
Text="{Binding ProfileName, Mode=OneWay}" />
|
||||
<Button Grid.Column="1"
|
||||
Content="Browse..."
|
||||
Click="Browse_Click"
|
||||
Width="75" Margin="6,0,0,0" />
|
||||
</Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox IsChecked="{Binding CalibrateGamma}" VerticalAlignment="Center">Calibrate gamma to</CheckBox>
|
||||
<ComboBox IsEnabled="{Binding CalibrateGamma}" SelectedIndex="{Binding SelectedGamma}"
|
||||
VerticalAlignment="Center" Margin="6,0,0,0">
|
||||
<ComboBoxItem>sRGB</ComboBoxItem>
|
||||
<ComboBoxItem>BT.1886</ComboBoxItem>
|
||||
<ComboBoxItem>Absolute</ComboBoxItem>
|
||||
<ComboBoxItem>Relative</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<TextBox IsEnabled="{Binding CalibrateGamma}"
|
||||
Visibility="{Binding UseCustomGamma, Mode=OneWay}"
|
||||
Style="{StaticResource TextBoxInError}"
|
||||
VerticalContentAlignment="Center" Width="50" Margin="6,0,0,0">
|
||||
<TextBox.Text>
|
||||
<Binding Path="CustomGamma"
|
||||
UpdateSourceTrigger="PropertyChanged">
|
||||
<Binding.ValidationRules>
|
||||
<local:RangeRule Min="1" Max="4" />
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
</StackPanel>
|
||||
<StackPanel Visibility="{Binding UseCustomGamma}" IsEnabled="{Binding CalibrateGamma}"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center">with</TextBlock>
|
||||
<TextBox
|
||||
VerticalAlignment="Center" VerticalContentAlignment="Center" TextAlignment="Right"
|
||||
Height="22" Width="50" Style="{StaticResource TextBoxInError}" Margin="6,0,0,0">
|
||||
<TextBox.Text>
|
||||
<Binding Path="CustomPercentage"
|
||||
UpdateSourceTrigger="PropertyChanged">
|
||||
<Binding.ValidationRules>
|
||||
<local:RangeRule Min="0" Max="100" />
|
||||
</Binding.ValidationRules>
|
||||
</Binding>
|
||||
</TextBox.Text>
|
||||
</TextBox>
|
||||
<TextBlock VerticalAlignment="Center" Margin="2,0,0,0">% black output offset</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,2,0,0">
|
||||
<CheckBox IsChecked="{Binding DisableOptimization}" VerticalAlignment="Center">Disable 8-bit color optimization</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</RadioButton>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,6,0,0">
|
||||
<Button Width="75" Click="OK_Click" Margin="0,0,6,0" IsDefault="True">OK</Button>
|
||||
<Button Width="75" IsCancel="True">Cancel</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public partial class AdvancedWindow
|
||||
{
|
||||
private AdvancedViewModel _viewModel;
|
||||
|
||||
public AdvancedWindow(MonitorData monitor)
|
||||
{
|
||||
_viewModel = new AdvancedViewModel(monitor);
|
||||
DataContext = _viewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private static string BrowseProfiles()
|
||||
{
|
||||
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Filter = "ICC Profiles|*.icc;*.icm"
|
||||
};
|
||||
|
||||
var result = dlg.ShowDialog();
|
||||
|
||||
return result == true ? dlg.FileName : null;
|
||||
}
|
||||
|
||||
private void Browse_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var profilePath = BrowseProfiles();
|
||||
if (!string.IsNullOrEmpty(profilePath))
|
||||
{
|
||||
_viewModel.ProfilePath = profilePath;
|
||||
}
|
||||
}
|
||||
|
||||
private void OK_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_viewModel.ApplyChanges();
|
||||
DialogResult = true;
|
||||
}
|
||||
|
||||
public bool ChangedCalibration => _viewModel.ChangedCalibration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// credit to https://mina86.com/2019/srgb-xyz-matrix/ and http://www.brucelindbloom.com/ for the math
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public static class Colorimetry
|
||||
{
|
||||
public struct Point
|
||||
{
|
||||
public bool Equals(Point other)
|
||||
{
|
||||
return X.Equals(other.X) && Y.Equals(other.Y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Point other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (X.GetHashCode() * 397) ^ Y.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public double X;
|
||||
public double Y;
|
||||
}
|
||||
|
||||
public struct ColorSpace
|
||||
{
|
||||
public bool Equals(ColorSpace other)
|
||||
{
|
||||
return Red.Equals(other.Red) && Green.Equals(other.Green) && Blue.Equals(other.Blue) &&
|
||||
White.Equals(other.White);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ColorSpace other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = Red.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Green.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Blue.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ White.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public Point Red;
|
||||
public Point Green;
|
||||
public Point Blue;
|
||||
public Point White;
|
||||
}
|
||||
|
||||
public static Point D65 = new Point { X = 0.3127, Y = 0.3290 };
|
||||
|
||||
public static ColorSpace sRGB = new ColorSpace
|
||||
{
|
||||
Red = new Point { X = 0.64, Y = 0.33 },
|
||||
Green = new Point { X = 0.3, Y = 0.6 },
|
||||
Blue = new Point { X = 0.15, Y = 0.06 },
|
||||
White = D65
|
||||
};
|
||||
|
||||
public static ColorSpace DisplayP3 = new ColorSpace
|
||||
{
|
||||
Red = new Point { X = 0.68, Y = 0.32 },
|
||||
Green = new Point { X = 0.265, Y = 0.69 },
|
||||
Blue = new Point { X = 0.15, Y = 0.06 },
|
||||
White = D65
|
||||
};
|
||||
|
||||
public static ColorSpace AdobeRGB = new ColorSpace
|
||||
{
|
||||
Red = new Point { X = 0.64, Y = 0.33 },
|
||||
Green = new Point { X = 0.21, Y = 0.71 },
|
||||
Blue = new Point { X = 0.15, Y = 0.06 },
|
||||
White = D65
|
||||
};
|
||||
|
||||
public static ColorSpace BT2020 = new ColorSpace
|
||||
{
|
||||
Red = new Point { X = 0.708, Y = 0.292 },
|
||||
Green = new Point { X = 0.17, Y = 0.797 },
|
||||
Blue = new Point { X = 0.131, Y = 0.046 },
|
||||
White = D65
|
||||
};
|
||||
|
||||
public static ColorSpace[] ColorSpaces => new[] { sRGB, DisplayP3, AdobeRGB, BT2020 };
|
||||
|
||||
public static Matrix D50 = Matrix.FromValues(new[,] { { 0.9642 }, { 1 }, { 0.8249 } });
|
||||
|
||||
public static Matrix RGBToXYZ(ColorSpace colorSpace)
|
||||
{
|
||||
var red = colorSpace.Red;
|
||||
var green = colorSpace.Green;
|
||||
var blue = colorSpace.Blue;
|
||||
var white = colorSpace.White;
|
||||
var whiteXYZ = Matrix.FromValues(new[,]
|
||||
{ { white.X / white.Y }, { 1 }, { (1 - white.X - white.Y) / white.Y } });
|
||||
|
||||
var Mprime = Matrix.FromValues(new[,]
|
||||
{
|
||||
{ red.X / red.Y, green.X / green.Y, blue.X / blue.Y },
|
||||
{ 1, 1, 1 },
|
||||
{ (1 - red.X - red.Y) / red.Y, (1 - green.X - green.Y) / green.Y, (1 - blue.X - blue.Y) / blue.Y }
|
||||
});
|
||||
|
||||
return Mprime * Matrix.FromDiagonal(Mprime.Inverse() * whiteXYZ);
|
||||
}
|
||||
|
||||
public static Matrix XYZToRGB(ColorSpace colorSpace)
|
||||
{
|
||||
return RGBToXYZ(colorSpace).Inverse();
|
||||
}
|
||||
|
||||
public static Matrix RGBToRGB(ColorSpace from, ColorSpace to)
|
||||
{
|
||||
var result = XYZToRGB(to) * RGBToXYZ(from);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Matrix RGBToAdaptedXYZ(ColorSpace colorspace, Matrix whiteXYZ)
|
||||
{
|
||||
var xyz = RGBToXYZ(colorspace);
|
||||
var bradford = Matrix.FromValues(new[,]
|
||||
{
|
||||
{ 0.8951, 0.2664, -0.1614 },
|
||||
{ -0.7502, 1.7135, 0.0367 },
|
||||
{ 0.0389, -0.0685, 1.0296 }
|
||||
});
|
||||
var ws = colorspace.White;
|
||||
var aws = bradford * Matrix.FromValues(new[,]
|
||||
{
|
||||
{ ws.X / ws.Y }, { 1 }, { (1 - ws.X - ws.Y) / ws.Y }
|
||||
});
|
||||
var awd = bradford * whiteXYZ;
|
||||
var m = bradford.Inverse() * Matrix.FromDiagonal(new[]
|
||||
{ awd[0] / aws[0], awd[1] / aws[1], awd[2] / aws[2] }) * bradford;
|
||||
return m * xyz;
|
||||
}
|
||||
|
||||
public static Matrix RGBToPCSXYZ(ColorSpace colorspace)
|
||||
{
|
||||
return RGBToAdaptedXYZ(colorspace, D50);
|
||||
}
|
||||
|
||||
public static Matrix PCSXYZToRGB(ColorSpace colorspace)
|
||||
{
|
||||
return RGBToPCSXYZ(colorspace).Inverse();
|
||||
}
|
||||
|
||||
public static Matrix XYZScale(Matrix matrix, Matrix target)
|
||||
{
|
||||
var result = Matrix.Zero3x3();
|
||||
var white = matrix * Matrix.One3x1();
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
result[i, j] = matrix[i, j] * target[i] / white[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Matrix XYZScaleToD50(Matrix matrix)
|
||||
{
|
||||
return XYZScale(matrix, D50);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public static class DisplayConfigManager
|
||||
{
|
||||
[DllImport("user32")]
|
||||
private static extern int GetDisplayConfigBufferSizes(QDC flags, out int numPathArrayElements, out int numModeInfoArrayElements);
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern int QueryDisplayConfig(QDC flags, ref int numPathArrayElements, [In, Out] DISPLAYCONFIG_PATH_INFO[] pathArray, ref int numModeInfoArrayElements, [In, Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, out DISPLAYCONFIG_TOPOLOGY_ID currentTopologyId);
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern int QueryDisplayConfig(QDC flags, ref int numPathArrayElements, [In, Out] DISPLAYCONFIG_PATH_INFO[] pathArray, ref int numModeInfoArrayElements, [In, Out] DISPLAYCONFIG_MODE_INFO[] modeInfoArray, IntPtr currentTopologyId);
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO requestPacket);
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DEVICE_NAME requestPacket);
|
||||
|
||||
[DllImport("user32")]
|
||||
private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_TARGET_DEVICE_NAME requestPacket);
|
||||
|
||||
public static HashSet<string> GetHdrDisplayPaths()
|
||||
{
|
||||
Action<int> check = (e) =>
|
||||
{
|
||||
if (e != 0)
|
||||
{
|
||||
throw new Win32Exception(e);
|
||||
}
|
||||
};
|
||||
|
||||
check(GetDisplayConfigBufferSizes(QDC.QDC_ONLY_ACTIVE_PATHS, out var pathCount, out var modeCount));
|
||||
|
||||
var paths = new DISPLAYCONFIG_PATH_INFO[pathCount];
|
||||
var modes = new DISPLAYCONFIG_MODE_INFO[modeCount];
|
||||
|
||||
check(QueryDisplayConfig(QDC.QDC_ONLY_ACTIVE_PATHS, ref pathCount, paths, ref modeCount, modes, IntPtr.Zero));
|
||||
|
||||
var result = new HashSet<string>();
|
||||
|
||||
Array.ForEach(paths, path =>
|
||||
{
|
||||
var displayInfo = new DISPLAYCONFIG_TARGET_DEVICE_NAME();
|
||||
displayInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
|
||||
displayInfo.header.size = Marshal.SizeOf<DISPLAYCONFIG_TARGET_DEVICE_NAME>();
|
||||
displayInfo.header.adapterId = path.targetInfo.adapterId;
|
||||
displayInfo.header.id = path.targetInfo.id;
|
||||
|
||||
check(DisplayConfigGetDeviceInfo(ref displayInfo));
|
||||
|
||||
var colorInfo = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO();
|
||||
colorInfo.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
|
||||
colorInfo.header.size = Marshal.SizeOf<DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO>();
|
||||
colorInfo.header.adapterId = path.targetInfo.adapterId;
|
||||
colorInfo.header.id = path.targetInfo.id;
|
||||
|
||||
check(DisplayConfigGetDeviceInfo(ref colorInfo));
|
||||
|
||||
if (colorInfo.advancedColorEnabled)
|
||||
{
|
||||
result.Add(displayInfo.monitorDevicePath);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_DEVICE_INFO_TYPE
|
||||
{
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4,
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION = 7,
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION = 8,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9,
|
||||
DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10,
|
||||
DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_COLOR_ENCODING
|
||||
{
|
||||
DISPLAYCONFIG_COLOR_ENCODING_RGB = 0,
|
||||
DISPLAYCONFIG_COLOR_ENCODING_YCBCR444 = 1,
|
||||
DISPLAYCONFIG_COLOR_ENCODING_YCBCR422 = 2,
|
||||
DISPLAYCONFIG_COLOR_ENCODING_YCBCR420 = 3,
|
||||
DISPLAYCONFIG_COLOR_ENCODING_INTENSITY = 4,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_SCALING
|
||||
{
|
||||
DISPLAYCONFIG_SCALING_IDENTITY = 1,
|
||||
DISPLAYCONFIG_SCALING_CENTERED = 2,
|
||||
DISPLAYCONFIG_SCALING_STRETCHED = 3,
|
||||
DISPLAYCONFIG_SCALING_ASPECTRATIOCENTEREDMAX = 4,
|
||||
DISPLAYCONFIG_SCALING_CUSTOM = 5,
|
||||
DISPLAYCONFIG_SCALING_PREFERRED = 128,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_ROTATION
|
||||
{
|
||||
DISPLAYCONFIG_ROTATION_IDENTITY = 1,
|
||||
DISPLAYCONFIG_ROTATION_ROTATE90 = 2,
|
||||
DISPLAYCONFIG_ROTATION_ROTATE180 = 3,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY
|
||||
{
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = -1,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 = 0,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SVIDEO = 1,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPOSITE_VIDEO = 2,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPONENT_VIDEO = 3,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DVI = 4,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI = 5,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_LVDS = 6,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_D_JPN = 8,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDI = 9,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL = 10,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED = 11,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EXTERNAL = 12,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED = 13,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDTVDONGLE = 14,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_MIRACAST = 15,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_WIRED = 16,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INDIRECT_VIRTUAL = 17,
|
||||
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL = unchecked((int)0x80000000),
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_TOPOLOGY_ID
|
||||
{
|
||||
DISPLAYCONFIG_TOPOLOGY_INTERNAL = 0x00000001,
|
||||
DISPLAYCONFIG_TOPOLOGY_CLONE = 0x00000002,
|
||||
DISPLAYCONFIG_TOPOLOGY_EXTEND = 0x00000004,
|
||||
DISPLAYCONFIG_TOPOLOGY_EXTERNAL = 0x00000008,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_PATH
|
||||
{
|
||||
DISPLAYCONFIG_PATH_ACTIVE = 0x00000001,
|
||||
DISPLAYCONFIG_PATH_PREFERRED_UNSCALED = 0x00000004,
|
||||
DISPLAYCONFIG_PATH_SUPPORT_VIRTUAL_MODE = 0x00000008,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_SOURCE_FLAGS
|
||||
{
|
||||
DISPLAYCONFIG_SOURCE_IN_USE = 0x00000001,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_TARGET_FLAGS
|
||||
{
|
||||
DISPLAYCONFIG_TARGET_IN_USE = 0x00000001,
|
||||
DISPLAYCONFIG_TARGET_FORCIBLE = 0x00000002,
|
||||
DISPLAYCONFIG_TARGET_FORCED_AVAILABILITY_BOOT = 0x00000004,
|
||||
DISPLAYCONFIG_TARGET_FORCED_AVAILABILITY_PATH = 0x00000008,
|
||||
DISPLAYCONFIG_TARGET_FORCED_AVAILABILITY_SYSTEM = 0x00000010,
|
||||
DISPLAYCONFIG_TARGET_IS_HMD = 0x00000020,
|
||||
}
|
||||
|
||||
internal enum QDC
|
||||
{
|
||||
QDC_ALL_PATHS = 0x00000001,
|
||||
QDC_ONLY_ACTIVE_PATHS = 0x00000002,
|
||||
QDC_DATABASE_CURRENT = 0x00000004,
|
||||
QDC_VIRTUAL_MODE_AWARE = 0x00000010,
|
||||
QDC_INCLUDE_HMD = 0x00000020,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_SCANLINE_ORDERING
|
||||
{
|
||||
DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED = 0,
|
||||
DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE = 1,
|
||||
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED = 2,
|
||||
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST = DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED,
|
||||
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST = 3,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_PIXELFORMAT
|
||||
{
|
||||
DISPLAYCONFIG_PIXELFORMAT_8BPP = 1,
|
||||
DISPLAYCONFIG_PIXELFORMAT_16BPP = 2,
|
||||
DISPLAYCONFIG_PIXELFORMAT_24BPP = 3,
|
||||
DISPLAYCONFIG_PIXELFORMAT_32BPP = 4,
|
||||
DISPLAYCONFIG_PIXELFORMAT_NONGDI = 5,
|
||||
}
|
||||
|
||||
internal enum DISPLAYCONFIG_MODE_INFO_TYPE
|
||||
{
|
||||
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1,
|
||||
DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2,
|
||||
DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_DEVICE_INFO_HEADER
|
||||
{
|
||||
public DISPLAYCONFIG_DEVICE_INFO_TYPE type;
|
||||
public int size;
|
||||
public LUID adapterId;
|
||||
public uint id;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO
|
||||
{
|
||||
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
|
||||
public uint value;
|
||||
public DISPLAYCONFIG_COLOR_ENCODING colorEncoding;
|
||||
public int bitsPerColorChannel;
|
||||
|
||||
public bool advancedColorSupported => (value & 0x1) == 0x1;
|
||||
public bool advancedColorEnabled => (value & 0x2) == 0x2;
|
||||
public bool wideColorEnforced => (value & 0x4) == 0x4;
|
||||
public bool advancedColorForceDisabled => (value & 0x8) == 0x8;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct POINTL
|
||||
{
|
||||
public int x;
|
||||
public int y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct LUID
|
||||
{
|
||||
public uint LowPart;
|
||||
public int HighPart;
|
||||
|
||||
public long Value => ((long)HighPart << 32) | LowPart;
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_SOURCE_MODE
|
||||
{
|
||||
public uint width;
|
||||
public uint height;
|
||||
public DISPLAYCONFIG_PIXELFORMAT pixelFormat;
|
||||
public POINTL position;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_RATIONAL
|
||||
{
|
||||
public uint Numerator;
|
||||
public uint Denominator;
|
||||
|
||||
public override string ToString() => Numerator + " / " + Denominator;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_2DREGION
|
||||
{
|
||||
public uint cx;
|
||||
public uint cy;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_DESKTOP_IMAGE_INFO
|
||||
{
|
||||
public POINTL PathSourceSize;
|
||||
public RECT DesktopImageRegion;
|
||||
public RECT DesktopImageClip;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_VIDEO_SIGNAL_INFO
|
||||
{
|
||||
public ulong pixelRate;
|
||||
public DISPLAYCONFIG_RATIONAL hSyncFreq;
|
||||
public DISPLAYCONFIG_RATIONAL vSyncFreq;
|
||||
public DISPLAYCONFIG_2DREGION activeSize;
|
||||
public DISPLAYCONFIG_2DREGION totalSize;
|
||||
public uint videoStandard;
|
||||
public DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_TARGET_MODE
|
||||
{
|
||||
public DISPLAYCONFIG_VIDEO_SIGNAL_INFO targetVideoSignalInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
internal struct DISPLAYCONFIG_MODE_INFO_union
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public DISPLAYCONFIG_TARGET_MODE targetMode;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public DISPLAYCONFIG_SOURCE_MODE sourceMode;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public DISPLAYCONFIG_DESKTOP_IMAGE_INFO desktopImageInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_PATH_SOURCE_INFO
|
||||
{
|
||||
public LUID adapterId;
|
||||
public uint id;
|
||||
public uint modeInfoIdx;
|
||||
public DISPLAYCONFIG_SOURCE_FLAGS statusFlags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_PATH_TARGET_INFO
|
||||
{
|
||||
public LUID adapterId;
|
||||
public uint id;
|
||||
public uint modeInfoIdx;
|
||||
public DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology;
|
||||
public DISPLAYCONFIG_ROTATION rotation;
|
||||
public DISPLAYCONFIG_SCALING scaling;
|
||||
public DISPLAYCONFIG_RATIONAL refreshRate;
|
||||
public DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering;
|
||||
public bool targetAvailable;
|
||||
public DISPLAYCONFIG_TARGET_FLAGS statusFlags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_PATH_INFO
|
||||
{
|
||||
public DISPLAYCONFIG_PATH_SOURCE_INFO sourceInfo;
|
||||
public DISPLAYCONFIG_PATH_TARGET_INFO targetInfo;
|
||||
public DISPLAYCONFIG_PATH flags;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct DISPLAYCONFIG_MODE_INFO
|
||||
{
|
||||
public DISPLAYCONFIG_MODE_INFO_TYPE infoType;
|
||||
public uint id;
|
||||
public LUID adapterId;
|
||||
public DISPLAYCONFIG_MODE_INFO_union info;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct DISPLAYCONFIG_SOURCE_DEVICE_NAME
|
||||
{
|
||||
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
|
||||
public string viewGdiDeviceName;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS
|
||||
{
|
||||
public uint value;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
internal struct DISPLAYCONFIG_TARGET_DEVICE_NAME
|
||||
{
|
||||
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
|
||||
public DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags;
|
||||
public DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology;
|
||||
public ushort edidManufactureId;
|
||||
public ushort edidProductCodeId;
|
||||
public uint connectorInstance;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
|
||||
public string monitorFriendlyDeviceName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string monitorDevicePath;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct RECT
|
||||
{
|
||||
public int left;
|
||||
public int top;
|
||||
public int right;
|
||||
public int bottom;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class DoubleToneCurve : ToneCurve
|
||||
{
|
||||
private double[] _values;
|
||||
|
||||
public DoubleToneCurve(double[] values)
|
||||
{
|
||||
_values = values;
|
||||
}
|
||||
|
||||
public double SampleAt(double x)
|
||||
{
|
||||
if (x == 0) return _values[0];
|
||||
if (x >= 1) return _values[_values.Length - 1];
|
||||
|
||||
var index = x * (_values.Length - 1);
|
||||
var frac = index - (uint)index;
|
||||
return _values[(uint)index] * (1 - frac) + _values[(uint)index + 1] * frac;
|
||||
}
|
||||
|
||||
public double SampleInverseAt(double x)
|
||||
{
|
||||
if (_values[0] >= x) return 0;
|
||||
if (_values[_values.Length - 1] <= x) return 1;
|
||||
|
||||
var lowerIndex = -1;
|
||||
for (var i = 0; i < _values.Length; i++)
|
||||
{
|
||||
if (x >= _values[i])
|
||||
{
|
||||
lowerIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
var low = _values[lowerIndex];
|
||||
var high = _values[lowerIndex + 1];
|
||||
var frac = (x - low) / (high - low);
|
||||
|
||||
return (lowerIndex + frac) / (_values.Length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class GammaToneCurve : ToneCurve
|
||||
{
|
||||
private readonly double _gamma;
|
||||
private double _a = 1;
|
||||
private double _b;
|
||||
private readonly double _c;
|
||||
|
||||
public unsafe GammaToneCurve(double gamma, double black = 0, double outputOffset = 1, bool relative = false)
|
||||
{
|
||||
if (black == 0)
|
||||
{
|
||||
_gamma = gamma;
|
||||
return;
|
||||
}
|
||||
|
||||
if (outputOffset == 1)
|
||||
{
|
||||
_gamma = !relative
|
||||
? gamma
|
||||
: Math.Log((black - 1) * Math.Pow(2, gamma) / (black * Math.Pow(2, gamma) - 1), 2);
|
||||
_a = 1 - black;
|
||||
_c = black;
|
||||
}
|
||||
else
|
||||
{
|
||||
var outBlack = outputOffset * black;
|
||||
var btWhite = 1 - outBlack;
|
||||
var btBlack = black - outBlack;
|
||||
_c = outBlack;
|
||||
|
||||
if (!relative)
|
||||
{
|
||||
_gamma = gamma;
|
||||
CalculateBT1886(btWhite, btBlack);
|
||||
}
|
||||
else
|
||||
{
|
||||
// assume sane values for black and gamma
|
||||
double lowD = 1;
|
||||
double highD = 8;
|
||||
|
||||
// what the hell
|
||||
var low = *(ulong*)&lowD;
|
||||
var high = *(ulong*)&highD;
|
||||
|
||||
var target = Math.Pow(0.5, gamma);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var mid = (low + high) / 2;
|
||||
_gamma = *(double*)∣
|
||||
CalculateBT1886(btWhite, btBlack);
|
||||
var sample = SampleAt(0.5);
|
||||
if (sample == target || low == mid || high == mid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (sample > target)
|
||||
{
|
||||
low = mid;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CalculateBT1886(double white, double black)
|
||||
{
|
||||
var lwg = Math.Pow(white, 1 / _gamma);
|
||||
var lbg = Math.Pow(black, 1 / _gamma);
|
||||
_a = Math.Pow(lwg - lbg, _gamma);
|
||||
_b = lbg / (lwg - lbg);
|
||||
}
|
||||
|
||||
public double SampleAt(double x)
|
||||
{
|
||||
if (x >= 1) return 1;
|
||||
return _a * Math.Pow(Math.Max(x + _b, 0), _gamma) + _c;
|
||||
}
|
||||
|
||||
public double SampleInverseAt(double x)
|
||||
{
|
||||
if (_a != 1) throw new NotSupportedException();
|
||||
if (x >= 1) return 1;
|
||||
return Math.Pow(x, 1 / _gamma);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class ICCBinaryReader : BinaryReader
|
||||
{
|
||||
public ICCBinaryReader(Stream stream) : base(stream)
|
||||
{
|
||||
}
|
||||
|
||||
public override short ReadInt16()
|
||||
{
|
||||
return IPAddress.NetworkToHostOrder(base.ReadInt16());
|
||||
}
|
||||
|
||||
public override ushort ReadUInt16()
|
||||
{
|
||||
return (ushort)ReadInt16();
|
||||
}
|
||||
|
||||
public override int ReadInt32()
|
||||
{
|
||||
return IPAddress.NetworkToHostOrder(base.ReadInt32());
|
||||
}
|
||||
|
||||
public override uint ReadUInt32()
|
||||
{
|
||||
return (uint)ReadInt32();
|
||||
}
|
||||
|
||||
public override long ReadInt64()
|
||||
{
|
||||
return IPAddress.NetworkToHostOrder(base.ReadInt64());
|
||||
}
|
||||
|
||||
public override ulong ReadUInt64()
|
||||
{
|
||||
return (ulong)ReadInt64();
|
||||
}
|
||||
|
||||
public float ReadU8Fixed8()
|
||||
{
|
||||
return ReadUInt16() / 256f;
|
||||
}
|
||||
|
||||
public double ReadS15Fixed16()
|
||||
{
|
||||
return ReadInt32() / 65536d;
|
||||
}
|
||||
|
||||
public double ReadCIEXYZ()
|
||||
{
|
||||
return ReadUInt16() / 32768d;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class ICCMatrixProfile
|
||||
{
|
||||
public Matrix matrix = Matrix.Zero3x3();
|
||||
public ToneCurve[] trcs = new ToneCurve[3];
|
||||
public ToneCurve[] vcgt;
|
||||
|
||||
private ICCMatrixProfile()
|
||||
{
|
||||
}
|
||||
|
||||
public static ICCMatrixProfile FromFile(string path)
|
||||
{
|
||||
var result = new ICCMatrixProfile();
|
||||
|
||||
using (var reader = new ICCBinaryReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
{
|
||||
var stream = reader.BaseStream;
|
||||
|
||||
{
|
||||
stream.Seek(0x24, SeekOrigin.Begin);
|
||||
var magic = new string(reader.ReadChars(4));
|
||||
if (magic != "acsp")
|
||||
{
|
||||
throw new ICCProfileException("Not an ICC profile");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
stream.Seek(0xC, SeekOrigin.Begin);
|
||||
var type = new string(reader.ReadChars(4));
|
||||
if (type != "mntr")
|
||||
{
|
||||
throw new ICCProfileException("Not a display device profile");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
stream.Seek(0x10, SeekOrigin.Begin);
|
||||
var spaces = new string(reader.ReadChars(8));
|
||||
if (spaces != "RGB XYZ ")
|
||||
{
|
||||
throw new ICCProfileException("Not an RGB profile with XYZ PCS");
|
||||
}
|
||||
}
|
||||
|
||||
stream.Seek(0x80, SeekOrigin.Begin);
|
||||
|
||||
var tagCount = reader.ReadUInt32();
|
||||
|
||||
var seenTags = 0;
|
||||
|
||||
var useCLUT = false;
|
||||
for (uint i = 0; i < tagCount; i++)
|
||||
{
|
||||
stream.Seek(0x80 + 4 + 12 * i, SeekOrigin.Begin);
|
||||
var tagSig = new string(reader.ReadChars(4));
|
||||
|
||||
var offset = reader.ReadUInt32();
|
||||
var size = reader.ReadUInt32();
|
||||
|
||||
reader.BaseStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
var index = Array.IndexOf(new[] { 'r', 'g', 'b' }, tagSig[0]);
|
||||
|
||||
if (tagSig == "A2B1")
|
||||
{
|
||||
useCLUT = true;
|
||||
var typeSig = new string(reader.ReadChars(4));
|
||||
if (typeSig != "mft2")
|
||||
{
|
||||
throw new ICCProfileException(tagSig + " is not of lut16Type");
|
||||
}
|
||||
|
||||
reader.ReadUInt32();
|
||||
|
||||
var inputChannels = reader.ReadByte();
|
||||
if (inputChannels != 3)
|
||||
{
|
||||
throw new ICCProfileException(tagSig + " must have 3 input channels");
|
||||
}
|
||||
|
||||
var outputChannels = reader.ReadByte();
|
||||
if (outputChannels != 3)
|
||||
{
|
||||
throw new ICCProfileException(tagSig + " must have 3 output channels");
|
||||
}
|
||||
|
||||
var lutPoints = reader.ReadByte();
|
||||
|
||||
reader.ReadByte();
|
||||
for (var j = 0; j < 9; j++)
|
||||
{
|
||||
reader.ReadS15Fixed16();
|
||||
}
|
||||
|
||||
var inputEntries = reader.ReadUInt16();
|
||||
var outputEntries = reader.ReadUInt16();
|
||||
|
||||
var input = new ToneCurve[3];
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var table = new ushort[inputEntries];
|
||||
for (var k = 0; k < inputEntries; k++)
|
||||
{
|
||||
table[k] = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
input[j] = new LutToneCurve(table);
|
||||
}
|
||||
|
||||
var clut = new ushort[lutPoints, lutPoints, lutPoints, 3];
|
||||
for (var r = 0; r < lutPoints; r++)
|
||||
{
|
||||
for (var g = 0; g < lutPoints; g++)
|
||||
{
|
||||
for (var b = 0; b < lutPoints; b++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
clut[r, g, b, j] = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var output = new LutToneCurve[3];
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var table = new ushort[outputEntries];
|
||||
for (var k = 0; k < outputEntries; k++)
|
||||
{
|
||||
table[k] = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
output[j] = new LutToneCurve(table, 32768);
|
||||
}
|
||||
|
||||
var lut16 = new Lut16(input, clut, output);
|
||||
var black = lut16.SampleGrayscaleAt(0);
|
||||
|
||||
var primaries = new[]
|
||||
{
|
||||
lut16.SampleAt(1, 0, 0),
|
||||
lut16.SampleAt(0, 1, 0),
|
||||
lut16.SampleAt(0, 0, 1)
|
||||
};
|
||||
|
||||
var Mprime = Matrix.Zero3x3();
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var purePrimary = primaries[j] - black;
|
||||
for (var k = 0; k < 3; k++)
|
||||
{
|
||||
Mprime[k, j] = purePrimary[k] / purePrimary[1];
|
||||
}
|
||||
}
|
||||
|
||||
var M = Mprime * Matrix.FromDiagonal(Mprime.Inverse() * Colorimetry.D50);
|
||||
var Minv = M.Inverse();
|
||||
result.matrix = M;
|
||||
|
||||
const int trcSize = 4096;
|
||||
var trcs = new double[3][];
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
trcs[j] = new double[trcSize];
|
||||
}
|
||||
|
||||
for (var j = 0; j < trcSize - 1; j++)
|
||||
{
|
||||
var values = lut16.SampleGrayscaleAt(j / (double)(trcSize - 1));
|
||||
|
||||
var toneResponse = Minv * values;
|
||||
for (var k = 0; k < 3; k++)
|
||||
{
|
||||
trcs[k][j] = Math.Min(Math.Max(toneResponse[k], 0), 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
trcs[j][trcSize - 1] = 1;
|
||||
result.trcs[j] = new DoubleToneCurve(trcs[j]);
|
||||
}
|
||||
}
|
||||
else if (tagSig.EndsWith("TRC") && !useCLUT)
|
||||
{
|
||||
var typeSig = new string(reader.ReadChars(4));
|
||||
if (typeSig != "curv")
|
||||
{
|
||||
throw new ICCProfileException(tagSig + " is not of curveType");
|
||||
}
|
||||
|
||||
reader.ReadUInt32();
|
||||
|
||||
var numEntries = reader.ReadUInt32();
|
||||
|
||||
ToneCurve curve;
|
||||
if (numEntries == 1)
|
||||
{
|
||||
var gamma = reader.ReadU8Fixed8();
|
||||
curve = new GammaToneCurve(gamma);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entries = new ushort[numEntries];
|
||||
for (uint j = 0; j < numEntries; j++)
|
||||
{
|
||||
entries[j] = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
curve = new LutToneCurve(entries);
|
||||
}
|
||||
|
||||
result.trcs[index] = curve;
|
||||
|
||||
seenTags++;
|
||||
}
|
||||
else if (tagSig.EndsWith("XYZ") && !useCLUT)
|
||||
{
|
||||
reader.ReadUInt32();
|
||||
reader.ReadUInt32();
|
||||
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
result.matrix[j, index] = reader.ReadS15Fixed16();
|
||||
}
|
||||
|
||||
seenTags++;
|
||||
}
|
||||
else if (tagSig == "vcgt")
|
||||
{
|
||||
reader.ReadChars(4);
|
||||
reader.ReadUInt32();
|
||||
var type = reader.ReadUInt32();
|
||||
if (type != 0) throw new ICCProfileException("Only VCGT type 0 is supported");
|
||||
|
||||
var numChannels = reader.ReadUInt16();
|
||||
var numEntries = reader.ReadUInt16();
|
||||
var entrySize = reader.ReadUInt16();
|
||||
|
||||
if (numChannels != 3) throw new ICCProfileException("Only VCGT with 3 channels is supported");
|
||||
|
||||
result.vcgt = new ToneCurve[3];
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var values = new ushort[numEntries];
|
||||
for (var k = 0; k < numEntries; k++)
|
||||
{
|
||||
switch (entrySize)
|
||||
{
|
||||
case 1:
|
||||
values[k] = (ushort)(reader.ReadByte() * ushort.MaxValue / byte.MaxValue);
|
||||
break;
|
||||
case 2:
|
||||
values[k] = reader.ReadUInt16();
|
||||
break;
|
||||
default:
|
||||
throw new ICCProfileException("Only 8 and 16 bit VCGT is supported");
|
||||
}
|
||||
}
|
||||
|
||||
result.vcgt[j] = new LutToneCurve(values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!useCLUT)
|
||||
{
|
||||
if (seenTags != 6)
|
||||
{
|
||||
throw new ICCProfileException("Missing required tags for curves + matrix profile");
|
||||
}
|
||||
|
||||
result.matrix = Colorimetry.XYZScaleToD50(result.matrix);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class ICCProfileException : FormatException
|
||||
{
|
||||
public ICCProfileException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class Lut16
|
||||
{
|
||||
private ushort[,,,] _lut;
|
||||
private int _lutSize;
|
||||
|
||||
private ToneCurve[] inputCurves;
|
||||
private ToneCurve[] outputCurves;
|
||||
|
||||
public Lut16(ToneCurve[] input, ushort[,,,] lut, ToneCurve[] output)
|
||||
{
|
||||
_lut = lut;
|
||||
_lutSize = _lut.GetLength(0);
|
||||
|
||||
inputCurves = input;
|
||||
outputCurves = output;
|
||||
}
|
||||
|
||||
public Matrix SampleGrayscaleAt(double index)
|
||||
{
|
||||
return SampleAt(index, index, index);
|
||||
}
|
||||
|
||||
public Matrix SampleAt(double r, double g, double b)
|
||||
{
|
||||
var result = Matrix.FromValues(new[,]
|
||||
{
|
||||
{ inputCurves[0].SampleAt(r) },
|
||||
{ inputCurves[1].SampleAt(g) },
|
||||
{ inputCurves[2].SampleAt(b) }
|
||||
});
|
||||
|
||||
result = SampleCLUTTetrahedral(result);
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
result[i] = outputCurves[i].SampleAt(result[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Matrix S(int x, int y, int z)
|
||||
{
|
||||
x = Math.Min(x, _lutSize - 1);
|
||||
y = Math.Min(y, _lutSize - 1);
|
||||
z = Math.Min(z, _lutSize - 1);
|
||||
var sample0 = _lut[x, y, z, 0] / (double)ushort.MaxValue;
|
||||
var sample1 = _lut[x, y, z, 1] / (double)ushort.MaxValue;
|
||||
var sample2 = _lut[x, y, z, 2] / (double)ushort.MaxValue;
|
||||
|
||||
return Matrix.FromValues(new[,] { { sample0 }, { sample1 }, { sample2 } });
|
||||
}
|
||||
|
||||
private Matrix S(double x, double y, double z)
|
||||
{
|
||||
return S((int)x, (int)y, (int)z);
|
||||
}
|
||||
|
||||
// https://www.filmlight.ltd.uk/pdf/whitepapers/FL-TL-TN-0057-SoftwareLib.pdf
|
||||
private Matrix SampleCLUTTetrahedral(Matrix rgb)
|
||||
{
|
||||
var lutIndex = rgb * (_lutSize - 1);
|
||||
var n = lutIndex.Map(Math.Floor);
|
||||
var f = lutIndex.Map(x => x - (int)x);
|
||||
|
||||
Matrix Sxyz;
|
||||
if (f[0] > f[1])
|
||||
{
|
||||
if (f[1] > f[2])
|
||||
{
|
||||
Sxyz = (1 - f[0]) * S(n[0], n[1], n[2])
|
||||
+ (f[0] - f[1]) * S(n[0] + 1, n[1], n[2])
|
||||
+ (f[1] - f[2]) * S(n[0] + 1, n[1] + 1, n[2])
|
||||
+ (f[2]) * S(n[0] + 1, n[1] + 1, n[2] + 1);
|
||||
}
|
||||
else if (f[0] > f[2])
|
||||
{
|
||||
Sxyz = (1 - f[0]) * S(n[0], n[1], n[2])
|
||||
+ (f[0] - f[2]) * S(n[0] + 1, n[1], n[2])
|
||||
+ (f[2] - f[1]) * S(n[0] + 1, n[1], n[2] + 1)
|
||||
+ (f[1]) * S(n[0] + 1, n[1] + 1, n[2] + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Sxyz = (1 - f[2]) * S(n[0], n[1], n[2])
|
||||
+ (f[2] - f[0]) * S(n[0], n[1], n[2] + 1)
|
||||
+ (f[0] - f[1]) * S(n[0] + 1, n[1], n[2] + 1)
|
||||
+ (f[1]) * S(n[0] + 1, n[1] + 1, n[2] + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (f[2] > f[1])
|
||||
{
|
||||
Sxyz = (1 - f[2]) * S(n[0], n[1], n[2])
|
||||
+ (f[2] - f[1]) * S(n[0], n[1], n[2] + 1)
|
||||
+ (f[1] - f[0]) * S(n[0], n[1] + 1, n[2] + 1)
|
||||
+ (f[0]) * S(n[0] + 1, n[1] + 1, n[2] + 1);
|
||||
}
|
||||
else if (f[2] > f[0])
|
||||
{
|
||||
Sxyz = (1 - f[1]) * S(n[0], n[1], n[2])
|
||||
+ (f[1] - f[2]) * S(n[0], n[1] + 1, n[2])
|
||||
+ (f[2] - f[0]) * S(n[0], n[1] + 1, n[2] + 1)
|
||||
+ (f[0]) * S(n[0] + 1, n[1] + 1, n[2] + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Sxyz = (1 - f[1]) * S(n[0], n[1], n[2])
|
||||
+ (f[1] - f[0]) * S(n[0], n[1] + 1, n[2])
|
||||
+ (f[0] - f[2]) * S(n[0] + 1, n[1] + 1, n[2])
|
||||
+ (f[2]) * S(n[0] + 1, n[1] + 1, n[2] + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return Sxyz;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class LutToneCurve : ToneCurve
|
||||
{
|
||||
private ushort[] _values;
|
||||
private ushort _divisor;
|
||||
|
||||
public LutToneCurve(ushort[] values, ushort divisor = ushort.MaxValue)
|
||||
{
|
||||
_values = values;
|
||||
_divisor = divisor;
|
||||
}
|
||||
|
||||
public double SampleAt(double x)
|
||||
{
|
||||
if (x == 0) return (double)_values[0] / _divisor;
|
||||
if (x >= 1) return (double)_values[_values.Length - 1] / _divisor;
|
||||
|
||||
var index = x * (_values.Length - 1);
|
||||
var frac = index - (uint)index;
|
||||
return (_values[(uint)index] * (1 - frac) + _values[(uint)index + 1] * frac) / _divisor;
|
||||
}
|
||||
|
||||
public double SampleInverseAt(double x)
|
||||
{
|
||||
var value = x * _divisor;
|
||||
var lowValue = (ushort)value;
|
||||
|
||||
if (_values[0] >= value) return 0;
|
||||
if (_values[_values.Length - 1] <= lowValue) return 1;
|
||||
|
||||
var lowerIndex = -1;
|
||||
for (var i = 0; i < _values.Length; i++)
|
||||
{
|
||||
if (lowValue >= _values[i])
|
||||
{
|
||||
lowerIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
var low = _values[lowerIndex];
|
||||
var high = _values[lowerIndex + 1];
|
||||
var frac = (value - low) / (high - low);
|
||||
|
||||
return (lowerIndex + frac) / (_values.Length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using Microsoft.Win32;
|
||||
using NvAPIWrapper.Display;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class MainViewModel
|
||||
{
|
||||
public static string ConfigPath { get; set; }
|
||||
|
||||
public ObservableCollection<MonitorData> Monitors { get; }
|
||||
|
||||
private string _configPath;
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
Monitors = new ObservableCollection<MonitorData>();
|
||||
_configPath = Path.Combine(ConfigPath, "NovideoConfig.xml");
|
||||
|
||||
UpdateMonitors();
|
||||
}
|
||||
|
||||
private void UpdateMonitors()
|
||||
{
|
||||
Monitors.Clear();
|
||||
List<XElement> config = null;
|
||||
if (File.Exists(_configPath))
|
||||
{
|
||||
config = XElement.Load(_configPath).Descendants("monitor").ToList();
|
||||
}
|
||||
|
||||
var hdrPaths = DisplayConfigManager.GetHdrDisplayPaths();
|
||||
|
||||
var number = 1;
|
||||
foreach (var display in Display.GetDisplays())
|
||||
{
|
||||
var displays = WindowsDisplayAPI.Display.GetDisplays();
|
||||
var path = displays.First(x => x.DisplayName == display.Name).DevicePath;
|
||||
|
||||
var hdrActive = hdrPaths.Contains(path);
|
||||
|
||||
var settings = config?.FirstOrDefault(x => (string)x.Attribute("path") == path);
|
||||
MonitorData monitor;
|
||||
if (settings != null)
|
||||
{
|
||||
monitor = new MonitorData(this, number++, display, path, hdrActive,
|
||||
(bool)settings.Attribute("clamp_sdr"),
|
||||
(bool)settings.Attribute("use_icc"),
|
||||
(string)settings.Attribute("icc_path"),
|
||||
(bool)settings.Attribute("calibrate_gamma"),
|
||||
(int)settings.Attribute("selected_gamma"),
|
||||
(double)settings.Attribute("custom_gamma"),
|
||||
(double)settings.Attribute("custom_percentage"),
|
||||
(int)settings.Attribute("target"),
|
||||
(bool)settings.Attribute("disable_optimization"));
|
||||
}
|
||||
else
|
||||
{
|
||||
monitor = new MonitorData(this, number++, display, path, hdrActive, false);
|
||||
}
|
||||
|
||||
Monitors.Add(monitor);
|
||||
}
|
||||
|
||||
foreach (var monitor in Monitors)
|
||||
{
|
||||
monitor.ReapplyClamp();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDisplaySettingsChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateMonitors();
|
||||
}
|
||||
|
||||
public void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
|
||||
{
|
||||
if (e.Mode != PowerModes.Resume) return;
|
||||
OnDisplaySettingsChanged(null, null);
|
||||
}
|
||||
|
||||
public void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var xElem = new XElement("monitors",
|
||||
Monitors.Select(x =>
|
||||
new XElement("monitor", new XAttribute("path", x.Path),
|
||||
new XAttribute("clamp_sdr", x.ClampSdr),
|
||||
new XAttribute("use_icc", x.UseIcc),
|
||||
new XAttribute("icc_path", x.ProfilePath),
|
||||
new XAttribute("calibrate_gamma", x.CalibrateGamma),
|
||||
new XAttribute("selected_gamma", x.SelectedGamma),
|
||||
new XAttribute("custom_gamma", x.CustomGamma),
|
||||
new XAttribute("custom_percentage", x.CustomPercentage),
|
||||
new XAttribute("target", x.Target),
|
||||
new XAttribute("disable_optimization", x.DisableOptimization))));
|
||||
xElem.Save(_configPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message + "\n\nTry extracting the program elsewhere.");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<Window x:Class="novideo_srgb.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:novideo_srgb"
|
||||
ResizeMode="CanMinimize"
|
||||
mc:Ignorable="d"
|
||||
Title="Novideo sRGB" Height="320" Width="500" MinHeight="150" MinWidth="300">
|
||||
<Window.DataContext>
|
||||
<local:MainViewModel />
|
||||
</Window.DataContext>
|
||||
<DockPanel Margin="6,0,6,6">
|
||||
<Grid DockPanel.Dock="Bottom">
|
||||
<Button DockPanel.Dock="Bottom" Content="About" Width="75" HorizontalAlignment="Left"
|
||||
Click="AboutButton_Click" />
|
||||
<WrapPanel DockPanel.Dock="Bottom" HorizontalAlignment="Right">
|
||||
<Button Content="Reapply" Width="75" Click="ReapplyButton_Click" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
<DataGrid ItemsSource="{Binding Monitors}" RowHeaderWidth="0" AutoGenerateColumns="False"
|
||||
DockPanel.Dock="Bottom" Margin="0,0,0,6" CanUserResizeColumns="False" CanUserSortColumns="False"
|
||||
CanUserReorderColumns="False">
|
||||
<DataGrid.Resources>
|
||||
<Style TargetType="{x:Type DataGridCell}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type DataGridCell}">
|
||||
<Grid Background="{TemplateBinding Background}">
|
||||
<ContentPresenter VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="#" Binding="{Binding Number}" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="GPU" Width="*" Binding="{Binding GPU}" IsReadOnly="True" />
|
||||
<DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<Button HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Click="AdvancedButton_Click" Width="75">
|
||||
Advanced
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellStyle>
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Margin" Value="0,0,-1,0" />
|
||||
</Style>
|
||||
</DataGridTemplateColumn.CellStyle>
|
||||
</DataGridTemplateColumn>
|
||||
<DataGridTemplateColumn Header="Clamped">
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<CheckBox HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsEnabled="{Binding CanClamp}"
|
||||
IsChecked="{Binding Clamped, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
<DataGridTemplateColumn.CellStyle>
|
||||
<Style TargetType="DataGridCell">
|
||||
<Setter Property="Margin" Value="0,0,-1,0" />
|
||||
</Style>
|
||||
</DataGridTemplateColumn.CellStyle>
|
||||
</DataGridTemplateColumn>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,136 @@
|
||||
using Microsoft.Win32;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public partial class MainWindow
|
||||
{
|
||||
private readonly MainViewModel _viewModel;
|
||||
|
||||
private static MainWindow _novideoWindow;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
_viewModel = (MainViewModel)DataContext;
|
||||
SystemEvents.DisplaySettingsChanged += _viewModel.OnDisplaySettingsChanged;
|
||||
SystemEvents.PowerModeChanged += _viewModel.OnPowerModeChanged;
|
||||
}
|
||||
|
||||
public static void CreateAndShow(bool show = true)
|
||||
{
|
||||
if (System.Windows.Application.Current == null)
|
||||
{
|
||||
new System.Windows.Application();
|
||||
}
|
||||
|
||||
_novideoWindow ??= new MainWindow();
|
||||
|
||||
if (show)
|
||||
{
|
||||
_novideoWindow.WindowState = WindowState.Normal;
|
||||
_novideoWindow.Show();
|
||||
_novideoWindow.Topmost = true;
|
||||
_novideoWindow.Topmost = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsInitialized()
|
||||
{
|
||||
return _novideoWindow != null;
|
||||
}
|
||||
|
||||
public static void UpdateContextMenu(ToolStripMenuItem parent)
|
||||
{
|
||||
if (_novideoWindow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var menu = new ToolStripDropDownMenu();
|
||||
|
||||
parent.DropDownItems.Clear();
|
||||
|
||||
foreach (var monitor in _novideoWindow._viewModel.Monitors)
|
||||
{
|
||||
var item = new ToolStripMenuItem();
|
||||
parent.DropDownItems.Add(item);
|
||||
item.Text = monitor.Name;
|
||||
item.Checked = monitor.Clamped;
|
||||
item.Enabled = monitor.CanClamp;
|
||||
item.ForeColor = parent.ForeColor;
|
||||
item.Click += (sender, args) => monitor.Clamped = !monitor.Clamped;
|
||||
}
|
||||
|
||||
parent.DropDownItems.Add("-").ForeColor = parent.ForeColor;
|
||||
|
||||
var reapplyItem = new ToolStripMenuItem();
|
||||
parent.DropDownItems.Add(reapplyItem);
|
||||
reapplyItem.Text = "Reapply";
|
||||
reapplyItem.ForeColor = parent.ForeColor;
|
||||
reapplyItem.Click += delegate { _novideoWindow.ReapplyMonitorSettings(); };
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
e.Cancel = true;
|
||||
|
||||
Hide();
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(EventArgs e)
|
||||
{
|
||||
if (WindowState == WindowState.Minimized)
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
|
||||
base.OnStateChanged(e);
|
||||
}
|
||||
|
||||
private void AboutButton_Click(object sender, RoutedEventArgs o)
|
||||
{
|
||||
var window = new AboutWindow
|
||||
{
|
||||
Owner = this
|
||||
};
|
||||
window.ShowDialog();
|
||||
}
|
||||
|
||||
private void AdvancedButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (System.Windows.Application.Current.Windows.Cast<Window>().Any(x => x is AdvancedWindow)) return;
|
||||
var monitor = ((FrameworkElement)sender).DataContext as MonitorData;
|
||||
var window = new AdvancedWindow(monitor)
|
||||
{
|
||||
Owner = this
|
||||
};
|
||||
|
||||
void CloseWindow(object o, EventArgs e2) => window.Close();
|
||||
|
||||
SystemEvents.DisplaySettingsChanged += CloseWindow;
|
||||
if (window.ShowDialog() == false) return;
|
||||
SystemEvents.DisplaySettingsChanged -= CloseWindow;
|
||||
|
||||
if (window.ChangedCalibration)
|
||||
{
|
||||
_viewModel.SaveConfig();
|
||||
monitor?.ReapplyClamp();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReapplyButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ReapplyMonitorSettings();
|
||||
}
|
||||
|
||||
private void ReapplyMonitorSettings()
|
||||
{
|
||||
foreach (var monitor in _viewModel.Monitors)
|
||||
{
|
||||
monitor.ReapplyClamp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class Matrix
|
||||
{
|
||||
private double[,] _values;
|
||||
|
||||
private Matrix()
|
||||
{
|
||||
}
|
||||
|
||||
public double this[int x, int y]
|
||||
{
|
||||
get => _values[x, y];
|
||||
set => _values[x, y] = value;
|
||||
}
|
||||
|
||||
public double this[int x]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Cols != 1)
|
||||
{
|
||||
throw new NotSupportedException("Matrix must be 3x1");
|
||||
}
|
||||
|
||||
return _values[x, 0];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Cols != 1)
|
||||
{
|
||||
throw new NotSupportedException("Matrix must be 3x1");
|
||||
}
|
||||
|
||||
_values[x, 0] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Rows => _values.GetLength(0);
|
||||
public int Cols => _values.GetLength(1);
|
||||
|
||||
public static Matrix FromValues(double[,] array)
|
||||
{
|
||||
if (array.GetLength(0) != 3 || !(array.GetLength(1) == 3 || array.GetLength(1) == 1))
|
||||
{
|
||||
throw new ArgumentException("Array must be 3x3 or 3x1");
|
||||
}
|
||||
|
||||
var result = new Matrix
|
||||
{
|
||||
_values = array
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Matrix Zero3x3()
|
||||
{
|
||||
return FromValues(new double[3, 3]);
|
||||
}
|
||||
|
||||
public static Matrix Zero3x1()
|
||||
{
|
||||
return FromValues(new double[3, 1]);
|
||||
}
|
||||
|
||||
public static Matrix One3x1()
|
||||
{
|
||||
return FromValues(new double[,] { { 1 }, { 1 }, { 1 } });
|
||||
}
|
||||
|
||||
public static Matrix FromDiagonal(double[] array)
|
||||
{
|
||||
if (array.Length != 3)
|
||||
{
|
||||
throw new ArgumentException("Array must have length 3");
|
||||
}
|
||||
|
||||
var result = new Matrix
|
||||
{
|
||||
_values = new double[3, 3]
|
||||
};
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
result._values[i, i] = array[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Matrix FromDiagonal(Matrix column)
|
||||
{
|
||||
if (column.Cols != 1)
|
||||
{
|
||||
throw new ArgumentException("Matrix must be 3x1");
|
||||
}
|
||||
|
||||
return FromDiagonal(new[] { column[0], column[1], column[2] });
|
||||
}
|
||||
|
||||
public static Matrix operator *(Matrix a, Matrix b)
|
||||
{
|
||||
if (a.Cols != 3)
|
||||
{
|
||||
throw new ArgumentException("Left side must be 3x3");
|
||||
}
|
||||
|
||||
var result = b.Cols == 3 ? Zero3x3() : Zero3x1();
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < result.Cols; j++)
|
||||
{
|
||||
for (var k = 0; k < 3; k++)
|
||||
{
|
||||
result[i, j] += a[i, k] * b[k, j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Matrix operator *(double a, Matrix b)
|
||||
{
|
||||
var result = b.Cols == 1 ? Zero3x1() : Zero3x3();
|
||||
|
||||
for (var i = 0; i < result.Rows; i++)
|
||||
{
|
||||
for (var j = 0; j < result.Cols; j++)
|
||||
{
|
||||
result[i, j] = a * b[i, j];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Matrix operator *(Matrix a, double b)
|
||||
{
|
||||
return b * a;
|
||||
}
|
||||
|
||||
public static Matrix operator /(double a, Matrix b)
|
||||
{
|
||||
return 1 / a * b;
|
||||
}
|
||||
|
||||
public static Matrix operator /(Matrix a, double b)
|
||||
{
|
||||
return a * (1 / b);
|
||||
}
|
||||
|
||||
public static Matrix operator +(Matrix a, Matrix b)
|
||||
{
|
||||
if (a.Cols != b.Cols)
|
||||
{
|
||||
throw new ArgumentException("Both sides must have same size");
|
||||
}
|
||||
|
||||
var result = b.Cols == 1 ? Zero3x1() : Zero3x3();
|
||||
|
||||
for (var i = 0; i < result.Rows; i++)
|
||||
{
|
||||
for (var j = 0; j < result.Cols; j++)
|
||||
{
|
||||
result[i, j] = a[i, j] + b[i, j];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Matrix operator -(Matrix a, Matrix b)
|
||||
{
|
||||
if (a.Cols != b.Cols)
|
||||
{
|
||||
throw new ArgumentException("Both sides must have same size");
|
||||
}
|
||||
|
||||
var result = b.Cols == 1 ? Zero3x1() : Zero3x3();
|
||||
|
||||
for (var i = 0; i < result.Rows; i++)
|
||||
{
|
||||
for (var j = 0; j < result.Cols; j++)
|
||||
{
|
||||
result[i, j] = a[i, j] - b[i, j];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Matrix Inverse()
|
||||
{
|
||||
if (Cols != 3)
|
||||
{
|
||||
throw new ArgumentException("Matrix must be 3x3");
|
||||
}
|
||||
|
||||
var a = this[0, 0];
|
||||
var b = this[0, 1];
|
||||
var c = this[0, 2];
|
||||
var d = this[1, 0];
|
||||
var e = this[1, 1];
|
||||
var f = this[1, 2];
|
||||
var g = this[2, 0];
|
||||
var h = this[2, 1];
|
||||
var i = this[2, 2];
|
||||
|
||||
var denom = a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
|
||||
return 1 / denom * FromValues(new[,]
|
||||
{
|
||||
{ e * i - f * h, -b * i + c * h, b * f - c * e },
|
||||
{ -d * i + f * g, a * i - c * g, -a * f + c * d },
|
||||
{ d * h - e * g, -a * h + b * g, a * e - b * d }
|
||||
});
|
||||
}
|
||||
|
||||
public Matrix Map(Func<double, double> func)
|
||||
{
|
||||
var result = Cols == 1 ? Zero3x1() : Zero3x3();
|
||||
for (var i = 0; i < result.Rows; i++)
|
||||
{
|
||||
for (var j = 0; j < result.Cols; j++)
|
||||
{
|
||||
result[i, j] = func(this[i, j]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
using EDIDParser;
|
||||
using EDIDParser.Descriptors;
|
||||
using EDIDParser.Enums;
|
||||
using NLog;
|
||||
using NvAPIWrapper.Display;
|
||||
using NvAPIWrapper.GPU;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class MonitorData : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
private readonly GPUOutput _output;
|
||||
private bool _clamped;
|
||||
|
||||
private MainViewModel _viewModel;
|
||||
|
||||
public MonitorData(MainViewModel viewModel, int number, Display display, string path, bool hdrActive, bool clampSdr)
|
||||
{
|
||||
_viewModel = viewModel;
|
||||
Number = number;
|
||||
_output = display.Output;
|
||||
|
||||
Edid = Novideo.GetEDID(path, display);
|
||||
|
||||
Name = Edid.Descriptors.OfType<StringDescriptor>()
|
||||
.FirstOrDefault(x => x.Type == StringDescriptorType.MonitorName)?.Value ?? "<no name>";
|
||||
|
||||
Path = path;
|
||||
ClampSdr = clampSdr;
|
||||
HdrActive = hdrActive;
|
||||
|
||||
var coords = Edid.DisplayParameters.ChromaticityCoordinates;
|
||||
EdidColorSpace = new Colorimetry.ColorSpace
|
||||
{
|
||||
Red = new Colorimetry.Point { X = Math.Round(coords.RedX, 3), Y = Math.Round(coords.RedY, 3) },
|
||||
Green = new Colorimetry.Point { X = Math.Round(coords.GreenX, 3), Y = Math.Round(coords.GreenY, 3) },
|
||||
Blue = new Colorimetry.Point { X = Math.Round(coords.BlueX, 3), Y = Math.Round(coords.BlueY, 3) },
|
||||
White = Colorimetry.D65
|
||||
};
|
||||
|
||||
_clamped = Novideo.IsColorSpaceConversionActive(_output);
|
||||
|
||||
ProfilePath = "";
|
||||
CustomGamma = 2.2;
|
||||
CustomPercentage = 100;
|
||||
}
|
||||
|
||||
public MonitorData(MainViewModel viewModel, int number, Display display, string path, bool hdrActive, bool clampSdr, bool useIcc, string profilePath,
|
||||
bool calibrateGamma,
|
||||
int selectedGamma, double customGamma, double customPercentage, int target, bool disableOptimization) :
|
||||
this(viewModel, number, display, path, hdrActive, clampSdr)
|
||||
{
|
||||
UseIcc = useIcc;
|
||||
ProfilePath = profilePath;
|
||||
CalibrateGamma = calibrateGamma;
|
||||
SelectedGamma = selectedGamma;
|
||||
CustomGamma = customGamma;
|
||||
CustomPercentage = customPercentage;
|
||||
Target = target;
|
||||
DisableOptimization = disableOptimization;
|
||||
}
|
||||
|
||||
public int Number { get; }
|
||||
public string Name { get; }
|
||||
public EDID Edid { get; }
|
||||
public string Path { get; }
|
||||
public bool ClampSdr { get; set; }
|
||||
public bool HdrActive { get; }
|
||||
|
||||
private void UpdateClamp(bool doClamp)
|
||||
{
|
||||
if (_clamped)
|
||||
{
|
||||
Novideo.DisableColorSpaceConversion(_output);
|
||||
}
|
||||
|
||||
if (!doClamp) return;
|
||||
|
||||
if (_clamped) Thread.Sleep(100);
|
||||
if (UseEdid)
|
||||
Novideo.SetColorSpaceConversion(_output, Colorimetry.RGBToRGB(TargetColorSpace, EdidColorSpace));
|
||||
else if (UseIcc)
|
||||
{
|
||||
var profile = ICCMatrixProfile.FromFile(ProfilePath);
|
||||
if (CalibrateGamma)
|
||||
{
|
||||
var trcBlack = Matrix.FromValues(new[,]
|
||||
{
|
||||
{ profile.trcs[0].SampleAt(0) },
|
||||
{ profile.trcs[1].SampleAt(0) },
|
||||
{ profile.trcs[2].SampleAt(0) }
|
||||
});
|
||||
var black = (profile.matrix * trcBlack)[1];
|
||||
|
||||
ToneCurve gamma;
|
||||
switch (SelectedGamma)
|
||||
{
|
||||
case 0:
|
||||
gamma = new SrgbEOTF(black);
|
||||
break;
|
||||
case 1:
|
||||
gamma = new GammaToneCurve(2.4, black, 0);
|
||||
break;
|
||||
case 2:
|
||||
gamma = new GammaToneCurve(CustomGamma, black, CustomPercentage / 100);
|
||||
break;
|
||||
case 3:
|
||||
gamma = new GammaToneCurve(CustomGamma, black, CustomPercentage / 100, true);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported gamma type " + SelectedGamma);
|
||||
}
|
||||
|
||||
Novideo.SetColorSpaceConversion(_output, profile, TargetColorSpace, gamma, DisableOptimization);
|
||||
}
|
||||
else
|
||||
{
|
||||
Novideo.SetColorSpaceConversion(_output, profile, TargetColorSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleClampException(Exception e)
|
||||
{
|
||||
Logger.Error(e);
|
||||
|
||||
//System.Windows.MessageBox.Show(e.Message);
|
||||
_clamped = Novideo.IsColorSpaceConversionActive(_output);
|
||||
ClampSdr = _clamped;
|
||||
_viewModel.SaveConfig();
|
||||
OnPropertyChanged(nameof(Clamped));
|
||||
}
|
||||
|
||||
public bool Clamped
|
||||
{
|
||||
set
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateClamp(value);
|
||||
ClampSdr = value;
|
||||
_viewModel.SaveConfig();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HandleClampException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
_clamped = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
get => _clamped;
|
||||
}
|
||||
|
||||
public void ReapplyClamp()
|
||||
{
|
||||
try
|
||||
{
|
||||
var clamped = CanClamp && ClampSdr;
|
||||
UpdateClamp(clamped);
|
||||
_clamped = clamped;
|
||||
OnPropertyChanged(nameof(CanClamp));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
HandleClampException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanClamp => !HdrActive && (UseEdid && !EdidColorSpace.Equals(TargetColorSpace) || UseIcc && ProfilePath != "");
|
||||
|
||||
public string GPU => _output.PhysicalGPU.FullName;
|
||||
|
||||
public bool UseEdid
|
||||
{
|
||||
set => UseIcc = !value;
|
||||
get => !UseIcc;
|
||||
}
|
||||
|
||||
public bool UseIcc { set; get; }
|
||||
|
||||
public string ProfilePath { set; get; }
|
||||
|
||||
public bool CalibrateGamma { set; get; }
|
||||
|
||||
public int SelectedGamma { set; get; }
|
||||
|
||||
public double CustomGamma { set; get; }
|
||||
|
||||
public double CustomPercentage { set; get; }
|
||||
|
||||
public bool DisableOptimization { set; get; }
|
||||
|
||||
public int Target { set; get; }
|
||||
|
||||
public Colorimetry.ColorSpace EdidColorSpace { get; }
|
||||
|
||||
private Colorimetry.ColorSpace TargetColorSpace => Colorimetry.ColorSpaces[Target];
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string name = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
using EDIDParser;
|
||||
using Microsoft.Win32;
|
||||
using NvAPIWrapper.Display;
|
||||
using NvAPIWrapper.GPU;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/*
|
||||
no NDAs violated here!
|
||||
everything figured out from publicly available information and/or
|
||||
throwing stuff at NVAPI and observing the results or errors
|
||||
*/
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public static class Novideo
|
||||
{
|
||||
/*
|
||||
observed pipeline: content degamma -> content to srgb -> srgb to monitor -> matrix2 -> matrix1 -> monitor gamma
|
||||
in hardware all the matrix stuff is done with a single matrix, i.e. these four multiplied together
|
||||
3x4 matrices cannot be multiplied with each other though, so only the 3x3 parts are combined "properly"
|
||||
and the offsets are simply added together
|
||||
*/
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct Csc
|
||||
{
|
||||
public uint version; // 0x1007C for V1, 0x200A0 for V2
|
||||
|
||||
public uint
|
||||
contentColorSpace; // built-in degamut/degamma transforms, 1 <= x <= 12, default 2 (probably srgb)
|
||||
|
||||
public uint monitorColorSpace; // built-in gamut/gamma transforms, 0 <= x <= 12, default 0 (= csc disabled)
|
||||
public uint unknown1; // no idea, set to 0 by both get and set functions -> some type of error code?
|
||||
public uint unknown2; // also no idea, not modified by either function -> unused?
|
||||
public uint useMatrix1; // 1 to enable
|
||||
public unsafe fixed float matrix1[3 * 4]; // r/g/b gain and offset
|
||||
public uint useMatrix2;
|
||||
|
||||
public unsafe fixed float matrix2[3 * 4];
|
||||
|
||||
// v2 stuff
|
||||
public unsafe float* degamma; // pointer to degamma part of buffer (= first element)
|
||||
public unsafe float* regamma; // pointer to regamma part of buffer (= index 0x3000)
|
||||
|
||||
public unsafe float*
|
||||
buffer; // float array of size 0x6000, contains interleaved rgb degamma followed by regamma
|
||||
|
||||
public int bufferSize; // 0x6000
|
||||
}
|
||||
|
||||
private const uint _NvAPI_GPU_GetColorSpaceConversion = 0x8159E87A;
|
||||
private const uint _NvAPI_GPU_SetColorSpaceConversion = 0x0FCABD23A;
|
||||
|
||||
[DllImport("nvapi64", EntryPoint = "nvapi_QueryInterface")]
|
||||
private static extern IntPtr NvAPI_QueryInterface(uint id);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate int NvAPI_GPU_GetColorSpaceConversion_t(uint displayId,
|
||||
[MarshalAs(UnmanagedType.Struct)] ref Csc csc);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate int NvAPI_GPU_SetColorSpaceConversion_t(uint displayId,
|
||||
[MarshalAs(UnmanagedType.Struct)] ref Csc csc);
|
||||
|
||||
private static NvAPI_GPU_GetColorSpaceConversion_t NvAPI_GPU_GetColorSpaceConversion;
|
||||
private static NvAPI_GPU_SetColorSpaceConversion_t NvAPI_GPU_SetColorSpaceConversion;
|
||||
|
||||
public struct ColorSpaceConversion
|
||||
{
|
||||
public uint contentColorSpace;
|
||||
public uint monitorColorSpace;
|
||||
public float[,] matrix1;
|
||||
public float[,] matrix2;
|
||||
}
|
||||
|
||||
public static ColorSpaceConversion GetColorSpaceConversion(GPUOutput output)
|
||||
{
|
||||
var displayId = output.PhysicalGPU.GetDisplayDeviceByOutput(output).DisplayId;
|
||||
|
||||
var csc = new Csc { version = 0x1007C };
|
||||
var status = NvAPI_GPU_GetColorSpaceConversion(displayId, ref csc);
|
||||
if (status != 0)
|
||||
{
|
||||
throw new Exception("NvAPI_GPU_GetColorSpaceConversion failed with error code " + status);
|
||||
}
|
||||
|
||||
var result = new ColorSpaceConversion
|
||||
{
|
||||
contentColorSpace = csc.contentColorSpace,
|
||||
monitorColorSpace = csc.monitorColorSpace
|
||||
};
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (csc.useMatrix1 == 1)
|
||||
{
|
||||
if (result.matrix1 == null) result.matrix1 = new float[3, 4];
|
||||
result.matrix1[i, j] = csc.matrix1[i * 4 + j];
|
||||
}
|
||||
|
||||
if (csc.useMatrix2 == 1)
|
||||
{
|
||||
if (result.matrix2 == null) result.matrix2 = new float[3, 4];
|
||||
result.matrix2[i, j] = csc.matrix2[i * 4 + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SetColorSpaceConversion(GPUOutput output, ColorSpaceConversion conversion)
|
||||
{
|
||||
var displayId = output.PhysicalGPU.GetDisplayDeviceByOutput(output).DisplayId;
|
||||
|
||||
var csc = new Csc
|
||||
{
|
||||
version = 0x1007C,
|
||||
contentColorSpace = conversion.contentColorSpace,
|
||||
monitorColorSpace = conversion.monitorColorSpace
|
||||
};
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 4; j++)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (conversion.matrix1 != null)
|
||||
{
|
||||
csc.useMatrix1 = 1;
|
||||
csc.matrix1[i * 4 + j] = conversion.matrix1[i, j];
|
||||
}
|
||||
|
||||
if (conversion.matrix2 != null)
|
||||
{
|
||||
csc.useMatrix2 = 1;
|
||||
csc.matrix2[i * 4 + j] = conversion.matrix2[i, j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var status = NvAPI_GPU_SetColorSpaceConversion(displayId, ref csc);
|
||||
if (status != 0)
|
||||
{
|
||||
throw new Exception("NvAPI_GPU_SetColorSpaceConversion failed with error code " + status);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetColorSpaceConversion(GPUOutput output, Matrix matrix)
|
||||
{
|
||||
SetColorSpaceConversion(output, MatrixToColorSpaceConversion(matrix));
|
||||
}
|
||||
|
||||
public static unsafe void SetColorSpaceConversion(GPUOutput output, ICCMatrixProfile profile,
|
||||
Colorimetry.ColorSpace target,
|
||||
ToneCurve curve = null,
|
||||
bool disableOptimization = false)
|
||||
{
|
||||
var matrix = profile.matrix.Inverse() * Colorimetry.RGBToPCSXYZ(target);
|
||||
|
||||
if (curve == null)
|
||||
{
|
||||
SetColorSpaceConversion(output, MatrixToColorSpaceConversion(matrix));
|
||||
return;
|
||||
}
|
||||
|
||||
var displayId = output.PhysicalGPU.GetDisplayDeviceByOutput(output).DisplayId;
|
||||
var gamma = new float[2, 1024, 3];
|
||||
fixed (float* buffer = gamma)
|
||||
{
|
||||
var csc = new Csc
|
||||
{
|
||||
version = 0x200A0,
|
||||
contentColorSpace = 2,
|
||||
monitorColorSpace = 2,
|
||||
degamma = buffer,
|
||||
regamma = buffer + 0x3000 / sizeof(float),
|
||||
buffer = buffer,
|
||||
bufferSize = 0x6000,
|
||||
};
|
||||
|
||||
double nextIndex = -1;
|
||||
for (var i = 1; i < 1024; i++)
|
||||
{
|
||||
var index = i / 1023d;
|
||||
|
||||
if (!disableOptimization)
|
||||
{
|
||||
var curr = i * 255 % 1023;
|
||||
var next = (i + 1) * 255 % 1023;
|
||||
|
||||
if (nextIndex != -1)
|
||||
{
|
||||
index = nextIndex;
|
||||
nextIndex = -1;
|
||||
}
|
||||
else if (next < curr)
|
||||
{
|
||||
nextIndex = (i + 1) * 255 / 1023 / 255d;
|
||||
if (next != 0)
|
||||
{
|
||||
index = nextIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sample = (float)curve.SampleAt(index);
|
||||
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
gamma[0, i, j] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
csc.matrix1[i * 4 + j] = (float)matrix[i, j];
|
||||
}
|
||||
}
|
||||
|
||||
csc.useMatrix1 = 1;
|
||||
|
||||
for (var i = 0; i < 1024; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
var value = profile.trcs[j].SampleInverseAt(i / 1023d);
|
||||
|
||||
if (profile.vcgt != null)
|
||||
{
|
||||
value = profile.vcgt[j].SampleAt(value);
|
||||
}
|
||||
|
||||
gamma[1, i, j] = (float)value;
|
||||
}
|
||||
}
|
||||
|
||||
var status = NvAPI_GPU_SetColorSpaceConversion(displayId, ref csc);
|
||||
if (status != 0)
|
||||
{
|
||||
throw new Exception("NvAPI_GPU_SetColorSpaceConversion failed with error code " + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsColorSpaceConversionActive(GPUOutput output)
|
||||
{
|
||||
return GetColorSpaceConversion(output).monitorColorSpace != 0;
|
||||
}
|
||||
|
||||
public static void DisableColorSpaceConversion(GPUOutput output)
|
||||
{
|
||||
SetColorSpaceConversion(output, new ColorSpaceConversion { contentColorSpace = 2 });
|
||||
}
|
||||
|
||||
public static EDID GetEDID(string path, Display display)
|
||||
{
|
||||
try
|
||||
{
|
||||
var registryPath = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\DISPLAY\\";
|
||||
registryPath += string.Join("\\", path.Split('#').Skip(1).Take(2));
|
||||
return new EDID((byte[])Registry.GetValue(registryPath + "\\Device Parameters", "EDID", null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new EDID(display.Output.PhysicalGPU.ReadEDIDData(display.Output));
|
||||
}
|
||||
}
|
||||
|
||||
private static ColorSpaceConversion MatrixToColorSpaceConversion(Matrix matrix)
|
||||
{
|
||||
var csc = new ColorSpaceConversion
|
||||
{
|
||||
contentColorSpace = 2,
|
||||
monitorColorSpace = 2,
|
||||
matrix1 = new float[3, 4]
|
||||
};
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
for (var j = 0; j < 3; j++)
|
||||
{
|
||||
csc.matrix1[i, j] = (float)matrix[i, j];
|
||||
}
|
||||
}
|
||||
|
||||
return csc;
|
||||
}
|
||||
|
||||
static Novideo()
|
||||
{
|
||||
NvAPI_GPU_GetColorSpaceConversion =
|
||||
Marshal.GetDelegateForFunctionPointer<NvAPI_GPU_GetColorSpaceConversion_t>(
|
||||
NvAPI_QueryInterface(_NvAPI_GPU_GetColorSpaceConversion));
|
||||
NvAPI_GPU_SetColorSpaceConversion =
|
||||
Marshal.GetDelegateForFunctionPointer<NvAPI_GPU_SetColorSpaceConversion_t>(
|
||||
NvAPI_QueryInterface(_NvAPI_GPU_SetColorSpaceConversion));
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace novideo_srgb.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("novideo_srgb.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||
/// </summary>
|
||||
internal static System.Drawing.Icon icon {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("icon", resourceCulture);
|
||||
return ((System.Drawing.Icon)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace novideo_srgb.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)"/>
|
||||
</Profiles>
|
||||
<Settings/>
|
||||
</SettingsFile>
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Controls;
|
||||
using static System.Double;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class RangeRule : ValidationRule
|
||||
{
|
||||
public int Min { get; set; }
|
||||
public int Max { get; set; }
|
||||
|
||||
public override ValidationResult Validate(object valueObj, CultureInfo cultureInfo)
|
||||
{
|
||||
if (valueObj == null) return null;
|
||||
|
||||
var valueString = (string)valueObj;
|
||||
if (valueString.EndsWith("."))
|
||||
{
|
||||
return new ValidationResult(false, "Input must not end in '.'");
|
||||
}
|
||||
|
||||
double value = 0;
|
||||
try
|
||||
{
|
||||
if (valueString.Length > 0)
|
||||
value = Parse(valueString.Replace(',', 'a'), CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ValidationResult(false, $"Illegal characters or {e.Message}");
|
||||
}
|
||||
|
||||
if (value < Min || value > Max)
|
||||
{
|
||||
return new ValidationResult(false,
|
||||
$"Value must be between {Min} and {Max}");
|
||||
}
|
||||
|
||||
return ValidationResult.ValidResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public class SrgbEOTF : ToneCurve
|
||||
{
|
||||
private double _black;
|
||||
|
||||
public SrgbEOTF(double black)
|
||||
{
|
||||
_black = black;
|
||||
}
|
||||
|
||||
public double SampleAt(double x)
|
||||
{
|
||||
if (x >= 1) return 1;
|
||||
|
||||
double result;
|
||||
if (x <= 0.04045)
|
||||
{
|
||||
result = x / 12.92;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = Math.Pow((x + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
return result * (1 - _black) + _black;
|
||||
}
|
||||
|
||||
public double SampleInverseAt(double x)
|
||||
{
|
||||
if (_black != 0) throw new NotSupportedException();
|
||||
if (x >= 1) return 1;
|
||||
|
||||
if (x <= 0.0031308) return 12.92 * x;
|
||||
return 1.055 * Math.Pow(x, 1 / 2.4) - 0.055;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace novideo_srgb
|
||||
{
|
||||
public interface ToneCurve
|
||||
{
|
||||
double SampleAt(double x);
|
||||
double SampleInverseAt(double x);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,49 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows10.0.20348.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<UseWPF>true</UseWPF>
|
||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.1.2" />
|
||||
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.14" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="EDIDParser" Version="1.2.5.4" />
|
||||
<PackageReference Include="WindowsDisplayAPI" Version="1.3.0.13" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NvAPIWrapper\NvAPIWrapper.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="icon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in new issue
Block a user