diff --git a/ColorControl.sln b/ColorControl.sln
index 1b8d8f4..6a445c8 100644
--- a/ColorControl.sln
+++ b/ColorControl.sln
@@ -15,6 +15,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogViewer", "NLogViewer\NL
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LittleCms", "LittleCms\LittleCms.csproj", "{42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MHC2Gen", "MHC2Gen\MHC2Gen.csproj", "{9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -71,6 +75,22 @@ Global
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Release|Any CPU.Build.0 = Release|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Release|x64.ActiveCfg = Release|Any CPU
{8F38D6B0-82DA-4DD1-89EB-918D8A18AEB1}.Release|x64.Build.0 = Release|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Debug|x64.Build.0 = Debug|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|x64.ActiveCfg = Release|Any CPU
+ {42D251AE-42AF-4C0D-B163-7C7A13C4A9A6}.Release|x64.Build.0 = Release|Any CPU
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|Any CPU.Build.0 = Debug|x64
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|x64.ActiveCfg = Debug|x64
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Debug|x64.Build.0 = Debug|x64
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|Any CPU.ActiveCfg = Release|x64
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|Any CPU.Build.0 = Release|x64
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|x64.ActiveCfg = Release|x64
+ {9FDF741F-0ECF-46DB-8671-AB11ACEB9F41}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj
index 0aa2f21..d7ff0cd 100644
--- a/ColorControl/ColorControl.csproj
+++ b/ColorControl/ColorControl.csproj
@@ -18,8 +18,8 @@
Maassoft
Maassoft
0
- 9.7.2.0
- 9.7.2.0
+ 9.8.0.0
+ 9.8.0.0
false
true
false
@@ -29,7 +29,7 @@
true
pngbarn.ico
10.0.20348.0
- 2020-2023 Maassoft
+ 2020-2024 Maassoft
Maassoft
CF0BC153EA664CD97AC6F234B91F45D34630958F
ColorControl_1_TemporaryKey.pfx
@@ -52,6 +52,7 @@
$(MSBuildThisFileDirectory)=.\
+
@@ -113,11 +114,16 @@
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
+
diff --git a/ColorControl/Forms/InfoPanel.cs b/ColorControl/Forms/InfoPanel.cs
index 9133f57..780116b 100644
--- a/ColorControl/Forms/InfoPanel.cs
+++ b/ColorControl/Forms/InfoPanel.cs
@@ -8,15 +8,12 @@ namespace ColorControl.Forms
public partial class InfoPanel : UserControl
{
private readonly WinApiService _winApiService;
- private bool _initialized;
public InfoPanel(WinApiService winApiService)
{
_winApiService = winApiService;
InitializeComponent();
- _initialized = true;
-
if (DarkModeUtils.UseDarkMode)
{
grpInfo.ForeColor = FormUtils.CurrentForeColor;
@@ -29,14 +26,14 @@ namespace ColorControl.Forms
{
var currentVersionInfo = FileVersionInfo.GetVersionInfo(Application.ExecutablePath);
- Text = Application.ProductName + " " + Application.ProductVersion;
+ var text = Application.ProductName + " " + Application.ProductVersion;
if (_winApiService.IsAdministrator())
{
- Text += " (administrator)";
+ text += " (administrator)";
}
- lblInfo.Text = Text + " - " + currentVersionInfo.LegalCopyright;
+ lblInfo.Text = text + " - " + currentVersionInfo.LegalCopyright;
lbPlugins.Items.Add("lgtv.net by gr4b4z");
lbPlugins.Items.Add("Newtonsoft.Json by James Newton-King");
@@ -49,7 +46,8 @@ namespace ColorControl.Forms
lbPlugins.Items.Add("Novideo sRGB by ledoge");
lbPlugins.Items.Add("NLogViewer by dojo90");
lbPlugins.Items.Add("WPFDarkTheme by AngryCarrot789");
-
+ lbPlugins.Items.Add("Little-CMS by mm2");
+ lbPlugins.Items.Add("MHC2 by dantmnf");
}
}
diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs
index c627d91..3ce3d9a 100644
--- a/ColorControl/MainForm.Designer.cs
+++ b/ColorControl/MainForm.Designer.cs
@@ -28,6 +28,7 @@
///
private void InitializeComponent()
{
+ components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
tcMain = new System.Windows.Forms.TabControl();
tabOptions = new System.Windows.Forms.TabPage();
@@ -35,6 +36,7 @@
label1 = new System.Windows.Forms.Label();
chkModules = new System.Windows.Forms.CheckedListBox();
grpMiscellaneousOptions = new System.Windows.Forms.GroupBox();
+ btnOptionsColorProfiles = new System.Windows.Forms.Button();
btnSetShortcutScreenSaver = new System.Windows.Forms.Button();
label11 = new System.Windows.Forms.Label();
edtBlankScreenSaverShortcut = new System.Windows.Forms.TextBox();
@@ -60,11 +62,14 @@
chkStartAfterLogin = new System.Windows.Forms.CheckBox();
tabInfo = new System.Windows.Forms.TabPage();
btnUpdate = new System.Windows.Forms.Button();
+ mnuColorProfiles = new System.Windows.Forms.ContextMenuStrip(components);
+ miCreateHDRColorProfile = new System.Windows.Forms.ToolStripMenuItem();
tcMain.SuspendLayout();
tabOptions.SuspendLayout();
grpOptionsModules.SuspendLayout();
grpMiscellaneousOptions.SuspendLayout();
grpGeneralOptions.SuspendLayout();
+ mnuColorProfiles.SuspendLayout();
SuspendLayout();
//
// tcMain
@@ -130,6 +135,7 @@
//
// grpMiscellaneousOptions
//
+ grpMiscellaneousOptions.Controls.Add(btnOptionsColorProfiles);
grpMiscellaneousOptions.Controls.Add(btnSetShortcutScreenSaver);
grpMiscellaneousOptions.Controls.Add(label11);
grpMiscellaneousOptions.Controls.Add(edtBlankScreenSaverShortcut);
@@ -144,6 +150,17 @@
grpMiscellaneousOptions.TabStop = false;
grpMiscellaneousOptions.Text = "Miscellaneous";
//
+ // btnOptionsColorProfiles
+ //
+ btnOptionsColorProfiles.Location = new System.Drawing.Point(538, 17);
+ btnOptionsColorProfiles.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ btnOptionsColorProfiles.Name = "btnOptionsColorProfiles";
+ btnOptionsColorProfiles.Size = new System.Drawing.Size(112, 27);
+ btnOptionsColorProfiles.TabIndex = 12;
+ btnOptionsColorProfiles.Text = "Color Profiles...";
+ btnOptionsColorProfiles.UseVisualStyleBackColor = true;
+ btnOptionsColorProfiles.Click += btnOptionsColorProfiles_Click;
+ //
// btnSetShortcutScreenSaver
//
btnSetShortcutScreenSaver.Location = new System.Drawing.Point(181, 98);
@@ -455,6 +472,20 @@
btnUpdate.Visible = false;
btnUpdate.Click += btnUpdate_Click;
//
+ // mnuColorProfiles
+ //
+ mnuColorProfiles.ImageScalingSize = new System.Drawing.Size(20, 20);
+ mnuColorProfiles.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miCreateHDRColorProfile });
+ mnuColorProfiles.Name = "mnuLgButtons";
+ mnuColorProfiles.Size = new System.Drawing.Size(214, 26);
+ //
+ // miCreateHDRColorProfile
+ //
+ miCreateHDRColorProfile.Name = "miCreateHDRColorProfile";
+ miCreateHDRColorProfile.Size = new System.Drawing.Size(213, 22);
+ miCreateHDRColorProfile.Text = "Create HDR Color Profile...";
+ miCreateHDRColorProfile.Click += miCreateHDRColorProfile_Click;
+ //
// MainForm
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
@@ -486,6 +517,7 @@
grpMiscellaneousOptions.PerformLayout();
grpGeneralOptions.ResumeLayout(false);
grpGeneralOptions.PerformLayout();
+ mnuColorProfiles.ResumeLayout(false);
ResumeLayout(false);
}
@@ -522,6 +554,9 @@
private System.Windows.Forms.CheckBox chkOptionsUseDarkMode;
private System.Windows.Forms.Button btnOptionsAdvanced;
private System.Windows.Forms.Button btnOptionsLog;
+ private System.Windows.Forms.Button btnOptionsColorProfiles;
+ private System.Windows.Forms.ContextMenuStrip mnuColorProfiles;
+ private System.Windows.Forms.ToolStripMenuItem miCreateHDRColorProfile;
}
}
diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs
index 8a04bdf..6d97155 100644
--- a/ColorControl/MainForm.cs
+++ b/ColorControl/MainForm.cs
@@ -66,7 +66,6 @@ namespace ColorControl
private AmdPanel _amdPanel;
private bool _skipResize;
- private FileVersionInfo _currentVersionInfo;
private bool _checkedForUpdates = false;
private string _updateHtmlUrl;
private string _downloadUrl;
@@ -96,6 +95,15 @@ namespace ColorControl
_dataDir = Program.DataDir;
_config = Program.Config;
+ var currentVersionInfo = FileVersionInfo.GetVersionInfo(Application.ExecutablePath);
+
+ Text = Application.ProductName + " " + Application.ProductVersion;
+
+ if (_winApiService.IsAdministrator())
+ {
+ Text += " (administrator)";
+ }
+
LoadConfig();
KeyboardShortcutManager.SetUseRawInput(_config.UseRawInput);
@@ -1423,5 +1431,15 @@ Currently ColorControl is {(_winApiService.IsAdministrator() ? "" : "not ")}runn
var module = _config.Modules[index];
module.IsActive = e.NewValue == CheckState.Checked;
}
+
+ private void miCreateHDRColorProfile_Click(object sender, EventArgs e)
+ {
+ ColorProfileWindow.CreateAndShow();
+ }
+
+ private void btnOptionsColorProfiles_Click(object sender, EventArgs e)
+ {
+ mnuColorProfiles.ShowCustom(btnOptionsColorProfiles);
+ }
}
}
\ No newline at end of file
diff --git a/ColorControl/MainForm.resx b/ColorControl/MainForm.resx
index 1de7ce7..74bd8a9 100644
--- a/ColorControl/MainForm.resx
+++ b/ColorControl/MainForm.resx
@@ -117,6 +117,9 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ 147, 17
+
diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs
index 35fb9e2..6646ffa 100644
--- a/ColorControl/Program.cs
+++ b/ColorControl/Program.cs
@@ -256,6 +256,7 @@ namespace ColorControl
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddTransient();
services.AddSingleton();
services.AddSingleton();
});
diff --git a/ColorControl/Services/GameLauncher/GameService.cs b/ColorControl/Services/GameLauncher/GameService.cs
index cf4db42..21ba132 100644
--- a/ColorControl/Services/GameLauncher/GameService.cs
+++ b/ColorControl/Services/GameLauncher/GameService.cs
@@ -77,7 +77,14 @@ namespace ColorControl.Services.GameLauncher
if (_winApiService.IsAdministrator())
{
- var fileName = process.MainModule.FileName;
+ var mainModule = process.MainModule;
+
+ if (mainModule == null)
+ {
+ return false;
+ }
+
+ var fileName = mainModule.FileName;
preset = _presets.FirstOrDefault(p => fileName.Contains(Path.GetDirectoryName(p.Path), System.StringComparison.OrdinalIgnoreCase));
}
diff --git a/ColorControl/Services/LG/LgPanel.cs b/ColorControl/Services/LG/LgPanel.cs
index 82dd916..1e58a11 100644
--- a/ColorControl/Services/LG/LgPanel.cs
+++ b/ColorControl/Services/LG/LgPanel.cs
@@ -829,7 +829,7 @@ Do you want to continue?"
mnuLgOLEDMotionPro.Visible = visible;
miLgEnableMotionPro.Visible = enableVisible;
- var svcMenuFlagVisible = new[] { "B2", "C2", "G2", "B3", "C3", "G3" }.Any(m => device?.ModelName?.Contains(m) == true);
+ var svcMenuFlagVisible = new[] { "A2", "B2", "C2", "G2", "A3", "B3", "C3", "G3" }.Any(m => device?.ModelName?.Contains(m) == true);
mnuLgSetSvcMenuFlag.Visible = svcMenuFlagVisible;
diff --git a/ColorControl/Services/NVIDIA/NvPanel.Designer.cs b/ColorControl/Services/NVIDIA/NvPanel.Designer.cs
index 987376b..3fbd11d 100644
--- a/ColorControl/Services/NVIDIA/NvPanel.Designer.cs
+++ b/ColorControl/Services/NVIDIA/NvPanel.Designer.cs
@@ -57,6 +57,8 @@ namespace ColorControl.Services.NVIDIA
miNvPrimaryDisplay = new System.Windows.Forms.ToolStripMenuItem();
mnuNvPresetsColorSettings = new System.Windows.Forms.ToolStripMenuItem();
miNvPresetColorSettings = new System.Windows.Forms.ToolStripMenuItem();
+ mnuNvPresetsColorEnhancements = new System.Windows.Forms.ToolStripMenuItem();
+ miNvPresetColorEnhancementsIncluded = new System.Windows.Forms.ToolStripMenuItem();
mnuRefreshRate = new System.Windows.Forms.ToolStripMenuItem();
miRefreshRateIncluded = new System.Windows.Forms.ToolStripMenuItem();
mnuNvResolution = new System.Windows.Forms.ToolStripMenuItem();
@@ -94,8 +96,8 @@ namespace ColorControl.Services.NVIDIA
miNvNovideo = new System.Windows.Forms.ToolStripMenuItem();
miNvSettings = new System.Windows.Forms.ToolStripMenuItem();
miNvTestDithering = new System.Windows.Forms.ToolStripMenuItem();
- lvNvPresetsToolTip = new System.Windows.Forms.ToolTip(components);
miNVIDIAInfo = new System.Windows.Forms.ToolStripMenuItem();
+ lvNvPresetsToolTip = new System.Windows.Forms.ToolTip(components);
mnuNvPresets.SuspendLayout();
mnuNvSettings.SuspendLayout();
SuspendLayout();
@@ -326,9 +328,9 @@ namespace ColorControl.Services.NVIDIA
// mnuNvPresets
//
mnuNvPresets.ImageScalingSize = new System.Drawing.Size(20, 20);
- mnuNvPresets.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvApply, miNvPresetApplyOnStartup, tssNvPresetMenu, mnuNvDisplay, mnuNvPresetsColorSettings, mnuRefreshRate, mnuNvResolution, miNvPresetDithering, miNvHDR, mnuNvDriverSettings, mnuNvOtherSettings, mnuNvHdmiSettings, mnuNvOverclocking, miNvCopyId });
+ mnuNvPresets.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvApply, miNvPresetApplyOnStartup, tssNvPresetMenu, mnuNvDisplay, mnuNvPresetsColorSettings, mnuNvPresetsColorEnhancements, mnuRefreshRate, mnuNvResolution, miNvPresetDithering, miNvHDR, mnuNvDriverSettings, mnuNvOtherSettings, mnuNvHdmiSettings, mnuNvOverclocking, miNvCopyId });
mnuNvPresets.Name = "mnuNvPresets";
- mnuNvPresets.Size = new System.Drawing.Size(185, 296);
+ mnuNvPresets.Size = new System.Drawing.Size(185, 340);
mnuNvPresets.Opening += mnuNvPresets_Opening;
//
// miNvApply
@@ -375,10 +377,24 @@ namespace ColorControl.Services.NVIDIA
// miNvPresetColorSettings
//
miNvPresetColorSettings.Name = "miNvPresetColorSettings";
- miNvPresetColorSettings.Size = new System.Drawing.Size(120, 22);
+ miNvPresetColorSettings.Size = new System.Drawing.Size(180, 22);
miNvPresetColorSettings.Text = "Included";
miNvPresetColorSettings.Click += miNvPresetColorSettings_Click;
//
+ // mnuNvPresetsColorEnhancements
+ //
+ mnuNvPresetsColorEnhancements.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvPresetColorEnhancementsIncluded });
+ mnuNvPresetsColorEnhancements.Name = "mnuNvPresetsColorEnhancements";
+ mnuNvPresetsColorEnhancements.Size = new System.Drawing.Size(184, 22);
+ mnuNvPresetsColorEnhancements.Text = "Color Enhancements";
+ //
+ // miNvPresetColorEnhancementsIncluded
+ //
+ miNvPresetColorEnhancementsIncluded.Name = "miNvPresetColorEnhancementsIncluded";
+ miNvPresetColorEnhancementsIncluded.Size = new System.Drawing.Size(180, 22);
+ miNvPresetColorEnhancementsIncluded.Text = "Included";
+ miNvPresetColorEnhancementsIncluded.Click += miNvPresetColorEnhancementsIncluded_Click;
+ //
// mnuRefreshRate
//
mnuRefreshRate.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { miRefreshRateIncluded });
@@ -638,7 +654,7 @@ namespace ColorControl.Services.NVIDIA
mnuNvSettings.ImageScalingSize = new System.Drawing.Size(20, 20);
mnuNvSettings.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { miNvProfileInspector, miNvNovideo, miNvSettings, miNvTestDithering, miNVIDIAInfo });
mnuNvSettings.Name = "mnuLgButtons";
- mnuNvSettings.Size = new System.Drawing.Size(202, 136);
+ mnuNvSettings.Size = new System.Drawing.Size(202, 114);
//
// miNvProfileInspector
//
@@ -778,5 +794,7 @@ namespace ColorControl.Services.NVIDIA
private System.Windows.Forms.ToolStripMenuItem miNvHdmiIncluded;
private System.Windows.Forms.ToolStripMenuItem miNvTestDithering;
private System.Windows.Forms.ToolStripMenuItem miNVIDIAInfo;
+ private System.Windows.Forms.ToolStripMenuItem mnuNvPresetsColorEnhancements;
+ private System.Windows.Forms.ToolStripMenuItem miNvPresetColorEnhancementsIncluded;
}
}
diff --git a/ColorControl/Services/NVIDIA/NvPanel.cs b/ColorControl/Services/NVIDIA/NvPanel.cs
index 5793aca..b38472c 100644
--- a/ColorControl/Services/NVIDIA/NvPanel.cs
+++ b/ColorControl/Services/NVIDIA/NvPanel.cs
@@ -337,6 +337,8 @@ namespace ColorControl.Services.NVIDIA
mnuNvDisplay.Visible = presetItemsVisible;
miNvPresetColorSettings.Enabled = presetItemsVisible;
mnuNvPresetsColorSettings.Enabled = preset != null;
+ miNvPresetColorEnhancementsIncluded.Enabled = presetItemsVisible;
+ mnuNvPresetsColorEnhancements.Enabled = preset != null;
mnuRefreshRate.Enabled = preset != null;
mnuNvResolution.Enabled = preset != null;
miNvPresetDithering.Enabled = preset != null;
@@ -363,6 +365,7 @@ namespace ColorControl.Services.NVIDIA
miNvPresetApplyDithering.Visible = !isCurrentDisplay;
miNvPresetColorSettings.Visible = !isCurrentDisplay;
+ miNvPresetColorEnhancementsIncluded.Visible = !isCurrentDisplay;
miNvResolutionIncluded.Visible = !isCurrentDisplay;
miRefreshRateIncluded.Visible = !isCurrentDisplay;
miHDRIncluded.Visible = !isCurrentDisplay;
@@ -422,8 +425,10 @@ namespace ColorControl.Services.NVIDIA
miNvOtherIncluded.Checked = preset.applyOther;
miNvHdmiIncluded.Checked = preset.applyHdmiSettings;
miNvDriverSettingsIncluded.Checked = preset.applyDriverSettings;
+ miNvPresetColorEnhancementsIncluded.Checked = preset.ApplyColorEnhancements;
mnuNvPresetsColorSettings.Font = _menuItemFonts[preset.applyColorData];
+ mnuNvPresetsColorEnhancements.Font = _menuItemFonts[preset.ApplyColorEnhancements];
mnuRefreshRate.Font = _menuItemFonts[preset.applyRefreshRate];
mnuNvResolution.Font = _menuItemFonts[preset.applyResolution];
miNvPresetDithering.Font = _menuItemFonts[preset.applyDithering];
@@ -432,22 +437,19 @@ namespace ColorControl.Services.NVIDIA
mnuNvOtherSettings.Font = _menuItemFonts[preset.applyOther];
mnuNvHdmiSettings.Font = _menuItemFonts[preset.applyHdmiSettings];
- var itemName = $"{mnuNvOtherSettings.Name}_SDRBrightness";
- var brightnessMenu = FormUtils.BuildMenuItem(mnuNvOtherSettings.DropDownItems, itemName, "SDR brightness");
+ FormUtils.BuildMenuItemAndSub(mnuNvPresetsColorEnhancements, "Digital Vibrance",
+ $"{preset.ColorEnhancementSettings.DigitalVibranceLevel}% (click to change)", nvPresetDvcMenuItem_Click);
+ FormUtils.BuildMenuItemAndSub(mnuNvPresetsColorEnhancements, "Hue",
+ $"{preset.ColorEnhancementSettings.HueAngle}° (click to change)", nvPresetHueMenuItem_Click);
- var subItemName = $"{itemName}_SubItem";
- var subItem = FormUtils.BuildMenuItem(brightnessMenu.DropDownItems, subItemName, "", onClick: nvPresetBrightnessMenuItem_Click);
- subItem.Text = (preset.SDRBrightness.HasValue ? $"{preset.SDRBrightness.Value}%" : "Unset") + " (click to change)";
+ FormUtils.BuildMenuItemAndSub(mnuNvOtherSettings, "SDR brightness",
+ (preset.SDRBrightness.HasValue ? $"{preset.SDRBrightness.Value}%" : "Unset") + " (click to change)", nvPresetBrightnessMenuItem_Click);
FormUtils.BuildDropDownMenu(mnuNvOtherSettings, "Scaling", typeof(Scaling), preset, "scaling", nvPresetScalingMenuItem_Click, unchanged: true, skipValues: new object[] { Scaling.Customized })
.DropDownItems[0].Visible = !isCurrentDisplay;
- var itemNameProfile = $"{mnuNvOtherSettings.Name}_Profile";
- var profileMenu = FormUtils.BuildMenuItem(mnuNvOtherSettings.DropDownItems, itemNameProfile, "Color Profile");
-
- var subItemNameProfile = $"{itemNameProfile}_SubItem";
- var subItemProfile = FormUtils.BuildMenuItem(profileMenu.DropDownItems, subItemNameProfile, "", onClick: nvPresetColorProfileMenuItem_Click);
- subItemProfile.Text = (!preset.ColorProfileSettings.ProfileName.IsNullOrWhiteSpace() ? $"{preset.ColorProfileSettings.ProfileName}" : "Unset") + " (click to change)";
+ FormUtils.BuildMenuItemAndSub(mnuNvOtherSettings, "Color Profile",
+ (!preset.ColorProfileSettings.ProfileName.IsNullOrWhiteSpace() ? $"{preset.ColorProfileSettings.ProfileName}" : "Unset") + " (click to change)", nvPresetColorProfileMenuItem_Click);
FormUtils.BuildDropDownMenu(mnuNvHdmiSettings, "Content type", typeof(InfoFrameVideoContentType), preset.HdmiInfoFrameSettings, "ContentType", nvPresetHdmiContentTypeMenuItem_Click, unchanged: true)
.DropDownItems[0].Visible = !isCurrentDisplay;
@@ -833,6 +835,68 @@ namespace ColorControl.Services.NVIDIA
AddOrUpdateItem();
}
+ private void nvPresetDvcMenuItem_Click(object sender, EventArgs e)
+ {
+ var preset = GetSelectedNvPreset(true);
+
+ var label = "Digital vibrance in % (clear to set to default)";
+
+ var display = _nvService.ResolveDisplay(preset);
+ var dvControl = display.DigitalVibranceControl;
+
+ var value = SelectValue(dvControl.MinimumLevel, dvControl.MaximumLevel, preset.ColorEnhancementSettings.DigitalVibranceLevel, label);
+
+ if (value == -1)
+ {
+ return;
+ }
+
+ var intValue = value ?? dvControl.DefaultLevel;
+
+ if (preset.IsDisplayPreset)
+ {
+ _nvService.SetDigitalVibranceLevel(preset.Display, intValue);
+
+ UpdateDisplayInfoItems();
+ return;
+ }
+
+ preset.ColorEnhancementSettings.DigitalVibranceLevel = intValue;
+
+ AddOrUpdateItem();
+ }
+
+ private void nvPresetHueMenuItem_Click(object sender, EventArgs e)
+ {
+ var preset = GetSelectedNvPreset(true);
+
+ var label = "Hue angle in ° (clear to set to default)";
+
+ var display = _nvService.ResolveDisplay(preset);
+ var hueControl = display.HUEControl;
+
+ var value = SelectValue(hueControl.DefaultAngle, 359, preset.ColorEnhancementSettings.HueAngle, label);
+
+ if (value == -1)
+ {
+ return;
+ }
+
+ var intValue = value ?? hueControl.DefaultAngle;
+
+ if (preset.IsDisplayPreset)
+ {
+ _nvService.SetHueAngle(preset.Display, intValue);
+
+ UpdateDisplayInfoItems();
+ return;
+ }
+
+ preset.ColorEnhancementSettings.HueAngle = intValue;
+
+ AddOrUpdateItem();
+ }
+
private void nvPresetScalingMenuItem_Click(object sender, EventArgs e)
{
var menuItem = (ToolStripMenuItem)sender;
@@ -1931,5 +1995,14 @@ namespace ColorControl.Services.NVIDIA
MessageForms.ShowControl(panel, "NVIDIA Info", height: 800);
}
+
+ private void miNvPresetColorEnhancementsIncluded_Click(object sender, EventArgs e)
+ {
+ var preset = GetSelectedNvPreset();
+
+ preset.ApplyColorEnhancements = !preset.ApplyColorEnhancements;
+
+ AddOrUpdateItem();
+ }
}
}
diff --git a/ColorControl/Services/NVIDIA/NvPanel.resx b/ColorControl/Services/NVIDIA/NvPanel.resx
index 86701cd..474820f 100644
--- a/ColorControl/Services/NVIDIA/NvPanel.resx
+++ b/ColorControl/Services/NVIDIA/NvPanel.resx
@@ -18,7 +18,7 @@
System.Resources.ResXResourceReader, System.Windows.Forms, ...
System.Resources.ResXResourceWriter, System.Windows.Forms, ...
this is my long stringthis is a comment
- Blue
+ Blue
[base64 mime encoded serialized .NET Framework object]
diff --git a/ColorControl/Services/NVIDIA/NvPreset.cs b/ColorControl/Services/NVIDIA/NvPreset.cs
index ad04060..4bb50c7 100644
--- a/ColorControl/Services/NVIDIA/NvPreset.cs
+++ b/ColorControl/Services/NVIDIA/NvPreset.cs
@@ -20,6 +20,8 @@ namespace ColorControl.Services.NVIDIA
public bool applyColorData { get; set; }
public ColorData colorData { get; set; }
+ public bool ApplyColorEnhancements { get; set; }
+ public NvColorEnhancementSettings ColorEnhancementSettings { get; set; }
public bool applyHDR { get; set; }
public bool HDREnabled { get; set; }
public bool toggleHDR { get; set; }
@@ -77,6 +79,7 @@ namespace ColorControl.Services.NVIDIA
ocSettings = new List();
HdmiInfoFrameSettings = new NvHdmiInfoFrameSettings();
ColorProfileSettings = new NvColorProfileSettings();
+ ColorEnhancementSettings = new NvColorEnhancementSettings();
}
public NvPreset(ColorData colorData) : this()
@@ -116,6 +119,9 @@ namespace ColorControl.Services.NVIDIA
HdmiInfoFrameSettings = new NvHdmiInfoFrameSettings(preset.HdmiInfoFrameSettings);
applyHdmiSettings = preset.applyHdmiSettings;
+
+ ColorEnhancementSettings = new NvColorEnhancementSettings(preset.ColorEnhancementSettings);
+ ApplyColorEnhancements = preset.ApplyColorEnhancements;
}
public NvPreset Clone()
@@ -127,7 +133,7 @@ namespace ColorControl.Services.NVIDIA
public static string[] GetColumnNames()
{
- return new[] { "Name", "Display|140", "Color settings (BPC, format, dyn. range, color space)|260", "Refresh rate|100", "Resolution|120", "Dithering", "HDR", "Driver settings|300", "HDMI settings|200", "Other|200", "Overclocking|300", "Shortcut", "Apply on startup" };
+ return new[] { "Name", "Display|140", "Color settings (BPC, format, dyn. range, color space)|260", "Color enhancements", "Refresh rate|100", "Resolution|120", "Dithering", "HDR", "Driver settings|300", "HDMI settings|200", "Other|200", "Overclocking|300", "Shortcut", "Apply on startup" };
}
public override List GetDisplayValues(Config config = null)
@@ -145,6 +151,9 @@ namespace ColorControl.Services.NVIDIA
var colorSettings = FormatDisplaySetting(string.Format("{0}, {1}, {2}, {3}", colorData.ColorDepth, colorData.ColorFormat, colorData.DynamicRange, colorData.Colorimetry), isCurrent, applyColorData);
values.Add(colorSettings);
+
+ values.Add(FormatDisplaySetting(GetColorEnhancementsDescription(), isCurrent, ApplyColorEnhancements));
+
values.Add(FormatDisplaySetting(string.Format("{0}Hz", refreshRate), isCurrent, applyRefreshRate));
if (applyResolution || resolutionWidth > 0)
@@ -298,6 +307,27 @@ namespace ColorControl.Services.NVIDIA
return string.Join(", ", values);
}
+ public string GetColorEnhancementsDescription()
+ {
+ var values = new List();
+
+ if (ColorEnhancementSettings.DigitalVibranceLevel != 50)
+ {
+ values.Add($"Digital vibrance: {ColorEnhancementSettings.DigitalVibranceLevel}%");
+ }
+ if (ColorEnhancementSettings.HueAngle != 0)
+ {
+ values.Add($"Hue: {ColorEnhancementSettings.HueAngle}°");
+ }
+
+ if (!values.Any())
+ {
+ values.Add("None");
+ }
+
+ return string.Join(", ", values);
+ }
+
public static ColorData DefaultColorData => new ColorData(ColorDataFormat.Auto, dynamicRange: ColorDataDynamicRange.Auto, colorDepth: ColorDataDepth.Default, colorSelectionPolicy: ColorDataSelectionPolicy.BestQuality);
public static List GetDefaultPresets()
diff --git a/ColorControl/Services/NVIDIA/NvService.cs b/ColorControl/Services/NVIDIA/NvService.cs
index 90c633c..ca17db5 100644
--- a/ColorControl/Services/NVIDIA/NvService.cs
+++ b/ColorControl/Services/NVIDIA/NvService.cs
@@ -398,6 +398,12 @@ namespace ColorControl.Services.NVIDIA
}
}
+ if (preset.ApplyColorEnhancements)
+ {
+ SetDigitalVibranceLevel(display, preset.ColorEnhancementSettings.DigitalVibranceLevel);
+ SetHueAngle(display, preset.ColorEnhancementSettings.HueAngle);
+ }
+
if (preset.applyDriverSettings)
{
SetDriverSettings(preset.driverSettings);
@@ -609,6 +615,28 @@ namespace ColorControl.Services.NVIDIA
return 0;
}
+ public void SetDigitalVibranceLevel(Display display, int level)
+ {
+ var dvControl = display.DigitalVibranceControl;
+
+ dvControl.CurrentLevel = level;
+ }
+
+ public int GetDigitalVibranceLevel(Display display)
+ {
+ return display.DigitalVibranceControl.CurrentLevel;
+ }
+
+ public void SetHueAngle(Display display, int angle)
+ {
+ display.HUEControl.CurrentAngle = angle;
+ }
+
+ public int GetHueAngle(Display display)
+ {
+ return display.HUEControl.CurrentAngle;
+ }
+
public bool SetDithering(NvDitherState state, uint bits = 1, uint mode = 4, NvPreset preset = null, Display currentDisplay = null)
{
var result = true;
@@ -1132,6 +1160,7 @@ namespace ColorControl.Services.NVIDIA
preset.displayName = FormUtils.ExtendedDisplayName(display.Name);
preset.colorData = GetCurrentColorData(display);
+ preset.ColorEnhancementSettings = GetCurrentColorEnhancements(display);
preset.HdmiInfoFrameSettings = GetHdmiSettings(display);
preset.SDRBrightness = GetSDRBrightness(display);
preset.scaling = GetScaling(display);
@@ -1199,6 +1228,15 @@ namespace ColorControl.Services.NVIDIA
return preset;
}
+ private NvColorEnhancementSettings GetCurrentColorEnhancements(Display display)
+ {
+ return new NvColorEnhancementSettings
+ {
+ DigitalVibranceLevel = GetDigitalVibranceLevel(display),
+ HueAngle = GetHueAngle(display)
+ };
+ }
+
public List GetVisibleSettings()
{
return GetSettings().Where(s => _driverSettingIds.Contains(s.SettingId)).ToList();
@@ -1368,14 +1406,14 @@ namespace ColorControl.Services.NVIDIA
Utils.WriteObject(_configFilename, Config);
}
- internal void ResolveDisplay(NvPreset preset)
+ internal Display ResolveDisplay(NvPreset preset)
{
- if (preset.Display != null)
+ if (preset.Display == null)
{
- return;
+ preset.Display = GetPresetDisplay(preset);
}
- preset.Display = GetPresetDisplay(preset);
+ return preset.Display;
}
}
}
diff --git a/ColorControl/XForms/ColorProfileViewModel.cs b/ColorControl/XForms/ColorProfileViewModel.cs
new file mode 100644
index 0000000..4a63820
--- /dev/null
+++ b/ColorControl/XForms/ColorProfileViewModel.cs
@@ -0,0 +1,260 @@
+using ColorControl.Shared.Common;
+using ColorControl.Shared.Forms;
+using ColorControl.Shared.Native;
+using ColorControl.Shared.XForms;
+using MHC2Gen;
+using novideo_srgb;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Windows.Forms;
+using WindowsDisplayAPI;
+
+namespace ColorControl.XForms;
+
+public enum SaveOption
+{
+ Install,
+ [Description("Install and set as default")]
+ InstallAndSetAsDefault,
+ [Description("Save to file")]
+ SaveToFile
+}
+
+public enum DisplayPrimariesSource
+{
+ Custom,
+ EDID,
+ Windows
+}
+
+public class CustomDisplay
+{
+ public Display Display { get; set; }
+
+ public string DisplayName => Display.DisplayName;
+ public string DevicePath => Display.DevicePath;
+ public bool IsHDRSupportedAndEnabled()
+ {
+ var advancedColor = CCD.GetAdvancedColorSupportedAndEnabled(DisplayName);
+
+ return advancedColor.isSupported && advancedColor.isEnabled;
+ }
+
+ public CustomDisplay(Display display)
+ {
+ Display = display;
+ }
+
+ public override string ToString()
+ {
+ return $"{FormUtils.ExtendedDisplayName(Display.DisplayName)} on {Display.Adapter.DeviceName}";
+ }
+}
+
+public class ColorPointModel : BaseViewModel
+{
+ private double _x;
+ public double X { get => _x; set { _x = value; OnPropertyChanged(); } }
+
+ private double _y;
+ public double Y { get => _y; set { _y = value; OnPropertyChanged(); } }
+
+ public ColorPointModel()
+ {
+
+ }
+
+ public ColorPointModel(CIExy coord)
+ {
+ X = coord.x;
+ Y = coord.y;
+ }
+}
+
+internal class ColorProfileViewModel : BaseViewModel
+{
+ [StringLength(30)]
+ public string Description { get; set; }
+
+ public ColorPointModel RedPoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Red);
+ public ColorPointModel GreenPoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Green);
+ public ColorPointModel BluePoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.Blue);
+ public ColorPointModel WhitePoint { get; set; } = new ColorPointModel(RgbPrimaries.sRGB.White);
+ public Dictionary PrimariesSources { get; } = Utils.EnumToDictionary();
+ public DisplayPrimariesSource PrimariesSource { get; set; } = DisplayPrimariesSource.EDID;
+ public Dictionary ColorGamuts { get; } = Utils.EnumToDictionary();
+ public ColorGamut ColorGamut { get; set; } = ColorGamut.Native;
+ public Dictionary SDRTransferFunctions { get; } = Utils.EnumToDictionary();
+ public SDRTransferFunction SDRTransferFunction { get; set; } = SDRTransferFunction.PurePower;
+ [Range(0.1, 10)]
+ public double CustomGamma { get; set; } = 2.20;
+ [Range(0, 10)]
+ public double MinCLL { get; set; } = 0;
+ [Range(11, 10000)]
+ public double MaxCLL { get; set; } = 1000;
+ [Range(0, 10)]
+ public double BlackLuminance { get; set; } = 0;
+ [Range(11, 10000)]
+ public double WhiteLuminance { get; set; } = 1000;
+ [Range(0, 10)]
+ public double SDRMinBrightness { get; set; } = 0;
+ [Range(11, 1000)]
+ public double SDRMaxBrightness { get; set; } = 100;
+ [Range(-50, 50)]
+ public double SDRBrightnessBoost { get; set; }
+
+ public CustomDisplay SelectedDisplay { get; set; }
+ public IEnumerable Displays { get; set; }
+
+ public string SelectedDisplayName => SelectedDisplay?.DisplayName;
+
+ public List ExistingProfiles { get; set; }
+ public string SelectedExistingProfile { get; set; } = CreateANewProfile;
+ public string NewProfileName { get; set; }
+ public string ProfileName => SelectedExistingProfile == CreateANewProfile ? NewProfileName : SelectedExistingProfile;
+ public bool IsHDR { get; set; } = true;
+ public Dictionary SaveOptions { get; } = Utils.EnumToDictionary();
+ public SaveOption SaveOption { get; set; } = SaveOption.InstallAndSetAsDefault;
+
+
+ public override string this[string columnName]
+ {
+ get
+ {
+ var baseResult = base[columnName];
+
+ if (columnName == "SelectedDisplay" || columnName == "PrimariesSource")
+ {
+ UpdateDisplay();
+ }
+
+ return baseResult;
+ }
+ }
+
+ public override bool PreValidate()
+ {
+ return base.PreValidate() && Displays.Any();
+ }
+
+ public const string CreateANewProfile = "";
+
+ public ColorProfileViewModel(bool isHDR = true)
+ {
+ IsHDR = isHDR;
+
+ RefreshDisplays();
+
+ ExistingProfiles = new List { CreateANewProfile };
+ NewProfileName = $"CC_{(isHDR ? "HDR" : "SDR")}_profile_{DateTime.Now:yyyyMMddHHmmss}.icm";
+ Description = $"CC_{(isHDR ? "HDR" : "SDR")}";
+
+ if (Displays.Any())
+ {
+ SelectedDisplay = Displays.First();
+
+ UpdateDisplay();
+ }
+ }
+
+ public void RefreshDisplays()
+ {
+ Displays = Display.GetDisplays().Select(d => new CustomDisplay(d)).ToList();
+
+ if (IsHDR)
+ {
+ Displays = Displays.Where(d => d.IsHDRSupportedAndEnabled()).ToList();
+ }
+ }
+
+ private void UpdateDisplay()
+ {
+ if (SelectedDisplay == null)
+ {
+ return;
+ }
+
+ var colorParams = CCD.GetColorParams(SelectedDisplay.DisplayName);
+
+ if (colorParams.MaxLuminance > 0)
+ {
+ if (PrimariesSource == DisplayPrimariesSource.Windows)
+ {
+ var divider = colorParams.RedPointX <= 1 << 10 ? 1 << 10 : 1 << 20;
+
+ RedPoint.X = (double)colorParams.RedPointX / divider;
+ RedPoint.Y = (double)colorParams.RedPointY / divider;
+ GreenPoint.X = (double)colorParams.GreenPointX / divider;
+ GreenPoint.Y = (double)colorParams.GreenPointY / divider;
+ BluePoint.X = (double)colorParams.BluePointX / divider;
+ BluePoint.Y = (double)colorParams.BluePointY / divider;
+ WhitePoint.X = (double)colorParams.WhitePointX / divider;
+ WhitePoint.Y = (double)colorParams.WhitePointY / divider;
+ }
+
+ WhiteLuminance = (double)colorParams.MaxLuminance / 10000;
+ OnPropertyChanged(nameof(WhiteLuminance));
+ BlackLuminance = (double)colorParams.MinLuminance / 10000;
+ OnPropertyChanged(nameof(BlackLuminance));
+ MinCLL = BlackLuminance;
+ OnPropertyChanged(nameof(MinCLL));
+ MaxCLL = WhiteLuminance;
+ OnPropertyChanged(nameof(MaxCLL));
+ }
+
+ if (PrimariesSource == DisplayPrimariesSource.EDID)
+ {
+ GetEDID();
+ }
+
+ if (!CCD.GetUsePerUserDisplayProfiles(SelectedDisplay.DisplayName))
+ {
+ if (MessageForms.QuestionYesNo("To be able to select/activate a color profile, user specific settings have to be enabled. Do you want to enable this?") == DialogResult.Yes)
+ {
+ CCD.SetUsePerUserDisplayProfiles(SelectedDisplay.DisplayName, true);
+ }
+ }
+
+ ExistingProfiles = new List { CreateANewProfile };
+ ExistingProfiles.AddRange(CCD.GetDisplayColorProfiles(SelectedDisplay.DisplayName));
+ }
+
+ private void GetEDID()
+ {
+ if (SelectedDisplay == null)
+ {
+ return;
+ }
+
+ var path = SelectedDisplay.DevicePath;
+
+ var edid = Novideo.GetEDID(path, null);
+ var primaries = edid?.DisplayParameters?.ChromaticityCoordinates;
+
+ if (primaries == null)
+ {
+ return;
+ }
+
+ RedPoint.X = primaries.RedX;
+ RedPoint.Y = primaries.RedY;
+ GreenPoint.X = primaries.GreenX;
+ GreenPoint.Y = primaries.GreenY;
+ BluePoint.X = primaries.BlueX;
+ BluePoint.Y = primaries.BlueY;
+ WhitePoint.X = primaries.WhiteX;
+ WhitePoint.Y = primaries.WhiteY;
+ }
+
+ internal RgbPrimaries GetDevicePrimaries()
+ {
+ return new RgbPrimaries(
+ new CIExy { x = RedPoint.X, y = RedPoint.Y },
+ new CIExy { x = GreenPoint.X, y = GreenPoint.Y },
+ new CIExy { x = BluePoint.X, y = BluePoint.Y },
+ new CIExy { x = WhitePoint.X, y = WhitePoint.Y });
+ }
+}
diff --git a/ColorControl/XForms/ColorProfileWindow.xaml b/ColorControl/XForms/ColorProfileWindow.xaml
new file mode 100644
index 0000000..7030df6
--- /dev/null
+++ b/ColorControl/XForms/ColorProfileWindow.xaml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ColorControl/XForms/ColorProfileWindow.xaml.cs b/ColorControl/XForms/ColorProfileWindow.xaml.cs
new file mode 100644
index 0000000..dda1500
--- /dev/null
+++ b/ColorControl/XForms/ColorProfileWindow.xaml.cs
@@ -0,0 +1,152 @@
+using ColorControl.Shared.Forms;
+using ColorControl.Shared.Native;
+using ColorControl.Shared.Services;
+using ColorControl.Shared.XForms;
+using MHC2Gen;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Windows;
+using System.Windows.Forms.Integration;
+using System.Windows.Navigation;
+
+namespace ColorControl.XForms
+{
+ public partial class ColorProfileWindow : BaseWindow
+ {
+ private ColorProfileViewModel _viewModel;
+ private readonly WinApiAdminService _winApiAdminService;
+
+ public ColorProfileWindow(WinApiAdminService winApiAdminService)
+ {
+ _viewModel = new ColorProfileViewModel();
+
+ DataContext = _viewModel;
+ InitializeComponent();
+ _winApiAdminService = winApiAdminService;
+ }
+
+ private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri)
+ {
+ UseShellExecute = true,
+ };
+ System.Diagnostics.Process.Start(processStartInfo);
+ }
+
+ public static void CreateAndShow(bool show = true)
+ {
+ if (Application.Current == null)
+ {
+ new Application();
+ }
+
+ var window = Program.ServiceProvider.GetRequiredService();
+
+ if (show)
+ {
+ ElementHost.EnableModelessKeyboardInterop(window);
+ window.WindowState = WindowState.Normal;
+ window.Show();
+ window.Topmost = true;
+ window.Topmost = false;
+ }
+ }
+
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+ }
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ //e.Cancel = true;
+
+ //Hide();
+ }
+
+ protected override void OnStateChanged(EventArgs e)
+ {
+ if (WindowState == WindowState.Minimized)
+ {
+ Close();
+ }
+
+ base.OnStateChanged(e);
+ }
+
+ private void Close_Click(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (!_viewModel.Displays.Any())
+ {
+ MessageForms.WarningOk("There are no displays available that support HDR and have it enabled. Please activate HDR first.");
+ }
+ }
+
+ private void Button_Click(object sender, RoutedEventArgs e)
+ {
+ var command = new GenerateProfileCommand
+ {
+ Description = _viewModel.Description,
+ MinCLL = _viewModel.MinCLL,
+ MaxCLL = _viewModel.MaxCLL,
+ BlackLuminance = _viewModel.BlackLuminance,
+ WhiteLuminance = _viewModel.WhiteLuminance,
+ SDRMinBrightness = _viewModel.SDRMinBrightness,
+ SDRMaxBrightness = _viewModel.SDRMaxBrightness,
+ SDRTransferFunction = _viewModel.SDRTransferFunction,
+ SDRBrightnessBoost = _viewModel.SDRBrightnessBoost,
+ ColorGamut = _viewModel.ColorGamut,
+ Gamma = _viewModel.CustomGamma,
+ DevicePrimaries = _viewModel.GetDevicePrimaries(),
+ };
+
+ var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);
+
+ if (_viewModel.SaveOption == SaveOption.SaveToFile)
+ {
+ var saveFileDialog = new System.Windows.Forms.SaveFileDialog
+ {
+ FileName = _viewModel.NewProfileName
+ };
+ if (saveFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
+ {
+ File.WriteAllBytes(saveFileDialog.FileName, bytes);
+ }
+ return;
+ }
+
+ var tempFilename = Path.GetFullPath(_viewModel.ProfileName, Path.GetTempPath());
+
+ if (File.Exists(tempFilename))
+ {
+ File.Delete(tempFilename);
+ }
+
+ File.WriteAllBytes(tempFilename, bytes);
+
+ _winApiAdminService.UninstallColorProfile(tempFilename);
+
+ CCD.InstallColorProfile(tempFilename);
+
+ if (_viewModel.SaveOption != SaveOption.InstallAndSetAsDefault)
+ {
+ return;
+ }
+
+ var displayName = _viewModel.SelectedDisplayName;
+
+ var profileName = Path.GetFileName(tempFilename);
+
+ CCD.SetDisplayDefaultColorProfile(displayName, profileName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/LittleCms/CmsContext.cs b/LittleCms/CmsContext.cs
new file mode 100644
index 0000000..2a06e93
--- /dev/null
+++ b/LittleCms/CmsContext.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms
+{
+ public class CmsContext
+ {
+ public IntPtr Handle { get; }
+ public bool OwnedByGC { get; private set; }
+
+
+ static readonly ConcurrentDictionary GCOwnedContext = new();
+
+ public static CmsContext Default { get; } = new CmsContext(IntPtr.Zero, false);
+
+
+ private void MoveToGC()
+ {
+ OwnedByGC = true;
+
+ GCOwnedContext.TryAdd(Handle, this);
+ }
+
+ public CmsContext() {
+ Handle = CmsNative.cmsCreateContext(IntPtr.Zero, IntPtr.Zero);
+ MoveToGC();
+ }
+
+ private CmsContext(IntPtr handle, bool moveOwnership){
+ Handle = handle;
+ if (moveOwnership)
+ {
+ MoveToGC();
+ }
+ }
+
+ public static CmsContext GetFromHandle(IntPtr handle, bool moveOwnership = false)
+ {
+ if (handle == IntPtr.Zero) return Default;
+ if (GCOwnedContext.TryGetValue(handle, out var x)) return x;
+ return new CmsContext(handle, moveOwnership);
+ }
+
+ public double SetAdaptionState(double state)
+ {
+ return CmsNative.cmsSetAdaptationStateTHR(Handle, state);
+ }
+
+
+ ~CmsContext()
+ {
+ if (OwnedByGC && Handle != IntPtr.Zero )
+ {
+ if (GCOwnedContext.TryRemove(new(Handle, this))) {
+ CmsNative.cmsDeleteContext(Handle);
+ }
+ }
+ }
+ }
+}
diff --git a/LittleCms/CmsDefinition.cs b/LittleCms/CmsDefinition.cs
new file mode 100644
index 0000000..1394831
--- /dev/null
+++ b/LittleCms/CmsDefinition.cs
@@ -0,0 +1,539 @@
+using System;
+
+#pragma warning disable CS0649
+#pragma warning disable CS0169
+
+namespace LittleCms
+{
+ public enum TagSignature
+ {
+ AToB0Tag = 0x41324230, // 'A2B0'
+ AToB1Tag = 0x41324231, // 'A2B1'
+ AToB2Tag = 0x41324232, // 'A2B2'
+ BlueColorantTag = 0x6258595A, // 'bXYZ'
+ BlueMatrixColumnTag = 0x6258595A, // 'bXYZ'
+ BlueTRCTag = 0x62545243, // 'bTRC'
+ BToA0Tag = 0x42324130, // 'B2A0'
+ BToA1Tag = 0x42324131, // 'B2A1'
+ BToA2Tag = 0x42324132, // 'B2A2'
+ CalibrationDateTimeTag = 0x63616C74, // 'calt'
+ CharTargetTag = 0x74617267, // 'targ'
+ ChromaticAdaptationTag = 0x63686164, // 'chad'
+ ChromaticityTag = 0x6368726D, // 'chrm'
+ ColorantOrderTag = 0x636C726F, // 'clro'
+ ColorantTableTag = 0x636C7274, // 'clrt'
+ ColorantTableOutTag = 0x636C6F74, // 'clot'
+ ColorimetricIntentImageStateTag = 0x63696973, // 'ciis'
+ CopyrightTag = 0x63707274, // 'cprt'
+ CrdInfoTag = 0x63726469, // 'crdi'
+ DataTag = 0x64617461, // 'data'
+ DateTimeTag = 0x6474696D, // 'dtim'
+ DeviceMfgDescTag = 0x646D6E64, // 'dmnd'
+ DeviceModelDescTag = 0x646D6464, // 'dmdd'
+ DeviceSettingsTag = 0x64657673, // 'devs'
+ DToB0Tag = 0x44324230, // 'D2B0'
+ DToB1Tag = 0x44324231, // 'D2B1'
+ DToB2Tag = 0x44324232, // 'D2B2'
+ DToB3Tag = 0x44324233, // 'D2B3'
+ BToD0Tag = 0x42324430, // 'B2D0'
+ BToD1Tag = 0x42324431, // 'B2D1'
+ BToD2Tag = 0x42324432, // 'B2D2'
+ BToD3Tag = 0x42324433, // 'B2D3'
+ GamutTag = 0x67616D74, // 'gamt'
+ GrayTRCTag = 0x6b545243, // 'kTRC'
+ GreenColorantTag = 0x6758595A, // 'gXYZ'
+ GreenMatrixColumnTag = 0x6758595A, // 'gXYZ'
+ GreenTRCTag = 0x67545243, // 'gTRC'
+ LuminanceTag = 0x6C756d69, // 'lumi'
+ MeasurementTag = 0x6D656173, // 'meas'
+ MediaBlackPointTag = 0x626B7074, // 'bkpt'
+ MediaWhitePointTag = 0x77747074, // 'wtpt'
+ MHC2 = 0x4D484332, // 'MHC2'
+ NamedColorTag = 0x6E636f6C, // 'ncol' // Deprecated by the ICC
+ NamedColor2Tag = 0x6E636C32, // 'ncl2'
+ OutputResponseTag = 0x72657370, // 'resp'
+ PerceptualRenderingIntentGamutTag = 0x72696730, // 'rig0'
+ Preview0Tag = 0x70726530, // 'pre0'
+ Preview1Tag = 0x70726531, // 'pre1'
+ Preview2Tag = 0x70726532, // 'pre2'
+ ProfileDescriptionTag = 0x64657363, // 'desc'
+ ProfileDescriptionMLTag = 0x6473636d, // 'dscm'
+ ProfileSequenceDescTag = 0x70736571, // 'pseq'
+ ProfileSequenceIdTag = 0x70736964, // 'psid'
+ Ps2CRD0Tag = 0x70736430, // 'psd0'
+ Ps2CRD1Tag = 0x70736431, // 'psd1'
+ Ps2CRD2Tag = 0x70736432, // 'psd2'
+ Ps2CRD3Tag = 0x70736433, // 'psd3'
+ Ps2CSATag = 0x70733273, // 'ps2s'
+ Ps2RenderingIntentTag = 0x70733269, // 'ps2i'
+ RedColorantTag = 0x7258595A, // 'rXYZ'
+ RedMatrixColumnTag = 0x7258595A, // 'rXYZ'
+ RedTRCTag = 0x72545243, // 'rTRC'
+ SaturationRenderingIntentGamutTag = 0x72696732, // 'rig2'
+ ScreeningDescTag = 0x73637264, // 'scrd'
+ ScreeningTag = 0x7363726E, // 'scrn'
+ TechnologyTag = 0x74656368, // 'tech'
+ UcrBgTag = 0x62666420, // 'bfd '
+ ViewingCondDescTag = 0x76756564, // 'vued'
+ ViewingConditionsTag = 0x76696577, // 'view'
+ VcgtTag = 0x76636774, // 'vcgt'
+ MetaTag = 0x6D657461, // 'meta'
+ ArgyllArtsTag = 0x61727473 // 'arts'
+ }
+
+ ///
+ /// ICC Color spaces
+ ///
+ public enum ColorSpaceSignature
+ {
+ XYZ = 0x58595A20, // 'XYZ '
+ Lab = 0x4C616220, // 'Lab '
+ Luv = 0x4C757620, // 'Luv '
+ YCbCr = 0x59436272, // 'YCbr'
+ Yxy = 0x59787920, // 'Yxy '
+ Rgb = 0x52474220, // 'RGB '
+ Gray = 0x47524159, // 'GRAY'
+ Hsv = 0x48535620, // 'HSV '
+ Hls = 0x484C5320, // 'HLS '
+ Cmyk = 0x434D594B, // 'CMYK'
+ Cmy = 0x434D5920, // 'CMY '
+ MCH1 = 0x4D434831, // 'MCH1'
+ MCH2 = 0x4D434832, // 'MCH2'
+ MCH3 = 0x4D434833, // 'MCH3'
+ MCH4 = 0x4D434834, // 'MCH4'
+ MCH5 = 0x4D434835, // 'MCH5'
+ MCH6 = 0x4D434836, // 'MCH6'
+ MCH7 = 0x4D434837, // 'MCH7'
+ MCH8 = 0x4D434838, // 'MCH8'
+ MCH9 = 0x4D434839, // 'MCH9'
+ MCHA = 0x4D434841, // 'MCHA'
+ MCHB = 0x4D434842, // 'MCHB'
+ MCHC = 0x4D434843, // 'MCHC'
+ MCHD = 0x4D434844, // 'MCHD'
+ MCHE = 0x4D434845, // 'MCHE'
+ MCHF = 0x4D434846, // 'MCHF'
+ Named = 0x6e6d636c, // 'nmcl'
+ MultiColor1 = 0x31434C52, // '1CLR'
+ MultiColor2 = 0x32434C52, // '2CLR'
+ MultiColor3 = 0x33434C52, // '3CLR'
+ MultiColor4 = 0x34434C52, // '4CLR'
+ MultiColor5 = 0x35434C52, // '5CLR'
+ MultiColor6 = 0x36434C52, // '6CLR'
+ MultiColor7 = 0x37434C52, // '7CLR'
+ MultiColor8 = 0x38434C52, // '8CLR'
+ MultiColor9 = 0x39434C52, // '9CLR'
+ MultiColor10 = 0x41434C52, // 'ACLR'
+ MultiColor11 = 0x42434C52, // 'BCLR'
+ MultiColor12 = 0x43434C52, // 'CCLR'
+ MultiColor13 = 0x44434C52, // 'DCLR'
+ MultiColor14 = 0x45434C52, // 'ECLR'
+ MultiColor15 = 0x46434C52, // 'FCLR'
+ LuvK = 0x4C75764B // 'LuvK'
+ }
+
+ ///
+ /// ICC Profile Class
+ ///
+ public enum ProfileClassSignature
+ {
+ Input = 0x73636E72, // 'scnr'
+ Display = 0x6D6E7472, // 'mntr'
+ Output = 0x70727472, // 'prtr'
+ Link = 0x6C696E6B, // 'link'
+ Abstract = 0x61627374, // 'abst'
+ ColorSpace = 0x73706163, // 'spac'
+ NamedColor = 0x6e6d636c // 'nmcl'
+
+ }
+ public struct CIEXYZ
+ {
+ public double X;
+ public double Y;
+ public double Z;
+
+ public static CIEXYZ operator +(in CIEXYZ a, in CIEXYZ b)
+ {
+ return new()
+ {
+ X = a.X + b.X,
+ Y = a.Y + b.Y,
+ Z = a.Z + b.Z
+ };
+ }
+
+ public static CIEXYZ operator -(in CIEXYZ a, in CIEXYZ b)
+ {
+ return new()
+ {
+ X = a.X - b.X,
+ Y = a.Y - b.Y,
+ Z = a.Z - b.Z
+ };
+ }
+
+ public static CIEXYZ operator -(in CIEXYZ a)
+ {
+ return new()
+ {
+ X = -a.X,
+ Y = -a.Y,
+ Z = -a.Z
+ };
+ }
+ }
+
+ public struct CIExyY
+ {
+ public double x;
+ public double y;
+ public double Y;
+ }
+ public enum InfoType
+ {
+ Description = 0,
+ Manufacturer = 1,
+ Model = 2,
+ Copyright = 3
+ }
+
+ public enum ProfileUsedDirection
+ {
+ AsInput = 0,
+ AsOutput = 1,
+ AsProof = 2,
+ }
+
+ public struct CIEXYZTRIPLE
+ {
+ public CIEXYZ Red;
+ public CIEXYZ Green;
+ public CIEXYZ Blue;
+ }
+
+ public struct CIExyYTRIPLE
+ {
+ public CIExyY Red;
+ public CIExyY Green;
+ public CIExyY Blue;
+ }
+
+ public struct ICCData
+ {
+ public uint Flag;
+ public Memory Data;
+ }
+
+ internal struct _ICCDataHeader
+ {
+ public uint len;
+ public uint flag;
+ }
+
+ internal unsafe struct _ICCData
+ {
+ public uint len;
+ public uint flag;
+ public fixed byte data[1];
+
+ internal static unsafe ICCData ToManaged(_ICCData* ptr)
+ {
+ var result = new ICCData();
+ result.Flag = ptr->flag;
+ result.Data = new ReadOnlySpan(ptr->data, (int)ptr->len).ToArray();
+ return result;
+ }
+ }
+
+ public struct CIELab
+ {
+ public double L;
+ public double a;
+ public double b;
+ }
+
+ public struct CIELCh
+ {
+ public double L;
+ public double C;
+ public double h;
+ }
+
+ public struct JCh
+ {
+ public double J;
+ public double C;
+ public double h;
+ }
+
+ public enum IlluminantType
+ {
+ UNKNOWN = 0x0000000,
+ D50 = 0x0000001,
+ D65 = 0x0000002,
+ D93 = 0x0000003,
+ F2 = 0x0000004,
+ D55 = 0x0000005,
+ A = 0x0000006,
+ E = 0x0000007,
+ F8 = 0x0000008,
+ }
+
+ public struct ICCMeasurementConditions
+ {
+ public uint Observer; // 0 = unknown, 1=CIE 1931, 2=CIE 1964
+ public CIEXYZ Backing; // Value of backing
+ public uint Geometry; // 0=unknown, 1=45/0, 0/45 2=0d, d/0
+ public double Flare; // 0..1.0
+ public IlluminantType IlluminantType;
+
+ }
+
+ public struct ICCViewingConditions
+ {
+ public CIEXYZ IlluminantXYZ; // Not the same struct as CAM02,
+ public CIEXYZ SurroundXYZ; // This is for storing the tag
+ public IlluminantType IlluminantType; // viewing condition
+ }
+
+ public enum HeaderFlags
+ {
+ EmbeddedProfile = 1,
+ UseWithEmbeddedDataOnly = 2
+ }
+
+ internal struct CmsStructTm
+ {
+ public int tm_sec; // seconds after the minute - [0, 60] including leap second
+ public int tm_min; // minutes after the hour - [0, 59]
+ public int tm_hour; // hours since midnight - [0, 23]
+ public int tm_mday; // day of the month - [1, 31]
+ public int tm_mon; // months since January - [0, 11]
+ public int tm_year; // years since 1900
+ public int tm_wday; // days since Sunday - [0, 6]
+ public int tm_yday; // days since January 1 - [0, 365]
+ public int tm_isdst; // daylight savings time flag
+
+ public DateTime ToDateTime() => new DateTime(tm_year, tm_mon + 1, tm_mday, tm_hour, tm_min, tm_sec);
+
+ public static CmsStructTm FromDateTime(DateTime d)
+ {
+ return new CmsStructTm
+ {
+ tm_year = d.Year,
+ tm_mon = d.Month - 1,
+ tm_mday = d.Day,
+ tm_hour = d.Hour,
+ tm_min = d.Minute,
+ tm_sec = d.Second,
+ tm_wday = (int)d.DayOfWeek,
+ tm_yday = d.DayOfYear,
+ tm_isdst = d.IsDaylightSavingTime() ? 1 : 0
+ };
+ }
+ }
+
+ public enum SpotShape
+ {
+ Unknown = 0,
+ PrinterDefault = 1,
+ Round = 2,
+ Diamond = 3,
+ Ellipse = 4,
+ Line = 5,
+ Square = 6,
+ Cross = 7,
+ }
+
+ public struct cmsScreeningChannel
+ {
+ public double Frequency;
+ public double ScreenAngle;
+ public SpotShape SpotShape;
+ }
+
+
+ ///
+ /// Where to place/locate the stages in the pipeline chain
+ ///
+ public enum StageLoc
+ {
+ AtBegin,
+ AtEnd,
+ }
+
+ ///
+ /// Multi process elements types
+ ///
+ public enum StageSignature
+ {
+ CurveSetElemType = 0x63767374, //'cvst'
+ MatrixElemType = 0x6D617466, //'matf'
+ CLutElemType = 0x636C7574, //'clut'
+
+ BAcsElemType = 0x62414353, // 'bACS'
+ EAcsElemType = 0x65414353, // 'eACS'
+
+ // Custom from here, not in the ICC Spec
+ XYZ2LabElemType = 0x6C327820, // 'l2x '
+ Lab2XYZElemType = 0x78326C20, // 'x2l '
+ NamedColorElemType = 0x6E636C20, // 'ncl '
+ LabV2toV4 = 0x32203420, // '2 4 '
+ LabV4toV2 = 0x34203220, // '4 2 '
+
+ // Identities
+ IdentityElemType = 0x69646E20, // 'idn '
+
+ // Float to floatPCS
+ Lab2FloatPCS = 0x64326C20, // 'd2l '
+ FloatPCS2Lab = 0x6C326420, // 'l2d '
+ XYZ2FloatPCS = 0x64327820, // 'd2x '
+ FloatPCS2XYZ = 0x78326420, // 'x2d '
+ ClipNegativesElemType = 0x636c7020 // 'clp '
+ }
+
+ public struct cmsViewingConditions
+ {
+ public const int AVG_SURROUND = 1;
+ public const int DIM_SURROUND = 2;
+ public const int DARK_SURROUND = 3;
+ public const int CUTSHEET_SURROUND = 4;
+
+ public const double D_CALCULATE = (-1);
+
+ public CIEXYZ whitePoint;
+ public double Yb;
+ public double La;
+ public uint surround;
+ public double D_value;
+ }
+
+ public enum CmsError
+ {
+ UNDEFINED = 0,
+ FILE = 1,
+ RANGE = 2,
+ INTERNAL = 3,
+ NULL = 4,
+ READ = 5,
+ SEEK = 6,
+ WRITE = 7,
+ UNKNOWN_EXTENSION = 8,
+ COLORSPACE_CHECK = 9,
+ ALREADY_DEFINED = 10,
+ BAD_SIGNATURE = 11,
+ CORRUPTION_DETECTED = 12,
+ NOT_SUITABLE = 13,
+ }
+
+ public enum RenderingIntent
+ {
+ // ICC Intents
+ PERCEPTUAL = 0,
+ RELATIVE_COLORIMETRIC = 1,
+ SATURATION = 2,
+ ABSOLUTE_COLORIMETRIC = 3,
+ // Non-ICC intents
+ PRESERVE_K_ONLY_PERCEPTUAL = 10,
+ PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC = 11,
+ PRESERVE_K_ONLY_SATURATION = 12,
+ PRESERVE_K_PLANE_PERCEPTUAL = 13,
+ PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC = 14,
+ PRESERVE_K_PLANE_SATURATION = 15,
+ }
+
+ public enum TransformFlags
+ {
+ NOCACHE = 0x0040, // Inhibit 1-pixel cache
+ NOOPTIMIZE = 0x0100, // Inhibit optimizations
+ NULLTRANSFORM = 0x0200, // Don't transform anyway
+
+ // Proofing flags
+ GAMUTCHECK = 0x1000, // Out of Gamut alarm
+ SOFTPROOFING = 0x4000, // Do softproofing
+
+ // Misc
+ BLACKPOINTCOMPENSATION = 0x2000,
+ NOWHITEONWHITEFIXUP = 0x0004, // Don't fix scum dot
+ HIGHRESPRECALC = 0x0400, // Use more memory to give better accuracy
+ LOWRESPRECALC = 0x0800, // Use less memory to minimize resources
+
+ // For devicelink creation
+ DEVICELINK_8BITS = 0x0008, // Create 8 bits devicelinks
+ GUESSDEVICECLASS = 0x0020, // Guess device class (for transform2devicelink)
+ KEEP_SEQUENCE = 0x0080, // Keep profile sequence for devicelink creation
+
+ // Specific to a particular optimizations
+ FORCE_CLUT = 0x0002, // Force CLUT optimization
+ CLUT_POST_LINEARIZATION = 0x0001, // create postlinearization tables if possible
+ CLUT_PRE_LINEARIZATION = 0x0010, // create prelinearization tables if possible
+
+ // Specific to unbounded mode
+ NONEGATIVES = 0x8000, // Prevent negative numbers in floating point transforms
+
+ // Copy alpha channels when transforming
+ COPY_ALPHA = 0x04000000,
+
+ // Fine-tune control over number of gridpoints
+ // #define cmsFLAGS_GRIDPOINTS(n) (((n) & 0xFF) << 16)
+
+ // CRD special
+ NODEFAULTRESOURCEDEF = 0x01000000
+ }
+
+ public enum PSResourceType
+ {
+ CSA,
+ CRD
+ }
+
+ public enum PixelType
+ {
+ ///
+ /// Don't check colorspace
+ ///
+ ANY = 0,
+
+ // 1 & 2 are reserved
+
+ GRAY = 3,
+ RGB = 4,
+ CMY = 5,
+ CMYK = 6,
+ YCbCr = 7,
+
+ ///
+ /// Lu'v'
+ ///
+ YUV = 8,
+
+ XYZ = 9,
+ Lab = 10,
+
+ ///
+ /// Lu'v'K
+ ///
+ YUVK = 11,
+
+ HSV = 12,
+ HLS = 13,
+ Yxy = 14,
+ MCH1 = 15,
+ MCH2 = 16,
+ MCH3 = 17,
+ MCH4 = 18,
+ MCH5 = 19,
+ MCH6 = 20,
+ MCH7 = 21,
+ MCH8 = 22,
+ MCH9 = 23,
+ MCH10 = 24,
+ MCH11 = 25,
+ MCH12 = 26,
+ MCH13 = 27,
+ MCH14 = 28,
+ MCH15 = 29,
+ LabV2 = 30,
+ }
+}
+
+
diff --git a/LittleCms/CmsError.cs b/LittleCms/CmsError.cs
new file mode 100644
index 0000000..454d640
--- /dev/null
+++ b/LittleCms/CmsError.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+
+namespace LittleCms
+{
+ public class CmsException : Exception
+ {
+ public CmsError CmsError { get; }
+ public CmsException() : base("Error occurred in lcms2") { CmsError = CmsError.UNDEFINED; }
+ public CmsException(CmsError code, string message) : base(message) { CmsError = code; }
+ }
+
+ partial class CmsNative
+ {
+ private static readonly ThreadLocal LastError;
+ static CmsNative()
+ {
+ var libver = cmsGetEncodedCMMversion();
+ if (libver != LCMS_VERSION)
+ {
+ throw new TypeLoadException("lcms2 version mismatch");
+ }
+ LastError = new();
+ cmsSetLogErrorHandler(ErrorCallback);
+ }
+
+ private static void ErrorCallback(IntPtr ContextID, CmsError ErrorCode, string Text)
+ {
+ LastError.Value = new(ErrorCode, Text);
+ }
+
+ private static void ThrowLastTlsError()
+ {
+ if (LastError.IsValueCreated && LastError.Value != null)
+ {
+ var ex = LastError.Value;
+ LastError.Value = null;
+ throw ex;
+ }
+ else
+ {
+ throw new CmsException();
+ }
+ }
+
+ public static uint CheckError(uint value)
+ {
+ if (value == 0)
+ {
+ ThrowLastTlsError();
+ }
+ return value;
+ }
+
+ public static void CheckError(bool value)
+ {
+ if (!value)
+ {
+ ThrowLastTlsError();
+ }
+ }
+
+ public static IntPtr CheckError(IntPtr value)
+ {
+ if (value == IntPtr.Zero)
+ {
+ ThrowLastTlsError();
+ }
+ return value;
+ }
+
+ public unsafe static void* CheckError(void* value)
+ {
+ if (value == null)
+ {
+ ThrowLastTlsError();
+ }
+ return value;
+ }
+ }
+}
diff --git a/LittleCms/CmsExtension.cs b/LittleCms/CmsExtension.cs
new file mode 100644
index 0000000..3db903e
--- /dev/null
+++ b/LittleCms/CmsExtension.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms
+{
+ public static class CmsExtension
+ {
+
+ public static CIExyY ToCIExyY(this CIEXYZ xyz)
+ {
+ return new CIExyY { x = xyz.X / (xyz.X + xyz.Y + xyz.Z), y = xyz.Y / (xyz.X + xyz.Y + xyz.Z), Y = xyz.Y };
+ }
+ public static CIExyY ToXY(this CIEXYZ xyz)
+ {
+ var sum = xyz.X + xyz.Y + xyz.Z;
+ return new() { x = xyz.X / sum, y = xyz.Y / sum, Y = 1 };
+ }
+
+ public static CIEXYZ ToXYZ(this CIExyY xyY)
+ {
+ return new() { X = xyY.x * xyY.Y / xyY.y, Y = xyY.Y, Z = (1 - xyY.x - xyY.y) * xyY.Y / xyY.y };
+ }
+
+ public static int GetChannelCount(this ColorSpaceSignature sig)
+ {
+ return (int)CmsNative.cmsChannelsOf(sig);
+ }
+
+
+ }
+}
diff --git a/LittleCms/CmsGlobal.cs b/LittleCms/CmsGlobal.cs
new file mode 100644
index 0000000..5fb7254
--- /dev/null
+++ b/LittleCms/CmsGlobal.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace LittleCms
+{
+ public class CmsGlobal
+ {
+
+ public static CIEXYZ D50XYZ { get; } = CmsNative.cmsD50_XYZ();
+ public static CIExyY D50xyY { get; } = CmsNative.cmsD50_xyY();
+
+
+ public unsafe static CIEXYZ AdaptToIlluminant(in CIEXYZ SourceWhitePt, in CIEXYZ Illuminant, in CIEXYZ Value)
+ {
+ CmsNative.CheckError(CmsNative.cmsAdaptToIlluminant(out var result, SourceWhitePt, Illuminant, Value));
+ return result;
+ }
+ }
+}
diff --git a/LittleCms/CmsNative.cs b/LittleCms/CmsNative.cs
new file mode 100644
index 0000000..be981e6
--- /dev/null
+++ b/LittleCms/CmsNative.cs
@@ -0,0 +1,958 @@
+using LittleCms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+#pragma warning disable CS0649
+#pragma warning disable CS0169
+
+namespace LittleCms
+{
+ internal unsafe partial class CmsNative
+ {
+ private const int LCMS_VERSION = 2131;
+ private const string LIB = "lcms2";
+
+ public static readonly Encoding WcharEncoding = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Encoding.Unicode : Encoding.UTF32;
+
+ // Get LittleCMS version (for shared objects) -----------------------------------------------------------------------------
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsGetEncodedCMMversion();
+
+
+ // Context handling --------------------------------------------------------------------------------------------------------
+
+ // Each context holds its owns globals and its own plug-ins. There is a global context with the id = 0 for lecacy compatibility
+ // though using the global context is not recommended. Proper context handling makes lcms more thread-safe.
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateContext(IntPtr Plugin, IntPtr UserData);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsDeleteContext(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsDupContext(IntPtr ContextID, IntPtr NewUserData);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsGetContextUserData(IntPtr ContextID);
+
+ // Plug-In registering --------------------------------------------------------------------------------------------------
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsPlugin(void* Plugin);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsPluginTHR(IntPtr ContextID, void* Plugin);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsUnregisterPlugins();
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsUnregisterPluginsTHR(IntPtr ContextID);
+
+ // Error logging ----------------------------------------------------------------------------------------------------------
+
+ // There is no error handling at all. When a function fails, it returns proper value.
+ // For example, all create functions does return NULL on failure. Other may return FALSE.
+ // It may be interesting, for the developer, to know why the function is failing.
+ // for that reason, lcms2 does offer a logging function. This function will get
+ // an ENGLISH string with some clues on what is going wrong. You can show this
+ // info to the end user if you wish, or just create some sort of log on disk.
+ // The logging function should NOT terminate the program, as this obviously can leave
+ // unfreed resources. It is the programmer's responsibility to check each function
+ // return code to make sure it didn't fail.
+
+
+ // Error logger is called with the ContextID when a message is raised. This gives the
+ // chance to know which thread is responsible of the warning and any environment associated
+ // with it. Non-multithreading applications may safely ignore this parameter.
+ // Note that under certain special circumstances, ContextID may be NULL.
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void cmsLogErrorHandlerFunction(IntPtr ContextID, CmsError ErrorCode, [In, MarshalAs(UnmanagedType.LPStr)] string Text);
+
+ // Allows user to set any specific logger
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetLogErrorHandler(cmsLogErrorHandlerFunction Fn);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetLogErrorHandlerTHR(IntPtr ContextID, cmsLogErrorHandlerFunction Fn);
+
+ // Returns pointers to constant structs
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ref readonly CIEXYZ cmsD50_XYZ();
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ref readonly CIExyY cmsD50_xyY();
+
+ // Colorimetric space conversions
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsXYZ2xyY(out CIExyY Dest, in CIEXYZ Source);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsxyY2XYZ(CIEXYZ* Dest, in CIExyY Source);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsXYZ2Lab(in CIEXYZ WhitePoint, out CIELab Lab, in CIEXYZ xyz);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsLab2XYZ(in CIEXYZ WhitePoint, out CIEXYZ xyz, in CIELab Lab);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsLab2LCh(out CIELCh LCh, in CIELab Lab);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsLCh2Lab(out CIELab Lab, in CIELCh LCh);
+
+ // Encoding /Decoding on PCS
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsLabEncoded2Float(out CIELab Lab, in ushort wLab_3);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsLabEncoded2FloatV2(out CIELab Lab, in ushort wLab_3);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsFloat2LabEncoded(out ushort wLab_3, in CIELab Lab);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsFloat2LabEncodedV2(out ushort wLab_3, in CIELab Lab);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsXYZEncoded2Float(out CIEXYZ fxyz, in ushort XYZ_3);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsFloat2XYZEncoded(out ushort XYZ_3, in CIEXYZ fXYZ);
+
+
+ // DeltaE metrics
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsDeltaE(in CIELab Lab1, in CIELab Lab2);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsCIE94DeltaE(in CIELab Lab1, in CIELab Lab2);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsBFDdeltaE(in CIELab Lab1, in CIELab Lab2);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsCMCdeltaE(in CIELab Lab1, in CIELab Lab2, double l, double c);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsCIE2000DeltaE(in CIELab Lab1, in CIELab Lab2, double Kl, double Kc, double Kh);
+
+ // Temperature <-> Chromaticity (Black body)
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsWhitePointFromTemp(out CIExyY WhitePoint, double TempK);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsTempFromWhitePoint(double* TempK, in CIExyY WhitePoint);
+
+ // Chromatic adaptation
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsAdaptToIlluminant(out CIEXYZ Result, in CIEXYZ SourceWhitePt,
+ in CIEXYZ Illuminant,
+ in CIEXYZ Value);
+
+ // CIECAM02 ---------------------------------------------------------------------------------------------------
+
+ // Viewing conditions. Please note those are CAM model viewing conditions, and not the ICC tag viewing
+ // conditions, which I'm naming cmsICCViewingConditions to make differences evident. Unfortunately, the tag
+ // cannot deal with surround La, Yb and D value so is basically useless to store CAM02 viewing conditions.
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCIECAM02Init(IntPtr ContextID, in cmsViewingConditions pVC);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsCIECAM02Done(IntPtr hModel);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsCIECAM02Forward(IntPtr hModel, in CIEXYZ pIn, out JCh pOut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsCIECAM02Reverse(IntPtr hModel, in JCh pIn, out CIEXYZ pOut);
+
+
+ // Tone curves -----------------------------------------------------------------------------------------
+
+ // This describes a curve segment. For a table of supported types, see the manual. User can increase the number of
+ // available types by using a proper plug-in. Parametric segments allow 10 parameters at most
+ public struct cmsCurveSegment
+ {
+ public float x0, x1; // Domain; for x0 < x <= x1
+ public int Type; // Parametric type, Type == 0 means sampled segment. Negative values are reserved
+ public fixed double Params[10]; // Parameters if Type != 0
+ public uint nGridPoints; // Number of grid points if Type == 0
+ public float* SampledPoints; // Points to an array of floats if Type == 0
+ }
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsBuildSegmentedToneCurve(IntPtr ContextID, uint nSegments, in cmsCurveSegment Segments);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsBuildParametricToneCurve(IntPtr ContextID, int Type, in double Params);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsBuildGamma(IntPtr ContextID, double Gamma);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsBuildTabulatedToneCurve16(IntPtr ContextID, uint nEntries, in ushort values);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsBuildTabulatedToneCurveFloat(IntPtr ContextID, uint nEntries, in float values);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsFreeToneCurve(IntPtr Curve);
+ //[DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ //public static extern void cmsFreeToneCurveTriple(IntPtr Curve[3]);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsDupToneCurve(IntPtr Src);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsReverseToneCurve(IntPtr InGamma);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsReverseToneCurveEx(uint nResultSamples, IntPtr InGamma);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsJoinToneCurve(IntPtr ContextID, IntPtr X, IntPtr Y, uint nPoints);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsSmoothToneCurve(IntPtr Tab, double lambda);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern float cmsEvalToneCurveFloat(IntPtr Curve, float v);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ushort cmsEvalToneCurve16(IntPtr Curve, ushort v);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsToneCurveMultisegment(IntPtr InGamma);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsToneCurveLinear(IntPtr Curve);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsToneCurveMonotonic(IntPtr t);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsToneCurveDescending(IntPtr t);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsGetToneCurveParametricType(IntPtr t);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsEstimateGamma(IntPtr t, double Precision);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ref readonly double cmsGetToneCurveParams(IntPtr t);
+
+ // Tone curve tabular estimation
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetToneCurveEstimatedTableEntries(IntPtr t);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ref readonly ushort cmsGetToneCurveEstimatedTable(IntPtr t);
+
+ // Implements pipelines of multi-processing elements -------------------------------------------------------------
+
+ // Those are hi-level pipelines
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsPipelineAlloc(IntPtr ContextID, uint InputChannels, uint OutputChannels);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsPipelineFree(IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsPipelineDup(IntPtr Orig);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsGetPipelineContextID(IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsPipelineInputChannels(IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsPipelineOutputChannels(IntPtr lut);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsPipelineStageCount(IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsPipelineGetPtrToFirstStage(IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsPipelineGetPtrToLastStage(IntPtr lut);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsPipelineEval16(in ushort In, out ushort Out, IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsPipelineEvalFloat(in float In, out float Out, IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsPipelineEvalReverseFloat(ref float Target, ref float Result, ref float Hint, IntPtr lut);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsPipelineCat(IntPtr l1, IntPtr l2);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsPipelineSetSaveAs8bitsFlag(IntPtr lut, bool On);
+ }
+
+
+
+ internal unsafe partial class CmsNative
+ {
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsPipelineInsertStage(IntPtr lut, StageLoc loc, IntPtr mpe);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsPipelineUnlinkStage(IntPtr lut, StageLoc loc, out IntPtr mpe);
+
+ // This function is quite useful to analyze the structure of a Pipeline and retrieve the Stage elements
+ // that conform the Pipeline. It should be called with the Pipeline, the number of expected elements and
+ // then a list of expected types followed with a list of double pointers to Stage elements. If
+ // the function founds a match with current pipeline, it fills the pointers and returns TRUE
+ // if not, returns FALSE without touching anything.
+ [DllImport(LIB, CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ public static extern bool cmsPipelineCheckAndRetreiveStages(IntPtr Lut, uint n, __arglist);
+
+ // Matrix has double precision and CLUT has only float precision. That is because an ICC profile can encode
+ // matrices with far more precision that CLUTS
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageAllocIdentity(IntPtr ContextID, uint nChannels);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageAllocToneCurves(IntPtr ContextID, uint nChannels, in IntPtr Curves);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageAllocMatrix(IntPtr ContextID, uint Rows, uint Cols, in double Matrix, in double Offset);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageAllocCLut16bit(IntPtr ContextID, uint nGridPoints, uint inputChan, uint outputChan, in ushort Table);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageAllocCLutFloat(IntPtr ContextID, uint nGridPoints, uint inputChan, uint outputChan, in float Table);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageAllocCLut16bitGranular(IntPtr ContextID, in uint clutPoints, uint inputChan, uint outputChan, in ushort Table);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageAllocCLutFloatGranular(IntPtr ContextID, in uint clutPoints, uint inputChan, uint outputChan, in float Table);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageDup(IntPtr mpe);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsStageFree(IntPtr mpe);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageNext(IntPtr mpe);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsStageInputChannels(IntPtr mpe);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsStageOutputChannels(IntPtr mpe);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern StageSignature cmsStageType(IntPtr mpe);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsStageData(IntPtr mpe);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsGetStageContextID(IntPtr mpe);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate int cmsSAMPLER16(ushort* In, ushort* Out, IntPtr Cargo);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate int cmsSAMPLERFLOAT(float* In, float* Out, IntPtr Cargo);
+
+ const uint SAMPLER_INSPECT = 0x01000000;
+
+ // For CLUT only
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsStageSampleCLut16bit(IntPtr mpe, cmsSAMPLER16 Sampler, void* Cargo, uint dwFlags);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsStageSampleCLutFloat(IntPtr mpe, cmsSAMPLERFLOAT Sampler, void* Cargo, uint dwFlags);
+
+ // Slicers
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsSliceSpace16(uint nInputs, in uint clutPoints,
+ cmsSAMPLER16 Sampler, void* Cargo);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsSliceSpaceFloat(uint nInputs, in uint clutPoints,
+ cmsSAMPLERFLOAT Sampler, void* Cargo);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsMLUalloc(IntPtr ContextID, uint nItems);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsMLUfree(IntPtr mlu);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsMLUdup(IntPtr mlu);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, CharSet = CharSet.Ansi)]
+ public static extern bool cmsMLUsetASCII(IntPtr mlu,
+ in uint LanguageCode, in uint CountryCode,
+ string ASCIIString);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsMLUsetWide(IntPtr mlu,
+ in uint LanguageCode, in uint CountryCode,
+ in byte WideString);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsMLUgetASCII(IntPtr mlu,
+ in uint LanguageCode, in uint CountryCode,
+ byte* Buffer, uint BufferSize);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsMLUgetWide(IntPtr mlu,
+ in uint LanguageCode, in uint CountryCode,
+ byte* Buffer, uint BufferSize);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsMLUgetTranslation(IntPtr mlu,
+ in uint LanguageCode, in uint CountryCode,
+ ref byte ObtainedLanguage_3, ref byte ObtainedCountry_3);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsMLUtranslationsCount(IntPtr mlu);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsMLUtranslationsCodes(IntPtr mlu,
+ uint idx,
+ ref byte LanguageCode_3,
+ ref byte CountryCode_3);
+
+
+ struct cmsUcrBg
+ {
+ IntPtr UcrToneCurve;
+ IntPtr BgToneCurve;
+ IntPtr DescMLU;
+ }
+
+ public const int cmsPRINTER_DEFAULT_SCREENS = 0x0001;
+ public const int cmsFREQUENCE_UNITS_LINES_CM = 0x0000;
+ public const int cmsFREQUENCE_UNITS_LINES_INCH = 0x0002;
+
+
+ public struct cmsDICTentry
+ {
+ public cmsDICTentry* Next;
+ IntPtr DisplayNameMLU;
+ IntPtr DisplayValueMLU;
+ byte* NameWcharT;
+ byte* ValueWcharT;
+ }
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsDictAlloc(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsDictFree(IntPtr hDict);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsDictDup(IntPtr hDict);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsDictAddEntry(IntPtr hDict, void* Name, void* Value, IntPtr DisplayNameMLU, IntPtr DisplayValueMLU);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern cmsDICTentry* cmsDictGetEntryList(IntPtr hDict);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern cmsDICTentry* cmsDictNextEntry(cmsDICTentry* e);
+
+ // Access to Profile data ----------------------------------------------------------------------------------------------
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateProfilePlaceholder(IntPtr ContextID);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsGetProfileContextID(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsGetTagCount(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern TagSignature cmsGetTagSignature(IntPtr hProfile, uint n);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsTag(IntPtr hProfile, TagSignature sig);
+
+ // Read and write pre-formatted data
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void* cmsReadTag(IntPtr hProfile, TagSignature sig);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsWriteTag(IntPtr hProfile, TagSignature sig, void* data);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsLinkTag(IntPtr hProfile, TagSignature sig, TagSignature dest);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern TagSignature cmsTagLinkedTo(IntPtr hProfile, TagSignature sig);
+
+ // Read and write raw data
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsReadRawTag(IntPtr hProfile, TagSignature sig, void* Buffer, uint BufferSize);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsWriteRawTag(IntPtr hProfile, TagSignature sig, in byte data, uint Size);
+
+
+ // Access header data
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern HeaderFlags cmsGetHeaderFlags(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsGetHeaderAttributes(IntPtr hProfile, out ulong Flags);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsGetHeaderProfileID(IntPtr hProfile, out Guid ProfileID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsGetHeaderCreationDateTime(IntPtr hProfile, out CmsStructTm Dest);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetHeaderRenderingIntent(IntPtr hProfile);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetHeaderFlags(IntPtr hProfile, HeaderFlags Flags);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetHeaderManufacturer(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetHeaderManufacturer(IntPtr hProfile, uint manufacturer);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetHeaderCreator(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetHeaderModel(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetHeaderModel(IntPtr hProfile, uint model);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetHeaderAttributes(IntPtr hProfile, ulong Flags);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetHeaderProfileID(IntPtr hProfile, in Guid ProfileID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetHeaderRenderingIntent(IntPtr hProfile, uint RenderingIntent);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ColorSpaceSignature
+ cmsGetPCS(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetPCS(IntPtr hProfile, ColorSpaceSignature pcs);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ColorSpaceSignature
+ cmsGetColorSpace(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetColorSpace(IntPtr hProfile, ColorSpaceSignature sig);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ProfileClassSignature
+ cmsGetDeviceClass(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetDeviceClass(IntPtr hProfile, ProfileClassSignature sig);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetProfileVersion(IntPtr hProfile, double Version);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsGetProfileVersion(IntPtr hProfile);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetEncodedICCversion(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetEncodedICCversion(IntPtr hProfile, uint Version);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsIntentSupported(IntPtr hProfile, uint Intent, ProfileUsedDirection UsedDirection);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsMatrixShaper(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIsCLUT(IntPtr hProfile, uint Intent, ProfileUsedDirection UsedDirection);
+
+ // Translate form/to our notation to ICC
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern ColorSpaceSignature _cmsICCcolorSpace(int OurNotation);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int _cmsLCMScolorSpace(ColorSpaceSignature ProfileSpace);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsChannelsOf(ColorSpaceSignature ColorSpace);
+
+ // Build a suitable formatter for the colorspace of this profile. nBytes=1 means 8 bits, nBytes=2 means 16 bits.
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsFormatterForColorspaceOfProfile(IntPtr hProfile, uint nBytes, bool lIsFloat);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsFormatterForPCSOfProfile(IntPtr hProfile, uint nBytes, bool lIsFloat);
+
+ // Localized info
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetProfileInfo(IntPtr hProfile, InfoType Info,
+ in uint LanguageCode, in uint CountryCode,
+ void* Buffer, uint BufferSize);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetProfileInfoASCII(IntPtr hProfile, InfoType Info,
+ in uint LanguageCode, in uint CountryCode,
+ byte* Buffer, uint BufferSize);
+
+ // MD5 message digest --------------------------------------------------------------------------------------------------
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsMD5computeID(IntPtr hProfile);
+
+ // IO handlers ----------------------------------------------------------------------------------------------------------
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true, CharSet = CharSet.Ansi)]
+ public static extern IntPtr cmsOpenIOhandlerFromMem(IntPtr ContextID, void* Buffer, uint size, string AccessMode);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsOpenIOhandlerFromNULL(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsGetProfileIOhandler(IntPtr hProfile);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsCloseIOhandler(IntPtr io);
+
+
+ // Profile high level functions ------------------------------------------------------------------------------------------
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsOpenProfileFromMem(void* MemPtr, uint dwSize);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsOpenProfileFromMemTHR(IntPtr ContextID, void* MemPtr, uint dwSize);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsOpenProfileFromIOhandlerTHR(IntPtr ContextID, IntPtr io);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsOpenProfileFromIOhandler2THR(IntPtr ContextID, IntPtr io, bool write);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsCloseProfile(IntPtr hProfile);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsSaveProfileToMem(IntPtr hProfile, void* MemPtr, ref uint BytesNeeded);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsSaveProfileToIOhandler(IntPtr hProfile, IntPtr io);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateRGBProfileTHR(IntPtr ContextID,
+ in CIExyY WhitePoint,
+ in CIExyYTRIPLE Primaries,
+ ref IntPtr TransferFunction3);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateRGBProfile(in CIExyY WhitePoint,
+ in CIExyYTRIPLE Primaries,
+ ref IntPtr TransferFunction);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateGrayProfileTHR(IntPtr ContextID,
+ in CIExyY WhitePoint,
+ IntPtr TransferFunction);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateGrayProfile(in CIExyY WhitePoint,
+ IntPtr TransferFunction);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateLinearizationDeviceLinkTHR(IntPtr ContextID,
+ ColorSpaceSignature ColorSpace,
+ ref IntPtr TransferFunctions);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateLinearizationDeviceLink(ColorSpaceSignature ColorSpace,
+ ref IntPtr TransferFunctions);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateInkLimitingDeviceLinkTHR(IntPtr ContextID,
+ ColorSpaceSignature ColorSpace, double Limit);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateInkLimitingDeviceLink(ColorSpaceSignature ColorSpace, double Limit);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateLab2ProfileTHR(IntPtr ContextID, in CIExyY WhitePoint);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateLab2Profile(in CIExyY WhitePoint);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateLab4ProfileTHR(IntPtr ContextID, in CIExyY WhitePoint);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateLab4Profile(in CIExyY WhitePoint);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateXYZProfileTHR(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateXYZProfile();
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreate_sRGBProfileTHR(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreate_sRGBProfile();
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateBCHSWabstractProfileTHR(IntPtr ContextID,
+ uint nLUTPoints,
+ double Bright,
+ double Contrast,
+ double Hue,
+ double Saturation,
+ uint TempSrc,
+ uint TempDest);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateBCHSWabstractProfile(uint nLUTPoints,
+ double Bright,
+ double Contrast,
+ double Hue,
+ double Saturation,
+ uint TempSrc,
+ uint TempDest);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateNULLProfileTHR(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateNULLProfile();
+
+ // Converts a transform to a devicelink profile
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsTransform2DeviceLink(IntPtr hTransform, double Version, uint dwFlags);
+
+ // Call with NULL as parameters to get the intent count
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetSupportedIntents(uint nMax, uint* Codes, byte** Descriptions);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetSupportedIntentsTHR(IntPtr ContextID, uint nMax, uint* Codes, byte** Descriptions);
+
+
+ // Transforms ---------------------------------------------------------------------------------------------------
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateTransformTHR(IntPtr ContextID,
+ IntPtr Input,
+ uint InputFormat,
+ IntPtr Output,
+ uint OutputFormat,
+ uint Intent,
+ uint dwFlags);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateTransform(IntPtr Input,
+ uint InputFormat,
+ IntPtr Output,
+ uint OutputFormat,
+ uint Intent,
+ uint dwFlags);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateProofingTransformTHR(IntPtr ContextID,
+ IntPtr Input,
+ uint InputFormat,
+ IntPtr Output,
+ uint OutputFormat,
+ IntPtr Proofing,
+ uint Intent,
+ uint ProofingIntent,
+ uint dwFlags);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateProofingTransform(IntPtr Input,
+ uint InputFormat,
+ IntPtr Output,
+ uint OutputFormat,
+ IntPtr Proofing,
+ uint Intent,
+ uint ProofingIntent,
+ uint dwFlags);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateMultiprofileTransformTHR(IntPtr ContextID,
+ ref IntPtr hProfiles,
+ uint nProfiles,
+ uint InputFormat,
+ uint OutputFormat,
+ uint Intent,
+ uint dwFlags);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateMultiprofileTransform(ref IntPtr hProfiles,
+ uint nProfiles,
+ uint InputFormat,
+ uint OutputFormat,
+ uint Intent,
+ uint dwFlags);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsCreateExtendedTransform(IntPtr ContextID,
+ uint nProfiles, ref IntPtr hProfiles,
+ ref bool BPC,
+ ref uint Intents,
+ ref double AdaptationStates,
+ IntPtr hGamutProfile,
+ uint nGamutPCSposition,
+ uint InputFormat,
+ uint OutputFormat,
+ uint dwFlags);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsDeleteTransform(IntPtr hTransform);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsDoTransform(IntPtr Transform,
+ void* InputBuffer,
+ void* OutputBuffer,
+ uint Size);
+
+ [Obsolete]
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsDoTransformStride(IntPtr Transform, // Deprecated
+ void* InputBuffer,
+ void* OutputBuffer,
+ uint Size,
+ uint Stride);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsDoTransformLineStride(IntPtr Transform,
+ void* InputBuffer,
+ void* OutputBuffer,
+ uint PixelsPerLine,
+ uint LineCount,
+ uint BytesPerLineIn,
+ uint BytesPerLineOut,
+ uint BytesPerPlaneIn,
+ uint BytesPerPlaneOut);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetAlarmCodes(ref ushort NewAlarm_cmsMAXCHANNELS);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsGetAlarmCodes(ref ushort NewAlarm_cmsMAXCHANNELS);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsSetAlarmCodesTHR(IntPtr ContextID,
+ ref ushort NewAlarm_cmsMAXCHANNELS);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsGetAlarmCodesTHR(IntPtr ContextID,
+ ref ushort NewAlarm_cmsMAXCHANNELS);
+
+
+
+ // Adaptation state for absolute colorimetric intent
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsSetAdaptationState(double d);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsSetAdaptationStateTHR(IntPtr ContextID, double d);
+
+
+
+ // Grab the ContextID from an open transform. Returns NULL if a NULL transform is passed
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsGetTransformContextID(IntPtr hTransform);
+
+ // Grab the input/output formats
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetTransformInputFormat(IntPtr hTransform);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetTransformOutputFormat(IntPtr hTransform);
+
+ // For backwards compatibility
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsChangeBuffersFormat(IntPtr hTransform,
+ uint InputFormat,
+ uint OutputFormat);
+
+ // PostScript ColorRenderingDictionary and ColorSpaceArray ----------------------------------------------------
+
+ // lcms2 unified method to access postscript color resources
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetPostScriptColorResource(IntPtr ContextID,
+ PSResourceType Type,
+ IntPtr hProfile,
+ uint Intent,
+ uint dwFlags,
+ IntPtr io);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetPostScriptCSA(IntPtr ContextID, IntPtr hProfile, uint Intent, uint dwFlags, void* Buffer, uint dwBufferLen);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsGetPostScriptCRD(IntPtr ContextID, IntPtr hProfile, uint Intent, uint dwFlags, void* Buffer, uint dwBufferLen);
+
+
+ // IT8.7 / CGATS.17-200x handling -----------------------------------------------------------------------------
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsIT8Alloc(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsIT8Free(IntPtr hIT8);
+
+ // Tables
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsIT8TableCount(IntPtr hIT8);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsIT8SetTable(IntPtr hIT8, uint nTable);
+
+ // Persistence
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsIT8LoadFromMem(IntPtr ContextID, void* Ptr, uint len);
+ //[DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ //public static extern IntPtr cmsIT8LoadFromIOhandler(IntPtr ContextID, IntPtr io);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SaveToMem(IntPtr hIT8, void* MemPtr, uint* BytesNeeded);
+
+ // Properties
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.LPStr)]
+ public static extern string cmsIT8GetSheetType(IntPtr hIT8);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetSheetType(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Type);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetComment(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cComment);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetPropertyStr(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cProp, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Str);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetPropertyDbl(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cProp, double Val);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetPropertyHex(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cProp, uint Val);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetPropertyMulti(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Key, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string SubKey, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Buffer);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetPropertyUncooked(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Key, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Buffer);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.LPUTF8Str)]
+ public static extern string cmsIT8GetProperty(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cProp);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsIT8GetPropertyDbl(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cProp);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.LPUTF8Str)]
+ public static extern string cmsIT8GetPropertyMulti(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Key, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string SubKey);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsIT8EnumProperties(IntPtr hIT8, out byte** PropertyNames);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern uint cmsIT8EnumPropertyMulti(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cProp, out byte** SubpropertyNames);
+
+ // Datasets
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.LPUTF8Str)]
+ public static extern string cmsIT8GetDataRowCol(IntPtr hIT8, int row, int col);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsIT8GetDataRowColDbl(IntPtr hIT8, int row, int col);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetDataRowCol(IntPtr hIT8, int row, int col,
+ [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Val);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetDataRowColDbl(IntPtr hIT8, int row, int col,
+ double Val);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.LPUTF8Str)]
+ public static extern string cmsIT8GetData(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cPatch, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cSample);
+
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsIT8GetDataDbl(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cPatch, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cSample);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetData(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cPatch,
+ [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cSample,
+ [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Val);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetDataDbl(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cPatch,
+ [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cSample,
+ double Val);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsIT8FindDataFormat(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cSample);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetDataFormat(IntPtr hIT8, int n, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Sample);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsIT8EnumDataFormat(IntPtr hIT8, out byte** SampleNames);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.LPUTF8Str)]
+ public static extern string cmsIT8GetPatchName(IntPtr hIT8, int nPatch, void* buffer);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsIT8GetPatchByName(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cPatch);
+
+ // The LABEL extension
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern int cmsIT8SetTableByLabel(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cSet, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cField, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string ExpectedType);
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsIT8SetIndexColumn(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string cSample);
+
+ // Formatter for double
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsIT8DefineDblFormat(IntPtr hIT8, [In, MarshalAs(UnmanagedType.LPUTF8Str)] string Formatter);
+
+
+ // Gamut boundary description routines ------------------------------------------------------------------------------
+
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern IntPtr cmsGBDAlloc(IntPtr ContextID);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern void cmsGBDFree(IntPtr hGBD);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsGDBAddPoint(IntPtr hGBD, in CIELab Lab);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsGDBCompute(IntPtr hGDB, uint dwFlags);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsGDBCheckPoint(IntPtr hGBD, in CIELab Lab);
+
+ // Feature detection ----------------------------------------------------------------------------------------------
+
+ // Estimate the black point
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsDetectBlackPoint(out CIEXYZ BlackPoint, IntPtr hProfile, uint Intent, uint dwFlags);
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsDetectDestinationBlackPoint(out CIEXYZ BlackPoint, IntPtr hProfile, uint Intent, uint dwFlags);
+
+ // Estimate total area coverage
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsDetectTAC(IntPtr hProfile);
+
+ // Estimate gamma space, always positive. Returns -1 on error.
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern double cmsDetectRGBProfileGamma(IntPtr hProfile, double threshold);
+
+ // Poor man's gamut mapping
+ [DllImport(LIB, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)]
+ public static extern bool cmsDesaturateLab(ref CIELab Lab,
+ double amax, double amin,
+ double bmax, double bmin);
+ }
+
+
+}
+
diff --git a/LittleCms/CmsObject.cs b/LittleCms/CmsObject.cs
new file mode 100644
index 0000000..dddec69
--- /dev/null
+++ b/LittleCms/CmsObject.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace LittleCms
+{
+ public abstract class CmsObject
+ {
+ private IntPtr handle;
+ private bool isOwner;
+
+ public IntPtr Handle => handle;
+ public bool IsOwner => isOwner;
+
+ public abstract CmsContext Context { get; }
+
+
+ protected CmsObject() { }
+
+ protected CmsObject(IntPtr handle, bool moveOwnership)
+ {
+ AttachObject(handle, isOwner);
+ }
+
+
+ protected void AttachObject(IntPtr handle, bool moveOwnership)
+ {
+ if (handle != IntPtr.Zero && isOwner)
+ {
+ FreeObject();
+ }
+ if (handle == IntPtr.Zero)
+ {
+ throw new NullReferenceException();
+ }
+ this.handle = handle;
+ this.isOwner = moveOwnership;
+ }
+
+ ~CmsObject() {
+ if (handle != IntPtr.Zero && isOwner) {
+ FreeObject();
+ }
+ }
+
+ protected abstract void FreeObject();
+ }
+}
diff --git a/LittleCms/CmsPixelFormat.cs b/LittleCms/CmsPixelFormat.cs
new file mode 100644
index 0000000..acd4b14
--- /dev/null
+++ b/LittleCms/CmsPixelFormat.cs
@@ -0,0 +1,145 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace LittleCms
+{
+ public struct CmsPixelFormat
+ {
+ public uint Value;
+
+ public static implicit operator uint(CmsPixelFormat x) => x.Value;
+
+ // boolean fields
+ const int shift_IsPremultipliedAlpha = 23;
+ const int shift_IsFloatingPoint = 22;
+ const int shift_IsOptimized = 21;
+ const int shift_RotateFirstChannel = 14;
+ const int shift_IsInvertedIntensity = 13;
+ const int shift_IsPlanar = 12;
+ const int shift_IsWordOrderSwapped = 11;
+ const int shift_IsByteOrderSwapped = 10;
+
+ // enum fields
+ const uint mask_PixelType = 0b11111u;
+ const int shift_PixelType = 16;
+
+ // uint fields
+ const uint mask_ExtraChannels = 0b111u;
+ const int shift_ExtraChannels = 7;
+ const uint mask_Channels = 0b1111u;
+ const int shift_Channels = 3;
+ const uint mask_BytesPerChannel = 0b111;
+ const int shift_BytesPerChannel = 0;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool GetBit(int shift)
+ {
+ var mask = 1u << shift;
+ return (Value & mask) != 0;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetBit(int shift, bool value)
+ {
+ var mask = 1u << shift;
+ Value = (Value & (~mask)) | (value ? mask : 0);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private uint GetBits(int shift, uint mask) => (Value >> shift) & mask;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetBits(int shift, uint mask, uint value)
+ {
+ var smask = mask << shift;
+ value = (value & mask) << shift;
+ Value = (Value & (~smask)) | value;
+ }
+
+
+ public bool IsPremultipliedAlpha
+ {
+ get => GetBit(shift_IsPremultipliedAlpha);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_IsPremultipliedAlpha, value);
+ }
+
+ public bool IsFloatingPoint
+ {
+ get => GetBit(shift_IsFloatingPoint);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_IsFloatingPoint, value);
+ }
+ public bool IsOptimized
+ {
+ get => GetBit(shift_IsOptimized);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_IsOptimized, value);
+ }
+ public bool RotateFirstChannel
+ {
+ get => GetBit(shift_RotateFirstChannel);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_RotateFirstChannel, value);
+ }
+ public bool IsPlanar
+ {
+ get => GetBit(shift_IsPlanar);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_IsPlanar, value);
+ }
+ public bool IsInvertedIntensity
+ {
+ get => GetBit(shift_IsInvertedIntensity);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_IsInvertedIntensity, value);
+ }
+ public bool IsWordOrderSwapped
+ {
+ get => GetBit(shift_IsWordOrderSwapped);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_IsWordOrderSwapped, value);
+ }
+ public bool IsByteOrderSwapped
+ {
+ get => GetBit(shift_IsByteOrderSwapped);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBit(shift_IsByteOrderSwapped, value);
+ }
+
+ public PixelType PixelType
+ {
+ get => (PixelType)GetBits(shift_PixelType, mask_PixelType);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBits(shift_PixelType, mask_PixelType, (uint)value);
+ }
+ public int ExtraChannels
+ {
+ get => (int)GetBits(shift_ExtraChannels, mask_ExtraChannels);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBits(shift_ExtraChannels, mask_ExtraChannels, (uint)value);
+ }
+ public int Channels
+ {
+ get => (int)GetBits(shift_Channels, mask_Channels);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBits(shift_Channels, mask_Channels, (uint)value);
+ }
+ ///
+ /// use 0 for double-precision IEEE-754 floating point.
+ ///
+ public int BytesPerChannel
+ {
+ get => (int)GetBits(shift_BytesPerChannel, mask_BytesPerChannel);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ set => SetBits(shift_BytesPerChannel, mask_BytesPerChannel, (uint)value);
+ }
+
+
+ public static CmsPixelFormat RGB8 => new CmsPixelFormat { PixelType = PixelType.RGB, Channels = 3, BytesPerChannel = 1 };
+ public static CmsPixelFormat RGBDouble => new CmsPixelFormat { PixelType = PixelType.RGB, Channels = 3, BytesPerChannel = 0, IsFloatingPoint = true };
+ public static CmsPixelFormat XYZDouble => new CmsPixelFormat { PixelType = PixelType.XYZ, Channels = 3, BytesPerChannel = 0, IsFloatingPoint = true };
+ }
+}
diff --git a/LittleCms/CmsTransform.cs b/LittleCms/CmsTransform.cs
new file mode 100644
index 0000000..4e0df44
--- /dev/null
+++ b/LittleCms/CmsTransform.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using static LittleCms.CmsNative;
+
+namespace LittleCms
+{
+ public class CmsTransform : CmsObject
+ {
+ private IccProfile inputProfile;
+ private IccProfile outputProfile;
+ public override CmsContext Context { get; }
+ public CmsTransform(IccProfile inputProfile, CmsPixelFormat inputFormat,
+ IccProfile outputProfile, CmsPixelFormat outputFormat,
+ RenderingIntent intent, TransformFlags flags) : this(CmsContext.Default, inputProfile, inputFormat, outputProfile, outputFormat, intent, flags) { }
+
+ public CmsTransform(CmsContext context, IccProfile inputProfile, CmsPixelFormat inputFormat,
+ IccProfile outputProfile, CmsPixelFormat outputFormat,
+ RenderingIntent intent, TransformFlags flags)
+ {
+ this.Context = context;
+ this.inputProfile = inputProfile;
+ this.outputProfile = outputProfile;
+
+ var handle = CheckError(cmsCreateTransformTHR(context.Handle, inputProfile.Handle, inputFormat, outputProfile.Handle, outputFormat, (uint)intent, (uint)flags));
+ AttachObject(handle, true);
+ }
+
+
+ protected override void FreeObject()
+ {
+ cmsDeleteTransform(Handle);
+ }
+
+ public unsafe void DoTransform(ReadOnlySpan input, Span output, uint pixelsToTransform) where TIn : unmanaged where TOut : unmanaged
+ {
+ fixed (TIn* inptr = input)
+ fixed (TOut* outptr = output)
+ cmsDoTransform(Handle, inptr, outptr, pixelsToTransform);
+ }
+
+ public unsafe void DoTransform(ReadOnlySpan input, Span output, uint pixelsPerLine, uint lineCount,
+ uint bytesPerLineIn, uint bytesPerLineOut, uint bytesPerPlaneIn, uint bytesPerPlaneOut) where TIn : unmanaged where TOut : unmanaged
+ {
+ fixed (TIn* inptr = input)
+ fixed (TOut* outptr = output)
+ cmsDoTransformLineStride(Handle, inptr, outptr, pixelsPerLine, lineCount, bytesPerLineIn, bytesPerLineOut, bytesPerPlaneIn, bytesPerPlaneOut);
+ }
+
+ }
+}
diff --git a/LittleCms/Data/BlittableDataTagSignature.cs b/LittleCms/Data/BlittableDataTagSignature.cs
new file mode 100644
index 0000000..27a0c71
--- /dev/null
+++ b/LittleCms/Data/BlittableDataTagSignature.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+
+namespace LittleCms.Data
+{
+ internal record BlittableDataTag(TagSignature TagSignature) : ISafeTagSignature where T : unmanaged
+ {
+ public unsafe T ReadFromProfile(IccProfile profile)
+ {
+ var ptr = profile.ReadTag(TagSignature);
+ if (ptr == null) throw new TagNotFoundException();
+ return *(T*)ptr;
+ }
+
+ public unsafe void WriteToProfile(IccProfile profile, T data)
+ {
+ profile.WriteTag(TagSignature, &data);
+ }
+ }
+}
diff --git a/LittleCms/Data/ChromaticAdaptationTagSignature.cs b/LittleCms/Data/ChromaticAdaptationTagSignature.cs
new file mode 100644
index 0000000..0ced926
--- /dev/null
+++ b/LittleCms/Data/ChromaticAdaptationTagSignature.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms.Data
+{
+ internal class ChromaticAdaptationTagSignature : ISafeTagSignature
+ {
+ public TagSignature TagSignature => TagSignature.ChromaticAdaptationTag;
+
+ public unsafe double[,] ReadFromProfile(IccProfile profile)
+ {
+ var ptr = (double*)profile.ReadTag(TagSignature);
+ if (ptr == null) throw new TagNotFoundException();
+ var result = new double[3, 3];
+ new ReadOnlySpan(ptr, 9).CopyTo(MemoryMarshal.CreateSpan(ref result[0, 0], 9));
+ return result;
+ }
+
+ public unsafe void WriteToProfile(IccProfile profile, double[,] data)
+ {
+ if (data == null) throw new ArgumentNullException(nameof(data));
+ fixed (double* ptr = &data[0, 0])
+ profile.WriteTag(TagSignature, ptr);
+ }
+ }
+}
diff --git a/LittleCms/Data/ISafeTagSignature.cs b/LittleCms/Data/ISafeTagSignature.cs
new file mode 100644
index 0000000..61e8f69
--- /dev/null
+++ b/LittleCms/Data/ISafeTagSignature.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms.Data
+{
+ public interface ISafeTagSignature
+ {
+ TagSignature TagSignature { get; }
+ T ReadFromProfile(IccProfile profile);
+ void WriteToProfile(IccProfile profile, T data);
+ }
+
+}
diff --git a/LittleCms/Data/IccDataTagSignature.cs b/LittleCms/Data/IccDataTagSignature.cs
new file mode 100644
index 0000000..f015b21
--- /dev/null
+++ b/LittleCms/Data/IccDataTagSignature.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms.Data
+{
+ internal record IccDataTagSignature(TagSignature TagSignature) : ISafeTagSignature
+ {
+ public unsafe ICCData ReadFromProfile(IccProfile profile)
+ {
+ var ptr = profile.ReadTag(TagSignature);
+ if (ptr == null) throw new TagNotFoundException();
+ return _ICCData.ToManaged((_ICCData*)ptr);
+ }
+
+ public unsafe void WriteToProfile(IccProfile profile, ICCData obj)
+ {
+ var data = new byte[sizeof(_ICCDataHeader) + obj.Data.Length];
+ obj.Data.Span.CopyTo(data.AsSpan().Slice(sizeof(_ICCDataHeader)));
+ fixed (byte* ptr = data)
+ {
+ var header = (_ICCDataHeader*)ptr;
+ header->flag = obj.Flag;
+ header->len = (uint)obj.Data.Length;
+
+ profile.WriteTag(TagSignature, ptr);
+ }
+ }
+ }
+}
diff --git a/LittleCms/Data/MLU.cs b/LittleCms/Data/MLU.cs
new file mode 100644
index 0000000..582425c
--- /dev/null
+++ b/LittleCms/Data/MLU.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+using System.ComponentModel;
+
+
+using static LittleCms.CmsNative;
+using System.Runtime.CompilerServices;
+
+namespace LittleCms.Data
+{
+ public class MLU : CmsObject
+ {
+ public const string NoLanguage = "\0\0";
+ public const string NoCountry = "\0\0";
+
+#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member
+ [Obsolete("Only allocator in context is used in MLU, which is not modeled in this binding")]
+ public override CmsContext Context { get; } = CmsContext.Default;
+#pragma warning restore CS0809
+
+ internal unsafe static (uint, uint) EncodeLanguageCountryCode(string code1, string code2)
+ {
+ if (code1 == null || code1.Length != 2 || code1[0] >= 0x80 || code1[1] >= 0x80) throw new ArgumentException();
+ if (code2 == null || code2.Length != 2 || code2[0] >= 0x80 || code2[1] >= 0x80) throw new ArgumentException();
+ Span result32 = stackalloc uint[2];
+ var result8 = MemoryMarshal.Cast(result32);
+ result8[0] = (byte)code1[0];
+ result8[1] = (byte)code1[1];
+ result8[4] = (byte)code2[0];
+ result8[5] = (byte)code2[1];
+ return (result32[0], result32[1]);
+ }
+
+ ///
+ /// Attach a cmsMLU
+ ///
+ ///
+ ///
+ public MLU(IntPtr handle, bool moveOwnership = false) : base(handle, moveOwnership)
+ {
+ // FIXME: missing cmsGetMLUContextID
+ }
+
+ public MLU(int itemCount) : this(CmsContext.Default, itemCount) { }
+
+ public MLU(CmsContext context, int itemCount)
+ {
+ Context = context;
+ IntPtr handle = CheckError(cmsMLUalloc(context.Handle, (uint)itemCount));
+ AttachObject(handle, true);
+ }
+
+ ///
+ /// Copy a cmsMLU by handle
+ ///
+ ///
+ ///
+ public static MLU CopyFromObject(nint handleToCopy)
+ {
+ nint handle = CheckError(cmsMLUdup(handleToCopy));
+ return new(handle, true);
+ }
+
+ ///
+ /// Creates a MLU with en-US string
+ ///
+ ///
+ public MLU(string s) : this(1)
+ {
+ Set("en", "US", s);
+ }
+
+ protected override void FreeObject()
+ {
+ cmsMLUfree(Handle);
+ }
+
+ public unsafe void Set(string language, string country, string s)
+ {
+ var (lang, cont) = EncodeLanguageCountryCode(language, country);
+ var wchbuf = WcharEncoding.GetBytes(s + "\0");
+ CheckError(cmsMLUsetWide(Handle, in lang, in cont, in wchbuf[0]));
+
+ }
+
+ public unsafe string? Get(string languageCode, string countryCode)
+ {
+ var (lang, cont) = EncodeLanguageCountryCode(languageCode, countryCode);
+ var len = cmsMLUgetWide(Handle, in lang, in cont, null, 0);
+ if (len == 0)
+ {
+ return null;
+ }
+ var buf = new byte[(int)len];
+ fixed (byte* ptr = buf)
+ len = CheckError(cmsMLUgetWide(Handle, in lang, in cont, ptr, len));
+ var s = WcharEncoding.GetString(buf);
+ return s[..^1];
+ }
+
+ public string? GetAny() => Get(NoLanguage, NoCountry);
+
+ public override string ToString() => GetAny() ?? "";
+
+
+ }
+}
\ No newline at end of file
diff --git a/LittleCms/Data/MluTagSignature.cs b/LittleCms/Data/MluTagSignature.cs
new file mode 100644
index 0000000..6ffddbc
--- /dev/null
+++ b/LittleCms/Data/MluTagSignature.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms.Data
+{
+ internal record MluTagSignature(TagSignature TagSignature) : ISafeTagSignature
+ {
+ public unsafe MLU ReadFromProfile(IccProfile profile)
+ {
+ var ptr = profile.ReadTag(TagSignature);
+ if (ptr == null) throw new TagNotFoundException();
+ return MLU.CopyFromObject((IntPtr)ptr);
+ }
+
+ public unsafe void WriteToProfile(IccProfile profile, MLU data)
+ {
+ if (data == null) throw new ArgumentNullException(nameof(data));
+ profile.WriteTag(TagSignature, (void*)data.Handle);
+ }
+ }
+}
diff --git a/LittleCms/Data/RgbToneCurve.cs b/LittleCms/Data/RgbToneCurve.cs
new file mode 100644
index 0000000..50479ef
--- /dev/null
+++ b/LittleCms/Data/RgbToneCurve.cs
@@ -0,0 +1,7 @@
+namespace LittleCms.Data
+{
+ public record RgbToneCurve(ToneCurve Red, ToneCurve Green, ToneCurve Blue)
+ {
+ public ToneCurve[] ToArray() => new[] { Red, Green, Blue };
+ }
+}
diff --git a/LittleCms/Data/SafeTagSignature.cs b/LittleCms/Data/SafeTagSignature.cs
new file mode 100644
index 0000000..25bd91a
--- /dev/null
+++ b/LittleCms/Data/SafeTagSignature.cs
@@ -0,0 +1,41 @@
+namespace LittleCms.Data
+{
+ public static partial class SafeTagSignature
+ {
+ public static ISafeTagSignature ChromaticityTag { get; } = new BlittableDataTag(TagSignature.ChromaticityTag);
+ public static ISafeTagSignature MediaWhitePointTag { get; } = new BlittableDataTag(TagSignature.MediaWhitePointTag);
+ public static ISafeTagSignature BlueColorantTag { get; } = new BlittableDataTag(TagSignature.BlueColorantTag);
+ public static ISafeTagSignature GreenColorantTag { get; } = new BlittableDataTag(TagSignature.GreenColorantTag);
+ public static ISafeTagSignature LuminanceTag { get; } = new BlittableDataTag(TagSignature.LuminanceTag);
+ public static ISafeTagSignature MediaBlackPointTag { get; } = new BlittableDataTag(TagSignature.MediaBlackPointTag);
+ public static ISafeTagSignature RedColorantTag { get; } = new BlittableDataTag(TagSignature.RedColorantTag);
+
+ public static ISafeTagSignature ChromaticAdaptationTag { get; } = new ChromaticAdaptationTagSignature();
+
+ public static ISafeTagSignature Mhc2Tag { get; } = new IccDataTagSignature(TagSignature.MHC2);
+
+ public static ISafeTagSignature DataTag { get; } = new IccDataTagSignature(TagSignature.DataTag);
+ public static ISafeTagSignature Ps2CRD0Tag { get; } = new IccDataTagSignature(TagSignature.Ps2CRD0Tag);
+ public static ISafeTagSignature Ps2CRD1Tag { get; } = new IccDataTagSignature(TagSignature.Ps2CRD1Tag);
+ public static ISafeTagSignature Ps2CRD2Tag { get; } = new IccDataTagSignature(TagSignature.Ps2CRD2Tag);
+ public static ISafeTagSignature Ps2CRD3Tag { get; } = new IccDataTagSignature(TagSignature.Ps2CRD3Tag);
+ public static ISafeTagSignature Ps2CSATag { get; } = new IccDataTagSignature(TagSignature.Ps2CSATag);
+ public static ISafeTagSignature Ps2RenderingIntentTag { get; } = new IccDataTagSignature(TagSignature.Ps2RenderingIntentTag);
+
+ public static ISafeTagSignature BlueTRCTag { get; } = new ToneCurveTagSignature(TagSignature.BlueTRCTag);
+ public static ISafeTagSignature GrayTRCTag { get; } = new ToneCurveTagSignature(TagSignature.GrayTRCTag);
+ public static ISafeTagSignature GreenTRCTag { get; } = new ToneCurveTagSignature(TagSignature.GreenTRCTag);
+ public static ISafeTagSignature RedTRCTag { get; } = new ToneCurveTagSignature(TagSignature.RedTRCTag);
+
+ public static ISafeTagSignature VcgtTag { get; } = new VideoCardGammaTableTagSignature();
+
+ public static ISafeTagSignature CharTargetTag { get; } = new MluTagSignature(TagSignature.CharTargetTag);
+ public static ISafeTagSignature CopyrightTag { get; } = new MluTagSignature(TagSignature.CopyrightTag);
+ public static ISafeTagSignature DeviceMfgDescTag { get; } = new MluTagSignature(TagSignature.DeviceMfgDescTag);
+ public static ISafeTagSignature DeviceModelDescTag { get; } = new MluTagSignature(TagSignature.DeviceModelDescTag);
+ public static ISafeTagSignature ProfileDescriptionTag { get; } = new MluTagSignature(TagSignature.ProfileDescriptionTag);
+ public static ISafeTagSignature ScreeningDescTag { get; } = new MluTagSignature(TagSignature.ScreeningDescTag);
+ public static ISafeTagSignature ViewingCondDescTag { get; } = new MluTagSignature(TagSignature.ViewingCondDescTag);
+
+ }
+}
diff --git a/LittleCms/Data/TagNotFoundException.cs b/LittleCms/Data/TagNotFoundException.cs
new file mode 100644
index 0000000..ce83236
--- /dev/null
+++ b/LittleCms/Data/TagNotFoundException.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms.Data
+{
+ public class TagNotFoundException : Exception
+ {
+ private const int hr = unchecked((int)0x80131577);
+ public TagNotFoundException()
+ {
+ HResult = hr;
+ }
+
+ public TagNotFoundException(string message) : base(message)
+ {
+ HResult = hr;
+
+ }
+ public TagNotFoundException(string? message, Exception? innerException)
+ : base(message, innerException)
+ {
+ HResult = hr;
+ }
+ }
+}
diff --git a/LittleCms/Data/ToneCurve.cs b/LittleCms/Data/ToneCurve.cs
new file mode 100644
index 0000000..954b499
--- /dev/null
+++ b/LittleCms/Data/ToneCurve.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using static LittleCms.CmsNative;
+
+namespace LittleCms.Data
+{
+ public class ToneCurve : CmsObject
+ {
+#pragma warning disable CS0809
+ [Obsolete("lcms2 don't expose API to get context of cmsToneCurve")]
+ public override CmsContext Context => throw new NotImplementedException();
+#pragma warning restore CS0809
+
+ public ToneCurve(nint handle, bool moveOwnership) : base(handle, moveOwnership) { }
+
+ public ToneCurve(CmsContext context, double gamma)
+ {
+ AttachObject(CheckError(cmsBuildGamma(context.Handle, gamma)), true);
+ }
+
+ public ToneCurve(CmsContext context, ReadOnlySpan table)
+ {
+ AttachObject(CheckError(cmsBuildTabulatedToneCurveFloat(context.Handle, (uint)table.Length, in table[0])), true);
+ }
+
+ public ToneCurve(CmsContext context, ReadOnlySpan table)
+ {
+ AttachObject(CheckError(cmsBuildTabulatedToneCurve16(context.Handle, (uint)table.Length, in table[0])), true);
+ }
+
+ public ToneCurve(double gamma) : this(CmsContext.Default, gamma) { }
+
+ public ToneCurve(ReadOnlySpan table) : this(CmsContext.Default, table) { }
+
+ public ToneCurve(ReadOnlySpan table) : this(CmsContext.Default, table) { }
+
+ public static ToneCurve CopyFromObject(nint copyFromObject)
+ {
+ var handle = CheckError(cmsDupToneCurve(copyFromObject));
+ return new(handle, true);
+ }
+ protected override void FreeObject()
+ {
+ throw new NotImplementedException();
+ }
+
+ public ToneCurve Reverse()
+ {
+ var handle = CheckError(cmsReverseToneCurve(Handle));
+ return new(handle, true);
+ }
+
+ public ToneCurve Reverse(uint sampleCount)
+ {
+ var handle = CheckError(cmsReverseToneCurveEx(sampleCount, Handle));
+ return new(handle, true);
+ }
+
+ public ushort EvalU16(ushort x) => cmsEvalToneCurve16(Handle, x);
+ public float EvalF32(float x) => cmsEvalToneCurveFloat(Handle, x);
+
+ }
+}
diff --git a/LittleCms/Data/ToneCurveTagSignature.cs b/LittleCms/Data/ToneCurveTagSignature.cs
new file mode 100644
index 0000000..5dfb1a7
--- /dev/null
+++ b/LittleCms/Data/ToneCurveTagSignature.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms.Data
+{
+ internal record ToneCurveTagSignature(TagSignature TagSignature) : ISafeTagSignature
+ {
+ public unsafe ToneCurve ReadFromProfile(IccProfile profile)
+ {
+ var ptr = profile.ReadTag(TagSignature);
+ if (ptr == null) throw new TagNotFoundException();
+ return ToneCurve.CopyFromObject((IntPtr)ptr);
+ }
+
+ public unsafe void WriteToProfile(IccProfile profile, ToneCurve data)
+ {
+ if (data == null) throw new ArgumentNullException(nameof(data));
+ profile.WriteTag(TagSignature, (void*)data.Handle);
+ }
+ }
+}
diff --git a/LittleCms/Data/VideoCardGammaTableTagSignature.cs b/LittleCms/Data/VideoCardGammaTableTagSignature.cs
new file mode 100644
index 0000000..adce4db
--- /dev/null
+++ b/LittleCms/Data/VideoCardGammaTableTagSignature.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LittleCms.Data
+{
+ internal class VideoCardGammaTableTagSignature : ISafeTagSignature
+ {
+ public TagSignature TagSignature => TagSignature.VcgtTag;
+
+ public unsafe RgbToneCurve ReadFromProfile(IccProfile profile)
+ {
+ var ptr = (IntPtr*)profile.ReadTag(TagSignature);
+ if (ptr == null) throw new TagNotFoundException();
+ return new RgbToneCurve(
+ ToneCurve.CopyFromObject(ptr[0]),
+ ToneCurve.CopyFromObject(ptr[1]),
+ ToneCurve.CopyFromObject(ptr[2])
+ );
+ }
+
+ public unsafe void WriteToProfile(IccProfile profile, RgbToneCurve obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ var handles = stackalloc IntPtr[] { obj.Red.Handle, obj.Green.Handle, obj.Blue.Handle };
+ profile.WriteTag(TagSignature, handles);
+ }
+ }
+}
diff --git a/LittleCms/IccProfile.cs b/LittleCms/IccProfile.cs
new file mode 100644
index 0000000..24d0e47
--- /dev/null
+++ b/LittleCms/IccProfile.cs
@@ -0,0 +1,259 @@
+using LittleCms.Data;
+using System;
+using System.Diagnostics.CodeAnalysis;
+using static LittleCms.CmsNative;
+
+namespace LittleCms
+{
+ public class IccProfile : CmsObject
+ {
+ public override CmsContext Context { get; }
+ public byte[] RawBytes { get; }
+
+ public IccProfile(IntPtr handle, bool moveOwnership) : base(handle, moveOwnership)
+ {
+ Context = CmsContext.GetFromHandle(cmsGetProfileContextID(handle));
+ }
+ public IccProfile(IntPtr handle, CmsContext context, ReadOnlySpan bytes) : base(handle, true)
+ {
+ Context = context;
+ RawBytes = bytes.ToArray();
+ }
+
+ public static IccProfile Create_sRGB(CmsContext context)
+ {
+ return new IccProfile(CheckError(cmsCreate_sRGBProfileTHR(context.Handle)), true);
+ }
+
+ public static IccProfile Create_sRGB()
+ {
+ return new IccProfile(CheckError(cmsCreate_sRGBProfile()), true);
+ }
+
+ public static IccProfile CreateXYZ(CmsContext context)
+ {
+ return new IccProfile(CheckError(cmsCreateXYZProfileTHR(context.Handle)), true);
+ }
+
+ public static IccProfile CreateXYZ()
+ {
+ return new IccProfile(CheckError(cmsCreateXYZProfile()), true);
+ }
+
+ public static IccProfile CreateRGB(CIExyY whitepoint, CIExyYTRIPLE primaries, RgbToneCurve transfer)
+ {
+ return CreateRGB(CmsContext.Default, whitepoint, primaries, transfer);
+ }
+
+ public static unsafe IccProfile CreateRGB(CmsContext context, CIExyY whitepoint, CIExyYTRIPLE primaries, RgbToneCurve transfer)
+ {
+ Span transferhandle = stackalloc IntPtr[] { transfer.Red.Handle, transfer.Green.Handle, transfer.Blue.Handle };
+ var handle = CheckError(cmsCreateRGBProfile(whitepoint, primaries, ref transferhandle[0]));
+ return new(handle, true);
+ }
+
+ public static unsafe IccProfile Open(CmsContext context, ReadOnlySpan bytes)
+ {
+ fixed (byte* ptr = bytes)
+ {
+ var handle = CheckError(cmsOpenProfileFromMemTHR(context.Handle, ptr, (uint)bytes.Length));
+ return new IccProfile(handle, context, bytes);
+ }
+ }
+ public static IccProfile Open(ReadOnlySpan bytes) => Open(CmsContext.Default, bytes);
+
+
+ public unsafe byte[] GetBytes()
+ {
+ uint newlen = 0;
+ CheckError(cmsSaveProfileToMem(Handle, null, ref newlen));
+ var newicc = new byte[newlen];
+ fixed (byte* ptr = newicc)
+ CheckError(cmsSaveProfileToMem(Handle, ptr, ref newlen));
+ return newicc;
+ }
+
+ protected override void FreeObject()
+ {
+ cmsCloseProfile(Handle);
+ }
+
+ public unsafe void* ReadTag(TagSignature sig)
+ {
+ return cmsReadTag(Handle, sig);
+ }
+
+ public unsafe bool ContainsTag(TagSignature sig) => ReadTag(sig) != null;
+
+ public T ReadTag(ISafeTagSignature sig)
+ {
+ return sig.ReadFromProfile(this);
+ }
+
+ [return: NotNullIfNotNull(nameof(defaultValue))]
+ public T? ReadTagOrDefault(ISafeTagSignature sig, T? defaultValue = default)
+ {
+ try
+ {
+ return sig.ReadFromProfile(this);
+ }
+ catch (TagNotFoundException)
+ {
+ return defaultValue;
+ }
+ }
+
+ public bool TryReadTag(ISafeTagSignature sig, [NotNullWhen(returnValue: true)] out T? value)
+ {
+ try
+ {
+ value = sig.ReadFromProfile(this)!;
+ return true;
+ }
+ catch (TagNotFoundException)
+ {
+ value = default;
+ return false;
+ }
+ }
+
+ public unsafe void WriteTag(TagSignature sig, void* handle)
+ {
+ var result = cmsWriteTag(Handle, sig, handle);
+ if (handle != null)
+ {
+ CheckError(result);
+ }
+ }
+
+ public void WriteTag(ISafeTagSignature sig, T value)
+ {
+ sig.WriteToProfile(this, value);
+ }
+
+ public unsafe void RemoveTag(TagSignature sig)
+ {
+ CheckError(cmsWriteTag(Handle, sig, null));
+ }
+
+ public unsafe void RemoveTag(ISafeTagSignature sig)
+ {
+ CheckError(cmsWriteTag(Handle, sig.TagSignature, null));
+ }
+
+ public unsafe byte[]? ReadRawTag(TagSignature sig)
+ {
+ if (!cmsIsTag(Handle, sig)) return null;
+ var len = cmsReadRawTag(Handle, sig, null, 0);
+ var buf = new byte[len];
+ fixed (byte* ptr = buf)
+ len = cmsReadRawTag(Handle, sig, ptr, len);
+ return buf;
+ }
+
+ public void WriteRawTag(TagSignature sig, ReadOnlySpan bytes)
+ {
+ CheckError(cmsWriteRawTag(Handle, sig, in bytes[0], (uint)bytes.Length));
+ }
+
+ public void ComputeProfileId()
+ {
+ CheckError(cmsMD5computeID(Handle));
+ }
+
+ public HeaderFlags HeaderFlags
+ {
+ get => cmsGetHeaderFlags(Handle);
+ set => cmsSetHeaderFlags(Handle, value);
+ }
+
+ public unsafe string GetInfo(InfoType info, string languageCode = MLU.NoLanguage, string countryCode = MLU.NoCountry)
+ {
+ var (lang, cont) = MLU.EncodeLanguageCountryCode(languageCode, countryCode);
+ var len = cmsGetProfileInfo(Handle, info, in lang, in cont, null, 0);
+ if (len == 0) return "";
+ var buf = new byte[len];
+ fixed (byte* ptr = buf)
+ len = cmsGetProfileInfo(Handle, info, in lang, in cont, ptr, len);
+ var s = WcharEncoding.GetString(buf)[..^1];
+ return s;
+ }
+
+ public ulong HeaderAttributes
+ {
+ get
+ {
+ cmsGetHeaderAttributes(Handle, out var attr);
+ return attr;
+ }
+ set => cmsSetHeaderAttributes(Handle, value);
+ }
+
+ public Guid HeaderProfileId
+ {
+ get
+ {
+ cmsGetHeaderProfileID(Handle, out var attr);
+ return attr;
+ }
+ set => cmsSetHeaderProfileID(Handle, value);
+ }
+
+ public DateTime HeaderCreationDateTime
+ {
+ get
+ {
+ CheckError(cmsGetHeaderCreationDateTime(Handle, out var tm));
+ return tm.ToDateTime();
+ }
+ }
+
+ public RenderingIntent HeaderRenderingIntent
+ {
+ get => (RenderingIntent)cmsGetHeaderRenderingIntent(Handle);
+ set => cmsSetHeaderRenderingIntent(Handle, (uint)value);
+ }
+
+ public uint HeaderManufacturer
+ {
+ get => cmsGetHeaderManufacturer(Handle);
+ set => cmsSetHeaderManufacturer(Handle, value);
+ }
+
+ public uint HeaderCreator
+ {
+ get => cmsGetHeaderCreator(Handle);
+ }
+
+ public uint HeaderModel
+ {
+ get => cmsGetHeaderModel(Handle);
+ set => cmsSetHeaderModel(Handle, value);
+ }
+
+ public ColorSpaceSignature PCS
+ {
+ get => cmsGetPCS(Handle);
+ set => cmsSetPCS(Handle, value);
+ }
+
+
+ public ColorSpaceSignature ColorSpace
+ {
+ get => cmsGetColorSpace(Handle);
+ set => cmsSetColorSpace(Handle, value);
+ }
+
+ public ProfileClassSignature DeviceClass
+ {
+ get => cmsGetDeviceClass(Handle);
+ set => cmsSetDeviceClass(Handle, value);
+ }
+
+ public double ProfileVersion
+ {
+ get => cmsGetProfileVersion(Handle);
+ set => cmsSetProfileVersion(Handle, value);
+ }
+ }
+}
diff --git a/LittleCms/LittleCms.csproj b/LittleCms/LittleCms.csproj
new file mode 100644
index 0000000..7c5ad42
--- /dev/null
+++ b/LittleCms/LittleCms.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net7.0
+ enable
+ True
+ 9
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/LittleCms/lcms2.dll b/LittleCms/lcms2.dll
new file mode 100644
index 0000000..a1e2539
Binary files /dev/null and b/LittleCms/lcms2.dll differ
diff --git a/MHC2Gen/CmsFunctions.cs b/MHC2Gen/CmsFunctions.cs
new file mode 100644
index 0000000..660d599
--- /dev/null
+++ b/MHC2Gen/CmsFunctions.cs
@@ -0,0 +1,96 @@
+using System;
+
+namespace MHC2Gen;
+
+public static class CmsFunctions
+{
+
+ private const double PQ_M1 = 0.1593017578125;
+ private const double PQ_M2 = 78.84375;
+ private const double PQ_C1 = 0.8359375;
+ private const double PQ_C2 = 18.8515625;
+ private const double PQ_C3 = 18.6875;
+
+ public static double PqEotf(double signalValue)
+ {
+ var signalValuePow = Math.Pow(signalValue, 1D / PQ_M2);
+
+ var value1 = Math.Max(signalValuePow - PQ_C1, 0);
+ var value2 = PQ_C2 - PQ_C3 * signalValuePow;
+
+ var luminance = 10000D * Math.Pow(value1 / value2, 1D / PQ_M1);
+
+ return luminance;
+ }
+
+ public static double InvPqEotf(double luminance)
+ {
+ var normalizedLuminance = luminance / 10000D;
+
+ var luminancePow = Math.Pow(normalizedLuminance, PQ_M1);
+
+ var value1 = PQ_C1 + PQ_C2 * luminancePow;
+ var value2 = 1 + PQ_C3 * luminancePow;
+
+ var signalValue = Math.Pow(value1 / value2, PQ_M2);
+
+ return signalValue;
+ }
+
+ public static double SrgbInvEotf(double luminance, double whiteLuminance, double blackLuminance, double gamma = 2.4)
+ {
+ //const double X1 = 0.0404482362771082;
+ const double X2 = 0.00313066844250063;
+
+ var x = whiteLuminance <= blackLuminance ? 0 : (luminance - blackLuminance) / (whiteLuminance - blackLuminance);
+
+ if (x > 1)
+ {
+ x = 1;
+ }
+ else if (x < 0)
+ {
+ x = 0;
+ }
+ else if (x <= X2)
+ {
+ x *= 12.92;
+ }
+ else
+ {
+ x = 1.055 * Math.Pow(x, 1 / gamma) - 0.055;
+ }
+
+ return x;
+ }
+
+ public static (double, double) SrgbAcm(double signalValue, double whiteLuminance, double blackLuminance = 0, double gamma = 2.2, int divider = 1023)
+ {
+ var b = signalValue / divider;
+ var c = PqEotf(b);
+ var d = SrgbInvEotf(c, whiteLuminance, blackLuminance);
+ var e = blackLuminance + (whiteLuminance - blackLuminance) * Math.Pow(d, gamma);
+ var f = InvPqEotf(Math.Max(0, e));
+ var x = f + Math.Min(1, c / whiteLuminance) * (b - f);
+
+ return (b, x);
+ }
+
+ public static double Srgb(double signalValue)
+ {
+ var b = signalValue / 1023;
+
+ return b;
+ }
+
+ public static double GammaRgb(double signalValue, double gamma = 2.4)
+ {
+ return signalValue <= 0.0031308 ? signalValue * 12.92 : 1.055 * Math.Pow(signalValue, 1.0 / gamma) - 0.055;
+ }
+
+ public static double RgbToLinear(double signalValue, double gamma = 2.4)
+ {
+ return signalValue <= 0.04045 ? signalValue / 12.92 : Math.Pow((signalValue + 0.055) / 1.055, gamma);
+ }
+
+}
diff --git a/MHC2Gen/Extension.cs b/MHC2Gen/Extension.cs
new file mode 100644
index 0000000..44c948f
--- /dev/null
+++ b/MHC2Gen/Extension.cs
@@ -0,0 +1,24 @@
+using LittleCms;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MHC2Gen
+{
+ internal static class Extension
+ {
+
+ public static CIExy ToXY(in this CIExyY xyY)
+ {
+ return new() { x = xyY.x, y = xyY.y };
+ }
+ public static CIExy ToXY(in this CIEXYZ xyz)
+ {
+ return xyz.ToCIExyY().ToXY();
+ }
+
+
+ }
+}
diff --git a/MHC2Gen/GenerateProfileCommand.cs b/MHC2Gen/GenerateProfileCommand.cs
new file mode 100644
index 0000000..ca3f291
--- /dev/null
+++ b/MHC2Gen/GenerateProfileCommand.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MHC2Gen
+{
+ internal class ST2084
+ {
+ private const double m1 = 2610.0 / 16384.0;
+ private const double m2 = 128.0 * 2523.0 / 4096.0;
+ private const double c1 = 3424.0 / 4096.0;
+ private const double c2 = 32.0 * 2413.0 / 4096.0;
+ private const double c3 = 32.0 * 2392.0 / 4096.0;
+
+ public static double NitsToSignal(double nits)
+ {
+ var Ypowm1 = Math.Pow(nits / 10000.0, m1);
+ return Math.Pow((c1 + c2 * Ypowm1) / (1 + c3 * Ypowm1), m2);
+ }
+
+ public static double SignalToNits(double signal)
+ {
+ var Epow1divm2 = Math.Pow(signal, 1.0 / m2);
+ return 10000 * Math.Pow(Math.Max(Epow1divm2 - c1, 0) / (c2 - c3 * Epow1divm2), 1 / m1);
+ }
+ }
+}
diff --git a/MHC2Gen/IccContext.cs b/MHC2Gen/IccContext.cs
new file mode 100644
index 0000000..068abc4
--- /dev/null
+++ b/MHC2Gen/IccContext.cs
@@ -0,0 +1,1134 @@
+using LittleCms;
+using LittleCms.Data;
+using MathNet.Numerics.LinearAlgebra;
+using MathNet.Numerics.LinearAlgebra.Double;
+using System;
+using System.Buffers.Binary;
+using System.IO;
+
+namespace MHC2Gen
+{
+ class MHC2Tag
+ {
+ public const TagSignature Signature = (TagSignature)0x4D484332;
+
+ public double MinCLL { get; set; }
+ public double MaxCLL { get; set; }
+ public double[,]? Matrix3x4 { get; set; }
+ public double[,]? RegammaLUT { get; set; }
+
+
+ public MHC2Tag() { }
+
+ public MHC2Tag(ReadOnlySpan bytes)
+ {
+ LoadFromBytes(bytes);
+ }
+
+ private void LoadFromBytes(ReadOnlySpan bytes)
+ {
+ var ms0 = new MemoryStream(bytes.ToArray());
+ var reader = new BinaryReader(ms0);
+
+ // Signature
+ reader.ReadBytes(4);
+ // 0
+ reader.ReadBytes(4);
+
+ // Lut Size
+ var lutSize = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4));
+
+ MinCLL = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)));
+ MaxCLL = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)));
+
+ // Matrix offset
+ var maxtrixOffset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4));
+
+ // Lut0 offset
+ var lut0Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4));
+
+ // Lut1 offset
+ var lut1Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4));
+
+ // Lut2 offset
+ var lut2Offset = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4));
+
+ Matrix3x4 = new double[3, 4];
+
+ for (var x = 0; x < 3; x++)
+ {
+ for (var y = 0; y < 4; y++)
+ {
+ Matrix3x4[x, y] = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)));
+ }
+ }
+
+ RegammaLUT = new double[3, lutSize];
+
+ for (var c = 0; c < 3; c++)
+ {
+ var discard = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(8));
+
+ for (var x = 0; x < lutSize; x++)
+ {
+ RegammaLUT[c, x] = DecodeS15F16(BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4)));
+ }
+ }
+ }
+
+ public void ApplySdrAcm(double whiteLuminance = 120.0, double blackLuminance = 0.0, double gamma = 2.2, double boostPercentage = 0)
+ {
+ var lutSize = 1024;
+ var amplifier = boostPercentage == 0 ? 1 : boostPercentage > 0 ? 1 + boostPercentage / 100 : 1 - boostPercentage / 100;
+
+ RegammaLUT = new double[3, lutSize];
+
+ for (var i = 0; i < lutSize; i++)
+ {
+ var (_, value) = CmsFunctions.SrgbAcm(i, whiteLuminance, blackLuminance, gamma, lutSize - 1);
+
+ for (var c = 0; c < 3; c++)
+ {
+ RegammaLUT[c, i] = value * amplifier;
+ }
+ }
+ }
+
+ public void ApplyPiecewise()
+ {
+ var lutSize = 1024;
+
+ RegammaLUT = new double[3, lutSize];
+
+ for (var i = 0; i < lutSize; i++)
+ {
+ var value = (double)i / 1023;
+
+ for (var c = 0; c < 3; c++)
+ {
+ RegammaLUT[c, i] = value;
+ }
+ }
+ }
+
+ private static int EncodeS15F16(double value)
+ {
+ var x = (int)Math.Round(value * 65536);
+ return x;
+ }
+
+ private static double DecodeS15F16(int value)
+ {
+ return (double)value / 65536;
+ }
+
+ public byte[] ToBytes()
+ {
+ if (RegammaLUT!.GetLength(0) != 3) throw new ArrayTypeMismatchException();
+ var lut_size = RegammaLUT.GetLength(1);
+ if (lut_size <= 1 || lut_size > 4096) throw new IndexOutOfRangeException();
+ if (Matrix3x4!.Length != 12 || Matrix3x4.GetLength(0) != 3) throw new ArrayTypeMismatchException();
+ var ms0 = new MemoryStream();
+ var writer = new BinaryWriter(ms0);
+ // type
+ writer.Write(BinaryPrimitives.ReverseEndianness((int)Signature));
+ writer.Write(BinaryPrimitives.ReverseEndianness(0));
+
+ writer.Write(BinaryPrimitives.ReverseEndianness(lut_size));
+ writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(MinCLL)));
+ writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(MaxCLL)));
+ // matrix offset
+ writer.Write(BinaryPrimitives.ReverseEndianness(36));
+ // lut0 offset
+ writer.Write(BinaryPrimitives.ReverseEndianness(84));
+ // lut1 offset
+ var lut1_offset = 84 + 8 + lut_size * 4;
+ writer.Write(BinaryPrimitives.ReverseEndianness(lut1_offset));
+ // lut2 offset
+ var lut2_offset = lut1_offset + 8 + lut_size * 4;
+ writer.Write(BinaryPrimitives.ReverseEndianness(lut2_offset));
+
+ foreach (var e in Matrix3x4)
+ {
+ writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(e)));
+ }
+
+ for (int ch = 0; ch < 3; ch++)
+ {
+ writer.Write(new ReadOnlySpan(new byte[] { (byte)'s', (byte)'f', (byte)'3', (byte)'2', 0, 0, 0, 0 }));
+ for (int i = 0; i < lut_size; i++)
+ {
+ writer.Write(BinaryPrimitives.ReverseEndianness(EncodeS15F16(RegammaLUT[ch, i])));
+ }
+ }
+ writer.Flush();
+ return ms0.ToArray();
+ }
+
+ internal static MHC2Tag? LoadFromProfile(IccProfile profile)
+ {
+ var bytes = profile.RawBytes;
+
+ if (bytes == null)
+ {
+ return null;
+ }
+
+ // 0x4D484332
+ var index = -1;
+ var first = true;
+ for (var i = 0; i < bytes.Length; i += 4)
+ {
+ var chunk = bytes.AsSpan(new Range(i, i + 4));
+
+ if (chunk[0] == 77 && chunk[1] == 72 && chunk[2] == 67 && chunk[3] == 50)
+ {
+ index = i;
+
+ if (first)
+ {
+ index = -1;
+ first = false;
+ continue;
+ }
+ break;
+ }
+ }
+
+ if (index == -1)
+ {
+ return null;
+ }
+
+ var mhc2Bytes = bytes.AsSpan(Range.StartAt(index));
+
+ return new MHC2Tag(mhc2Bytes);
+ }
+ }
+
+ internal class IccContext
+ {
+ protected IccProfile profile;
+ public CIEXYZ IlluminantRelativeWhitePoint { get; }
+ public Matrix? ChromaticAdaptionMatrix { get; }
+ public Matrix? InverseChromaticAdaptionMatrix { get; }
+ public RgbPrimaries ProfilePrimaries { get; }
+ public MHC2Tag? MHC2 { get; set; }
+
+ public IccContext(IccProfile profile)
+ {
+ if (profile.PCS != ColorSpaceSignature.XYZ || profile.ColorSpace != ColorSpaceSignature.Rgb)
+ {
+ throw new CmsException(CmsError.COLORSPACE_CHECK, "ICC profile is not XYZ->RGB");
+ }
+ this.profile = profile;
+ {
+ if (profile.TryReadTag(SafeTagSignature.ChromaticAdaptationTag, out var chad))
+ ChromaticAdaptionMatrix = DenseMatrix.OfArray(chad);
+ InverseChromaticAdaptionMatrix = ChromaticAdaptionMatrix?.Inverse();
+ }
+ (IlluminantRelativeWhitePoint, ProfilePrimaries) = PopulatePrimaries();
+
+ MHC2 = MHC2Tag.LoadFromProfile(profile);
+ }
+
+ private unsafe CIEXYZ GetIlluminantReletiveWhitePoint()
+ {
+ if (profile.TryReadTag(SafeTagSignature.MediaWhitePointTag, out var icc_wtpt))
+ {
+ if (ChromaticAdaptionMatrix == null || profile.HeaderCreator == 0x6170706c /* 'aapl' */)
+ {
+ // for profiels without 'chad' tag and Apple profiles, mediaWhitepointTag is illuminant-relative
+ return icc_wtpt;
+ }
+ else
+ {
+ // ... otherwise it is PCS-relative
+ var pcs_wtpt = icc_wtpt;
+ if (ChromaticAdaptionMatrix != null)
+ {
+ return ApplyInverseChad(pcs_wtpt);
+ }
+ }
+ }
+ if (ChromaticAdaptionMatrix != null)
+ {
+ // no wtpt in icc, sum RGB and reverse chad
+ var pcs_rXYZ = profile.ReadTag(SafeTagSignature.RedColorantTag);
+ var pcs_gXYZ = profile.ReadTag(SafeTagSignature.GreenColorantTag);
+ var pcs_bXYZ = profile.ReadTag(SafeTagSignature.BlueColorantTag);
+ var pcs_sumrgb = pcs_rXYZ + pcs_gXYZ + pcs_bXYZ;
+
+ return ApplyInverseChad(pcs_sumrgb);
+ }
+ else
+ {
+ throw new Exception("malformed profile: missing wtpt and chad");
+ }
+ }
+
+ protected CIEXYZ ApplyInverseChad(in CIEXYZ val)
+ {
+ var vec = InverseChromaticAdaptionMatrix!.Multiply(new DenseVector(new[] { val.X, val.Y, val.Z }));
+ return new() { X = vec[0], Y = vec[1], Z = vec[2] };
+ }
+
+
+ ///
+ /// use lcms transform to get illuminant-relative primaries.
+ ///
+ private unsafe (CIEXYZ, RgbPrimaries) PopulatePrimaries()
+ {
+ var ctx = new CmsContext();
+
+ ctx.SetAdaptionState(0);
+
+ var xyzprof = IccProfile.CreateXYZ();
+ var t = new CmsTransform(ctx, profile, CmsPixelFormat.RGBDouble, xyzprof, CmsPixelFormat.XYZDouble, RenderingIntent.ABSOLUTE_COLORIMETRIC, default);
+ var pixels = new ReadOnlySpan(new double[] {
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1,
+ 1, 1, 1
+ });
+ Span xyz = stackalloc double[3];
+
+
+ t.DoTransform(pixels.Slice(0), xyz, 1);
+ var rXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] };
+ t.DoTransform(pixels.Slice(3), xyz, 1);
+ var gXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] };
+ t.DoTransform(pixels.Slice(6), xyz, 1);
+ var bXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] };
+ t.DoTransform(pixels.Slice(9), xyz, 1);
+ var wXYZ = new CIEXYZ { X = xyz[0], Y = xyz[1], Z = xyz[2] };
+
+
+ return (wXYZ, new(rXYZ.ToXY(), gXYZ.ToXY(), bXYZ.ToXY(), wXYZ.ToXY()));
+
+ }
+
+ public string GetDescription()
+ {
+ return profile.GetInfo(InfoType.Description);
+ }
+
+ public CIEXYZ GetIlluminantRelativeBlackPoint()
+ {
+ // NOTE: mediaBlackPointTag is no longer in ICC standard
+ if (profile.TryReadTag(SafeTagSignature.MediaBlackPointTag, out var bkpt))
+ {
+ // no chad in profile, bkpt is illuminant-relative
+ if (ChromaticAdaptionMatrix == null)
+ {
+ return bkpt;
+ }
+ else
+ {
+ return ApplyInverseChad(bkpt);
+ }
+ }
+
+ // no bkpt in tag, use lcms transform
+ var ctx = new CmsContext();
+ ctx.SetAdaptionState(0);
+ var t = new CmsTransform(ctx, profile, CmsPixelFormat.RGB8, IccProfile.CreateXYZ(), CmsPixelFormat.XYZDouble, RenderingIntent.ABSOLUTE_COLORIMETRIC, default);
+ var input = new ReadOnlySpan(new byte[] { 0, 0, 0 });
+ Span outbuf = stackalloc double[3];
+ t.DoTransform(input, outbuf, 1);
+ bkpt = new CIEXYZ { X = outbuf[0], Y = outbuf[1], Z = outbuf[2] };
+ return bkpt;
+ }
+
+ public void WriteIlluminantRelativeMediaBlackPoint(in CIEXYZ value)
+ {
+ CIEXYZ valueToWrite;
+ if (ChromaticAdaptionMatrix != null)
+ {
+ var vec = new DenseVector(new double[] { value.X, value.Y, value.Z });
+ var pcs_vec = ChromaticAdaptionMatrix * vec;
+ valueToWrite = new() { X = pcs_vec[0], Y = pcs_vec[1], Z = pcs_vec[2] };
+ }
+ else
+ {
+ valueToWrite = value;
+ }
+ profile.WriteTag(SafeTagSignature.MediaBlackPointTag, valueToWrite);
+ }
+
+ public static Matrix GetChromaticAdaptationMatrix(CIEXYZ sourceIlluminant, CIEXYZ targetIlluminant)
+ {
+ // http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
+ // Bradford
+ var M_a = DenseMatrix.OfArray(new[,] {
+ { 0.8951, 0.2664, -0.1614 },
+ { -0.7502, 1.7135, 0.0367 },
+ { 0.0389, -0.0685, 1.0296 },
+ });
+
+ var M_a_inv = M_a.Inverse();
+
+ var cone_s_vec = M_a * new DenseVector(new double[] { sourceIlluminant.X, sourceIlluminant.Y, sourceIlluminant.Z });
+ var cone_t_vec = M_a * new DenseVector(new double[] { targetIlluminant.X, targetIlluminant.Y, targetIlluminant.Z });
+
+ var M = M_a_inv * DenseMatrix.Build.DenseOfDiagonalVector(cone_t_vec / cone_s_vec) * M_a;
+
+ return M;
+ }
+ }
+
+ internal class DeviceIccContext : IccContext
+ {
+ CIEXYZ illuminantRelativeBlackPoint;
+ double min_nits;
+ double max_nits;
+ ToneCurve profileRedToneCurve;
+ ToneCurve profileGreenToneCurve;
+ ToneCurve profileBlueToneCurve;
+ ToneCurve profileRedReverseToneCurve;
+ ToneCurve profileGreenReverseToneCurve;
+ ToneCurve profileBlueReverseToneCurve;
+
+ public bool UseChromaticAdaptation { get; set; }
+
+ public DeviceIccContext(IccProfile profile) : base(profile)
+ {
+ illuminantRelativeBlackPoint = GetIlluminantRelativeBlackPoint();
+ (max_nits, min_nits) = GetProfileLuminance();
+ profileRedToneCurve = profile.ReadTag(SafeTagSignature.RedTRCTag);
+ profileGreenToneCurve = profile.ReadTag(SafeTagSignature.GreenTRCTag);
+ profileBlueToneCurve = profile.ReadTag(SafeTagSignature.BlueTRCTag);
+ profileRedReverseToneCurve = profileRedToneCurve.Reverse();
+ profileGreenReverseToneCurve = profileGreenToneCurve.Reverse();
+ profileBlueReverseToneCurve = profileBlueToneCurve.Reverse();
+ }
+
+ public static Matrix RgbToXYZ(RgbPrimaries primaries)
+ {
+ var rXYZ = primaries.Red.ToXYZ();
+ var gXYZ = primaries.Green.ToXYZ();
+ var bXYZ = primaries.Blue.ToXYZ();
+ var wXYZ = primaries.White.ToXYZ();
+
+ var S = DenseMatrix.OfArray(new[,] {
+ {rXYZ.X, gXYZ.X, bXYZ.X},
+ {rXYZ.Y, gXYZ.Y, bXYZ.Y},
+ {rXYZ.Z, gXYZ.Z, bXYZ.Z},
+ }).Inverse().Multiply(DenseMatrix.OfArray(new[,] { { wXYZ.X }, { wXYZ.Y }, { wXYZ.Z } }));
+
+ var M = DenseMatrix.OfArray(new[,] {
+ {S[0,0] * rXYZ.X, S[1,0]*gXYZ.X, S[2,0]*bXYZ.X },
+ {S[0,0] * rXYZ.Y, S[1,0]*gXYZ.Y, S[2,0]*bXYZ.Y },
+ {S[0,0] * rXYZ.Z, S[1,0]*gXYZ.Z, S[2,0]*bXYZ.Z },
+ });
+ return M;
+ }
+
+ public static Matrix XYZToRgb(RgbPrimaries primaries) => RgbToXYZ(primaries).Inverse();
+
+ public static Matrix RgbToRgb(RgbPrimaries from, RgbPrimaries to)
+ {
+ var M1 = RgbToXYZ(from);
+ var M2 = XYZToRgb(to);
+ return M2 * M1;
+ }
+
+ public string GetDeviceDescription()
+ {
+ var model = profile.GetInfo(InfoType.Model);
+ if (!string.IsNullOrEmpty(model)) return model;
+ var desc = GetDescription();
+ if (!string.IsNullOrEmpty(desc)) return desc;
+ return "";
+ }
+
+ private (double MaxNits, double MinNits) GetProfileLuminance()
+ {
+ var wtpt = IlluminantRelativeWhitePoint;
+ double max_nits = 80;
+ if (profile.TryReadTag(SafeTagSignature.LuminanceTag, out var lumi))
+ {
+ max_nits = lumi.Y;
+ }
+ var min_nits = 0.005;
+ var bkpt = illuminantRelativeBlackPoint;
+ if (bkpt.Y != 0)
+ {
+ var bkpt_scale = bkpt.Y / wtpt.Y;
+ min_nits = max_nits * bkpt_scale;
+ }
+ return (max_nits, min_nits);
+ }
+
+ public IccProfile CreateMhc2CscIcc(RgbPrimaries? sourcePrimaries = null, string sourceDescription = "sRGB")
+ {
+ var wtpt = IlluminantRelativeWhitePoint;
+ var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray();
+
+ var devicePrimaries = ProfilePrimaries;
+
+ var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve };
+
+ var srgbTrc = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag)!;
+ var sourceEotf = new ToneCurve[] { srgbTrc, srgbTrc, srgbTrc };
+
+ sourcePrimaries ??= RgbPrimaries.sRGB;
+
+ var srgb_to_xyz = RgbToXYZ(RgbPrimaries.sRGB);
+ var xyz_to_srgb = XYZToRgb(RgbPrimaries.sRGB);
+
+
+ Matrix user_matrix = DenseMatrix.CreateIdentity(3);
+
+ // pipeline here: input signal converted to XYZ (interpreted as sRGB)
+
+ if (!ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB))
+ {
+ user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix;
+ }
+
+ // pipeline here: input signal converted to XYZ (interpreted as custom RGB)
+
+ if (UseChromaticAdaptation)
+ {
+ user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix;
+ }
+
+ // pipeline here: input signal XYZ adapted to device white point
+
+ // hook: scale white point
+
+ var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 });
+ var mapped_y = source_white_to_xyz[1];
+ var profile_max_nits = max_nits * (mapped_y / wtpt.Y);
+
+ // end hook
+
+ user_matrix = XYZToRgb(devicePrimaries) * user_matrix;
+
+ // pipeline here: linear device RGB
+
+ // hack: eliminate fixed sRGB to XYZ transform
+
+ user_matrix = srgb_to_xyz * user_matrix;
+
+ var mhc2_matrix = new double[,] {
+ { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 },
+ { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 },
+ { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 },
+ };
+
+ double[,] mhc2_lut;
+ if (vcgt != null)
+ {
+ var lut_size = 1024;
+ mhc2_lut = new double[3, lut_size];
+ for (int ch = 0; ch < 3; ch++)
+ {
+ for (int iinput = 0; iinput < lut_size; iinput++)
+ {
+ var input = (float)iinput / (lut_size - 1);
+ var linear = sourceEotf[ch].EvalF32(input);
+ var dev_output = deviceOetf[ch].EvalF32(linear);
+ if (vcgt != null)
+ {
+ dev_output = vcgt[ch].EvalF32(dev_output);
+ }
+ mhc2_lut[ch, iinput] = dev_output;
+ }
+ }
+ }
+ else
+ {
+ mhc2_lut = new double[,]
+ {
+ { 0, 1 },
+ { 0, 1 },
+ { 0, 1 },
+ };
+ }
+
+ var mhc2d = new MHC2Tag
+ {
+ MinCLL = min_nits,
+ MaxCLL = profile_max_nits,
+ Matrix3x4 = mhc2_matrix,
+ RegammaLUT = mhc2_lut
+ };
+
+ var mhc2 = mhc2d.ToBytes();
+
+ var outputProfile = IccProfile.CreateRGB(sourcePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE
+ {
+ Red = sourcePrimaries.Red.ToXYZ().ToCIExyY(),
+ Green = sourcePrimaries.Green.ToXYZ().ToCIExyY(),
+ Blue = sourcePrimaries.Blue.ToXYZ().ToCIExyY()
+ }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc));
+
+ outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits });
+
+ var outctx = new IccContext(outputProfile);
+ outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint);
+
+ // copy device description from device profile
+ var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag };
+
+ unsafe
+ {
+ foreach (var tag in copy_tags)
+ {
+ var tag_ptr = profile.ReadTag(tag);
+ if (tag_ptr != null)
+ {
+ outputProfile.WriteTag(tag, tag_ptr);
+ }
+ }
+ }
+
+ // set output profile description
+ outputProfile.HeaderManufacturer = profile.HeaderManufacturer;
+ outputProfile.HeaderModel = profile.HeaderModel;
+ outputProfile.HeaderAttributes = profile.HeaderAttributes;
+ outputProfile.HeaderRenderingIntent = RenderingIntent.ABSOLUTE_COLORIMETRIC;
+
+ var new_desc = $"CSC: {sourceDescription} ({GetDeviceDescription()})";
+ var new_desc_mlu = new MLU(new_desc);
+ outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu);
+
+ outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2);
+
+ outputProfile.ComputeProfileId();
+
+ return outputProfile;
+ }
+
+ public IccProfile CreatePQ10DecodeIcc(double? maxBrightnessOverride = null, double? minBrightnessOverride = null)
+ {
+ var sourcePrimaries = RgbPrimaries.Rec2020;
+ var devicePrimaries = ProfilePrimaries;
+
+ Matrix user_matrix = DenseMatrix.CreateIdentity(3);
+
+
+ if (UseChromaticAdaptation)
+ {
+ user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix;
+ }
+
+
+ // var rgb_transform = RgbToRgb(sourcePrimaries, devicePrimaries);
+ // rgb_transform = XYZToRgb(devicePrimaries) * RgbToXYZ(sourcePrimaries);
+ // var xyz_transform = RgbToXYZ(sourcePrimaries) * rgb_transform * XYZToRgb(sourcePrimaries);
+ user_matrix = RgbToXYZ(sourcePrimaries) * XYZToRgb(devicePrimaries) * user_matrix;
+
+ var mhc2_matrix = new double[,] {
+ { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 },
+ { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 },
+ { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 },
+ };
+
+ var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray();
+ var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve };
+
+ var use_max_nits = maxBrightnessOverride ?? max_nits;
+ var use_min_nits = minBrightnessOverride ?? min_nits;
+
+ var lut_size = 4096;
+ var mhc2_lut = new double[3, 4096];
+ for (int ch = 0; ch < 3; ch++)
+ {
+ for (int iinput = 0; iinput < lut_size; iinput++)
+ {
+ var pqinput = (double)iinput / (lut_size - 1);
+ var nits = ST2084.SignalToNits(pqinput);
+ var linear = Math.Max(nits - use_min_nits, 0) / (use_max_nits - use_min_nits);
+ var dev_output = deviceOetf[ch].EvalF32((float)linear);
+ if (vcgt != null)
+ {
+ dev_output = vcgt[ch].EvalF32(dev_output);
+ }
+ // Console.WriteLine($"Channel {ch}: PQ {iinput} -> {nits} cd/m2 -> SDR {dev_output * 255}");
+ mhc2_lut[ch, iinput] = dev_output;
+ }
+ }
+
+ var mhc2d = new MHC2Tag
+ {
+ MinCLL = use_min_nits,
+ MaxCLL = use_max_nits,
+ Matrix3x4 = mhc2_matrix,
+ RegammaLUT = mhc2_lut
+ };
+
+ var mhc2 = mhc2d.ToBytes();
+
+ var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE
+ {
+ Red = devicePrimaries.Red.ToXYZ().ToCIExyY(),
+ Green = devicePrimaries.Green.ToXYZ().ToCIExyY(),
+ Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY()
+ }, new RgbToneCurve(profileRedToneCurve, profileGreenToneCurve, profileBlueToneCurve));
+
+ // copy characteristics from device profile
+ var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag };
+ unsafe
+ {
+ foreach (var tag in copy_tags)
+ {
+ var tag_ptr = profile.ReadTag(tag);
+ if (tag_ptr != null)
+ {
+ outputProfile.WriteTag(tag, tag_ptr);
+ }
+ }
+ }
+
+ outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = use_max_nits });
+
+ // the profile is not read by regular applications
+ // var outctx = new IccContext(outputProfile);
+ // outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint);
+
+ // set output profile description
+ outputProfile.HeaderManufacturer = profile.HeaderManufacturer;
+ outputProfile.HeaderModel = profile.HeaderModel;
+ outputProfile.HeaderAttributes = profile.HeaderAttributes;
+ outputProfile.HeaderRenderingIntent = RenderingIntent.ABSOLUTE_COLORIMETRIC;
+
+ var new_desc = $"CSC: HDR10 to SDR ({GetDeviceDescription()}, {use_max_nits:0} nits)";
+ var new_desc_mlu = new MLU(new_desc);
+ outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu);
+
+ outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2);
+
+ outputProfile.ComputeProfileId();
+
+ return outputProfile;
+ }
+
+ public IccProfile CreateSdrAcmIcc(bool calibrateTransfer)
+ {
+ Matrix user_matrix = DenseMatrix.CreateIdentity(3);
+
+ if (UseChromaticAdaptation)
+ {
+ user_matrix = GetChromaticAdaptationMatrix(new CIExy { x = 0.3127, y = 0.3290 }.ToXYZ(), ProfilePrimaries.White.ToXYZ()) * user_matrix;
+ }
+
+ var mhc2_matrix = new double[,] {
+ { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 },
+ { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 },
+ { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 },
+ };
+
+ double[,] mhc2_lut;
+
+ var outputprofileTrc = new RgbToneCurve(profileRedToneCurve, profileGreenToneCurve, profileBlueToneCurve);
+ var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray();
+
+ if (calibrateTransfer)
+ {
+ var sourceEotf = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag);
+ outputprofileTrc = new RgbToneCurve(sourceEotf, sourceEotf, sourceEotf);
+
+ var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve };
+ var lut_size = 1024;
+ mhc2_lut = new double[3, lut_size];
+ for (int ch = 0; ch < 3; ch++)
+ {
+ for (int iinput = 0; iinput < lut_size; iinput++)
+ {
+ var input = (float)iinput / (lut_size - 1);
+ var linear = sourceEotf.EvalF32(input);
+ var dev_output = deviceOetf[ch].EvalF32(linear);
+ if (vcgt != null)
+ {
+ dev_output = vcgt[ch].EvalF32(dev_output);
+ }
+ mhc2_lut[ch, iinput] = dev_output;
+ }
+
+ }
+ }
+ else if (vcgt != null)
+ {
+ // move vcgt to mhc2 only
+ var lut_size = 1024;
+ mhc2_lut = new double[3, lut_size];
+ for (int ch = 0; ch < 3; ch++)
+ {
+ for (int iinput = 0; iinput < lut_size; iinput++)
+ {
+ var input = (float)iinput / (lut_size - 1);
+ var dev_output = vcgt[ch].EvalF32(input);
+ mhc2_lut[ch, iinput] = dev_output;
+ }
+ }
+ }
+ else
+ {
+ mhc2_lut = new double[,] { { 0, 1 }, { 0, 1 }, { 0, 1 } };
+ }
+
+
+ var mhc2d = new MHC2Tag
+ {
+ MinCLL = min_nits,
+ MaxCLL = max_nits,
+ Matrix3x4 = mhc2_matrix,
+ RegammaLUT = mhc2_lut
+ };
+
+ var mhc2 = mhc2d.ToBytes();
+
+ var devicePrimaries = ProfilePrimaries;
+ var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE
+ {
+ Red = devicePrimaries.Red.ToXYZ().ToCIExyY(),
+ Green = devicePrimaries.Green.ToXYZ().ToCIExyY(),
+ Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY()
+ }, outputprofileTrc);
+
+ // copy characteristics from device profile
+ var copy_tags = new TagSignature[] {
+ TagSignature.LuminanceTag,
+ TagSignature.DeviceMfgDescTag,
+ TagSignature.DeviceModelDescTag,
+ };
+
+ unsafe
+ {
+ foreach (var tag in copy_tags)
+ {
+ var tag_ptr = profile.ReadTag(tag);
+ outputProfile.WriteTag(tag, tag_ptr);
+ }
+ }
+
+ // the profile is not read by regular applications
+ //var outctx = new IccContext(outputProfile);
+ //outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint);
+
+ // SDR ACM will not work if the profile has negative XYZ colorants (possibly due to limited precision)
+ foreach (var sig in new ISafeTagSignature[] { SafeTagSignature.RedColorantTag, SafeTagSignature.GreenColorantTag, SafeTagSignature.BlueColorantTag })
+ {
+ var xyz = outputProfile.ReadTag(sig);
+ if (xyz.X < 0) xyz.X = 0;
+ if (xyz.Y < 0) xyz.Y = 0;
+ if (xyz.Z < 0) xyz.Z = 0;
+ outputProfile.WriteTag(sig, xyz);
+ }
+
+ // set output profile description
+
+ outputProfile.HeaderManufacturer = profile.HeaderManufacturer;
+ outputProfile.HeaderModel = profile.HeaderModel;
+ outputProfile.HeaderAttributes = profile.HeaderAttributes;
+ outputProfile.HeaderRenderingIntent = profile.HeaderRenderingIntent;
+
+ // outputProfile.ProfileVersion = profile.ProfileVersion;
+
+ var new_desc = $"SDR ACM: {profile.GetInfo(InfoType.Description)}";
+ var new_desc_mlu = new MLU(new_desc);
+ outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu);
+
+ outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2);
+
+ outputProfile.ComputeProfileId();
+
+ return outputProfile;
+ }
+
+ public IccProfile CreateIcc(GenerateProfileCommand command)
+ {
+ var maxNits = command.WhiteLuminance;
+
+ //var wtpt = IlluminantRelativeWhitePoint;
+
+ var devicePrimaries = command.DevicePrimaries; // new RgbPrimaries(new() { x = 0.698, y = 0.292 }, new() { x = 0.255, y = 0.699 }, new() { x = 0.148, y = 0.056 }, new() { x = 0.3127, y = 0.3290 });
+
+ var srgbTrc = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag)!;
+
+ var sourcePrimaries = RgbPrimaries.sRGB;
+
+ var xyz_to_srgb = XYZToRgb(sourcePrimaries);
+
+ Matrix user_matrix = DenseMatrix.CreateIdentity(3);
+
+ // pipeline here: input signal converted to XYZ (interpreted as sRGB)
+
+ if (!ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB))
+ {
+ user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix;
+ }
+
+ // pipeline here: input signal converted to XYZ (interpreted as custom RGB)
+
+ if (UseChromaticAdaptation)
+ {
+ user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix;
+ }
+
+ // pipeline here: input signal XYZ adapted to device white point
+
+ // hook: scale white point
+
+ //var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 });
+ //var mapped_y = source_white_to_xyz[1];
+ //var profile_max_nits = max_nits * (mapped_y / wtpt.Y);
+ var profile_max_nits = maxNits;
+
+ // end hook
+
+ // pipeline here: linear device RGB
+
+ // hack: eliminate fixed sRGB to XYZ transform
+
+ // CSC
+ var targetPrimaries = command.ColorGamut switch
+ {
+ ColorGamut.Native => devicePrimaries,
+ ColorGamut.sRGB => RgbPrimaries.sRGB,
+ ColorGamut.P3 => RgbPrimaries.P3D65,
+ ColorGamut.Rec2020 => RgbPrimaries.Rec2020,
+ ColorGamut.AdobeRGB => RgbPrimaries.AdobeRGB,
+ _ => devicePrimaries
+ };
+
+ var srgb_to_xyz = RgbToXYZ(targetPrimaries);
+ //var xyz_to_srgb = XYZToRgb(RgbPrimaries.sRGB);
+
+ user_matrix = XYZToRgb(devicePrimaries) * user_matrix;
+
+ user_matrix = srgb_to_xyz * user_matrix;
+
+ var mhc2_matrix = new double[,] {
+ { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 },
+ { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 },
+ { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 },
+ };
+
+ MHC2 = new MHC2Tag
+ {
+ MinCLL = command.MinCLL,
+ MaxCLL = command.MaxCLL
+ };
+
+ if (command.SDRTransferFunction == SDRTransferFunction.Piecewise)
+ {
+ MHC2.ApplyPiecewise();
+ }
+ else if (command.SDRTransferFunction == SDRTransferFunction.PurePower)
+ {
+ MHC2.ApplySdrAcm(command.SDRMaxBrightness, command.SDRMinBrightness, command.Gamma, command.SDRBrightnessBoost);
+ }
+ else if (command.SDRTransferFunction == SDRTransferFunction.BT_1886)
+ {
+ MHC2.ApplySdrAcm(120, 0.03, 2.4);
+ }
+
+ MHC2.Matrix3x4 = mhc2_matrix;
+
+ var outputProfile = IccProfile.CreateRGB(devicePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE
+ {
+ Red = devicePrimaries.Red.ToXYZ().ToCIExyY(),
+ Green = devicePrimaries.Green.ToXYZ().ToCIExyY(),
+ Blue = devicePrimaries.Blue.ToXYZ().ToCIExyY()
+ }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc));
+
+ outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits });
+
+ var outctx = new IccContext(outputProfile);
+ outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint);
+
+ // copy device description from device profile
+ var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag };
+
+ unsafe
+ {
+ foreach (var tag in copy_tags)
+ {
+ var tag_ptr = profile.ReadTag(tag);
+ if (tag_ptr != null)
+ {
+ outputProfile.WriteTag(tag, tag_ptr);
+ }
+ }
+ }
+
+ // set output profile description
+ outputProfile.HeaderManufacturer = profile.HeaderManufacturer;
+ outputProfile.HeaderModel = profile.HeaderModel;
+ outputProfile.HeaderAttributes = profile.HeaderAttributes;
+ outputProfile.HeaderRenderingIntent = RenderingIntent.PERCEPTUAL;
+
+ var new_desc = $"{command.Description} ({GetDeviceDescription()})";
+ var new_desc_mlu = new MLU(new_desc);
+ outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu);
+
+ outputProfile.WriteRawTag(MHC2Tag.Signature, MHC2.ToBytes());
+
+ outputProfile.ComputeProfileId();
+
+ return outputProfile;
+ }
+
+ public IccProfile CreateCscIcc(RgbPrimaries? sourcePrimaries = null, string sourceDescription = "sRGB")
+ {
+ var wtpt = IlluminantRelativeWhitePoint;
+ var vcgt = profile.ReadTagOrDefault(SafeTagSignature.VcgtTag)?.ToArray();
+
+ var customPrimaries = new RgbPrimaries(new() { x = 0.698, y = 0.292 }, new() { x = 0.255, y = 0.699 }, new() { x = 0.148, y = 0.056 }, new() { x = 0.3127, y = 0.3290 });
+
+ var devicePrimaries = customPrimaries;
+
+ var deviceOetf = new ToneCurve[] { profileRedReverseToneCurve, profileGreenReverseToneCurve, profileBlueReverseToneCurve };
+
+ var srgbTrc = IccProfile.Create_sRGB().ReadTag(SafeTagSignature.RedTRCTag)!;
+ var sourceEotf = new ToneCurve[] { srgbTrc, srgbTrc, srgbTrc };
+
+ sourcePrimaries ??= RgbPrimaries.sRGB;
+
+ var srgb_to_xyz = RgbToXYZ(RgbPrimaries.sRGB);
+ var xyz_to_srgb = XYZToRgb(RgbPrimaries.sRGB);
+
+
+ Matrix user_matrix = DenseMatrix.CreateIdentity(3);
+
+ // pipeline here: input signal converted to XYZ (interpreted as sRGB)
+
+ if (!ReferenceEquals(sourcePrimaries, RgbPrimaries.sRGB))
+ {
+ user_matrix = RgbToXYZ(sourcePrimaries) * xyz_to_srgb * user_matrix;
+ }
+
+ // pipeline here: input signal converted to XYZ (interpreted as custom RGB)
+
+ if (UseChromaticAdaptation)
+ {
+ user_matrix = GetChromaticAdaptationMatrix(sourcePrimaries.White.ToXYZ(), devicePrimaries.White.ToXYZ()) * user_matrix;
+ }
+
+ // pipeline here: input signal XYZ adapted to device white point
+
+ // hook: scale white point
+
+ var source_white_to_xyz = user_matrix * new DenseVector(new double[] { 1, 1, 1 });
+ var mapped_y = source_white_to_xyz[1];
+ var profile_max_nits = max_nits * (mapped_y / wtpt.Y);
+
+ // end hook
+
+ user_matrix = XYZToRgb(devicePrimaries) * user_matrix;
+
+ // pipeline here: linear device RGB
+
+ // hack: eliminate fixed sRGB to XYZ transform
+
+ user_matrix = srgb_to_xyz * user_matrix;
+
+ var mhc2_matrix = new double[,] {
+ { user_matrix[0,0], user_matrix[0,1], user_matrix[0,2], 0 },
+ { user_matrix[1,0], user_matrix[1,1], user_matrix[1,2], 0 },
+ { user_matrix[2,0], user_matrix[2,1], user_matrix[2,2], 0 },
+ };
+
+ double[,] mhc2_lut;
+ if (vcgt != null)
+ {
+ var lut_size = 1024;
+ mhc2_lut = new double[3, lut_size];
+ for (int ch = 0; ch < 3; ch++)
+ {
+ for (int iinput = 0; iinput < lut_size; iinput++)
+ {
+ var input = (float)iinput / (lut_size - 1);
+ var linear = sourceEotf[ch].EvalF32(input);
+ var dev_output = deviceOetf[ch].EvalF32(linear);
+ if (vcgt != null)
+ {
+ dev_output = vcgt[ch].EvalF32(dev_output);
+ }
+ mhc2_lut[ch, iinput] = dev_output;
+ }
+ }
+ }
+ else
+ {
+ var lut_size = 1024;
+ mhc2_lut = new double[3, lut_size];
+ for (int ch = 0; ch < 3; ch++)
+ {
+ for (int iinput = 0; iinput < lut_size; iinput++)
+ {
+ var input = (float)iinput / (lut_size - 1);
+ var linear = sourceEotf[ch].EvalF32(input);
+
+ var dev_output = CmsFunctions.RgbToLinear(linear, 1.6);
+
+ //var dev_output = deviceOetf[ch].EvalF32(input);
+ //if (vcgt != null)
+ //{
+ // dev_output = vcgt[ch].EvalF32(dev_output);
+ //}
+ mhc2_lut[ch, iinput] = dev_output;
+ }
+ }
+ }
+
+ var mhc2d = new MHC2Tag
+ {
+ MinCLL = min_nits,
+ MaxCLL = profile_max_nits,
+ Matrix3x4 = mhc2_matrix,
+ RegammaLUT = mhc2_lut
+ };
+
+ var mhc2 = mhc2d.ToBytes();
+
+ var outputProfile = IccProfile.CreateRGB(sourcePrimaries.White.ToXYZ().ToCIExyY(), new CIExyYTRIPLE
+ {
+ Red = sourcePrimaries.Red.ToXYZ().ToCIExyY(),
+ Green = sourcePrimaries.Green.ToXYZ().ToCIExyY(),
+ Blue = sourcePrimaries.Blue.ToXYZ().ToCIExyY()
+ }, new RgbToneCurve(srgbTrc, srgbTrc, srgbTrc));
+
+ outputProfile.WriteTag(SafeTagSignature.LuminanceTag, new CIEXYZ { Y = profile_max_nits });
+
+ var outctx = new IccContext(outputProfile);
+ outctx.WriteIlluminantRelativeMediaBlackPoint(illuminantRelativeBlackPoint);
+
+ // copy device description from device profile
+ var copy_tags = new TagSignature[] { TagSignature.DeviceMfgDescTag, TagSignature.DeviceModelDescTag };
+
+ unsafe
+ {
+ foreach (var tag in copy_tags)
+ {
+ var tag_ptr = profile.ReadTag(tag);
+ if (tag_ptr != null)
+ {
+ outputProfile.WriteTag(tag, tag_ptr);
+ }
+ }
+ }
+
+ // set output profile description
+ outputProfile.HeaderManufacturer = profile.HeaderManufacturer;
+ outputProfile.HeaderModel = profile.HeaderModel;
+ outputProfile.HeaderAttributes = profile.HeaderAttributes;
+ outputProfile.HeaderRenderingIntent = RenderingIntent.ABSOLUTE_COLORIMETRIC;
+
+ var new_desc = $"XCSC: {sourceDescription} ({GetDeviceDescription()})";
+ var new_desc_mlu = new MLU(new_desc);
+ outputProfile.WriteTag(SafeTagSignature.ProfileDescriptionTag, new_desc_mlu);
+
+ outputProfile.WriteRawTag(MHC2Tag.Signature, mhc2);
+
+ outputProfile.ComputeProfileId();
+
+ return outputProfile;
+ }
+
+ }
+}
diff --git a/MHC2Gen/MHC2Gen.csproj b/MHC2Gen/MHC2Gen.csproj
new file mode 100644
index 0000000..72f2703
--- /dev/null
+++ b/MHC2Gen/MHC2Gen.csproj
@@ -0,0 +1,55 @@
+
+
+
+ net7.0
+ enable
+ true
+ x64
+
+
+
+
+
+
+
+ net7.0
+ true
+
+
+
+
+ true
+ Size
+ true
+ true
+ true
+ false
+ full
+
+ false
+ false
+ false
+ true
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MHC2Gen/MHC2Wrapper.cs b/MHC2Gen/MHC2Wrapper.cs
new file mode 100644
index 0000000..27c05a6
--- /dev/null
+++ b/MHC2Gen/MHC2Wrapper.cs
@@ -0,0 +1,26 @@
+using LittleCms;
+
+namespace MHC2Gen;
+
+public class MHC2Wrapper
+{
+ public enum NamedGamut
+ {
+ sRGB,
+ AdobeRGB,
+ P3D65,
+ BT2020
+ }
+
+ public static byte[] GenerateSdrAcmProfile(GenerateProfileCommand command)
+ {
+ var profile = IccProfile.Create_sRGB();
+
+ var context = new DeviceIccContext(profile);
+
+ var newProfile = context.CreateIcc(command);
+
+ return newProfile.GetBytes();
+
+ }
+}
diff --git a/MHC2Gen/Primaries.cs b/MHC2Gen/Primaries.cs
new file mode 100644
index 0000000..f7e01a9
--- /dev/null
+++ b/MHC2Gen/Primaries.cs
@@ -0,0 +1,52 @@
+using LittleCms;
+
+namespace MHC2Gen
+{
+ public class CIExy
+ {
+ public double x { get; set; }
+ public double y { get; set; }
+
+ public CIExy() { }
+
+ public CIExy(CIExy source)
+ {
+ x = source.x;
+ y = source.y;
+ }
+
+ public CIEXYZ ToXYZ(double Y = 1.0)
+ {
+ return new() { X = x * Y / y, Y = Y, Z = (1 - x - y) * Y / y };
+ }
+ }
+ public class RgbPrimaries
+ {
+ public CIExy Red { get; }
+ public CIExy Green { get; }
+ public CIExy Blue { get; }
+ public CIExy White { get; }
+
+ public RgbPrimaries(RgbPrimaries primaries)
+ {
+ Red = new CIExy(primaries.Red);
+ Green = new CIExy(primaries.Green);
+ Blue = new CIExy(primaries.Blue);
+ White = new CIExy(primaries.White);
+
+ }
+ public RgbPrimaries(CIExy red, CIExy green, CIExy blue, CIExy white)
+ {
+ Red = red;
+ Green = green;
+ Blue = blue;
+ White = white;
+ }
+
+ public static RgbPrimaries sRGB { get; } = new RgbPrimaries(new() { x = 0.64, y = 0.33 }, new() { x = 0.30, y = 0.60 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
+ public static RgbPrimaries AdobeRGB { get; } = new RgbPrimaries(new() { x = 0.64, y = 0.33 }, new() { x = 0.21, y = 0.71 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
+ public static RgbPrimaries P3D65 { get; } = new RgbPrimaries(new() { x = 0.68, y = 0.32 }, new() { x = 0.265, y = 0.690 }, new() { x = 0.15, y = 0.06 }, new() { x = 0.3127, y = 0.3290 });
+ public static RgbPrimaries Rec2020 { get; } = new RgbPrimaries(new() { x = 0.708, y = 0.292 }, new() { x = 0.170, y = 0.797 }, new() { x = 0.131, y = 0.046 }, new() { x = 0.3127, y = 0.3290 });
+
+ }
+}
diff --git a/MHC2Gen/ST2084.cs b/MHC2Gen/ST2084.cs
new file mode 100644
index 0000000..e86d1a6
--- /dev/null
+++ b/MHC2Gen/ST2084.cs
@@ -0,0 +1,38 @@
+using System.ComponentModel;
+
+namespace MHC2Gen;
+
+public enum SDRTransferFunction
+{
+ [Description("BT.1886")]
+ BT_1886 = 0,
+ [Description("Pure Power")]
+ PurePower = 1,
+ [Description("Piecewise")]
+ Piecewise = 2
+}
+
+public enum ColorGamut
+{
+ Native = 0,
+ sRGB = 1,
+ P3 = 2,
+ Rec2020 = 3,
+ AdobeRGB = 4
+}
+
+public class GenerateProfileCommand
+{
+ public string Description { get; set; }
+ public RgbPrimaries DevicePrimaries { get; set; } = new RgbPrimaries(RgbPrimaries.sRGB);
+ public SDRTransferFunction SDRTransferFunction { get; set; }
+ public ColorGamut ColorGamut { get; set; }
+ public double MinCLL { get; set; }
+ public double MaxCLL { get; set; }
+ public double WhiteLuminance { get; set; } = 2000;
+ public double BlackLuminance { get; set; } = 0;
+ public double SDRMinBrightness { get; set; } = 0;
+ public double SDRMaxBrightness { get; set; } = 100;
+ public double SDRBrightnessBoost { get; set; } = 0;
+ public double Gamma { get; set; } = 2.2;
+}
diff --git a/Shared/Common/Utils.cs b/Shared/Common/Utils.cs
index 8a33cc3..3f62336 100644
--- a/Shared/Common/Utils.cs
+++ b/Shared/Common/Utils.cs
@@ -206,6 +206,11 @@ The best and suggested method to provide this is via a Windows Service. Only whe
return name.Substring(0, 1).ToUpper() + name.Substring(1);
}
+ public static Dictionary EnumToDictionary() where T : struct, Enum
+ {
+ return Enum.GetValues().ToDictionary(k => k, e => e.GetDescription());
+ }
+
public static string GetDescriptionByEnumName(string value) where T : IConvertible
{
string description = null;
diff --git a/Shared/Contracts/NVIDIA/NvColorEnhancementSettings.cs b/Shared/Contracts/NVIDIA/NvColorEnhancementSettings.cs
new file mode 100644
index 0000000..3653cd0
--- /dev/null
+++ b/Shared/Contracts/NVIDIA/NvColorEnhancementSettings.cs
@@ -0,0 +1,26 @@
+namespace ColorControl.Shared.Contracts.NVIDIA;
+
+public class NvColorEnhancementSettings
+{
+ public int DigitalVibranceLevel { get; set; } = 50;
+ public int HueAngle { get; set; }
+
+ public NvColorEnhancementSettings()
+ {
+ }
+
+ public NvColorEnhancementSettings(NvColorEnhancementSettings settings)
+ {
+ settings ??= new NvColorEnhancementSettings();
+
+ DigitalVibranceLevel = settings.DigitalVibranceLevel;
+ HueAngle = settings.HueAngle;
+ }
+
+ public override string ToString()
+ {
+ var value = string.Empty;
+
+ return value;
+ }
+}
diff --git a/Shared/Forms/FormUtils.cs b/Shared/Forms/FormUtils.cs
index 14d976b..48deded 100644
--- a/Shared/Forms/FormUtils.cs
+++ b/Shared/Forms/FormUtils.cs
@@ -250,6 +250,16 @@ namespace ColorControl.Shared.Forms
return item;
}
+ public static void BuildMenuItemAndSub(ToolStripMenuItem parent, string text, string subText, EventHandler onClick)
+ {
+ var itemName = $"{parent.Name}_{text}";
+ var menuItem = BuildMenuItem(parent.DropDownItems, itemName, text);
+
+ var subItemName = $"{itemName}_SubItem";
+ var subItem = BuildMenuItem(menuItem.DropDownItems, subItemName, "", onClick: onClick);
+ subItem.Text = subText;
+ }
+
public static void BuildComboBox(ComboBox comboBox, params T[] skip) where T : IConvertible
{
if (comboBox.Items.Count == 0)
diff --git a/Shared/Native/CCD.cs b/Shared/Native/CCD.cs
index e3e5e0e..7de80bc 100644
--- a/Shared/Native/CCD.cs
+++ b/Shared/Native/CCD.cs
@@ -1,6 +1,7 @@
using ColorControl.Shared.Forms;
using NWin32;
using System.ComponentModel;
+using System.IO;
using System.Runtime.InteropServices;
namespace ColorControl.Shared.Native
@@ -8,7 +9,6 @@ namespace ColorControl.Shared.Native
// This class takes care of wrapping "Connecting and Configuring Displays(CCD) Win32 API"
public static class CCD
{
- private static bool? IsHDRActive = null;
delegate (T, bool) DisplayConfigModeInfoDelegate(DisplayConfigModeInfo modeInfo);
public enum DisplayTopology
@@ -62,66 +62,6 @@ namespace ColorControl.Shared.Native
}
}
- public static void SetHDRState(bool enabled, string displayName = null)
- {
- uint pathCount, modeCount;
-
- var err = NativeMethods.GetDisplayConfigBufferSizes(QueryDisplayFlags.OnlyActivePaths, out pathCount, out modeCount);
- if (err == NativeConstants.ERROR_SUCCESS)
- {
- var pathsArray = new DisplayConfigPathInfo[pathCount];
- var modesArray = new DisplayConfigModeInfo[modeCount];
-
- DisplayConfigTopologyId displayTopology;
-
- err = NativeMethods.QueryDisplayConfig(QueryDisplayFlags.DatabaseCurrent, ref pathCount, pathsArray, ref modeCount, modesArray, out displayTopology);
- if (err == NativeConstants.ERROR_SUCCESS)
- {
- foreach (var path in pathsArray)
- {
- if (!MatchDisplayNames(path, displayName))
- {
- continue;
- }
-
- var setpacket = new DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO();
- setpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
- setpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
- setpacket.header.size = Marshal.SizeOf();
-
- var requestpacket = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO();
- requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
- requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
- requestpacket.header.size = Marshal.SizeOf();
-
- for (int i = 0; i < modeCount; i++)
- {
- if (modesArray[i].infoType == DisplayConfigModeInfoType.Target)
- {
- setpacket.header.adapterId = modesArray[i].adapterId;
- setpacket.header.id = modesArray[i].id;
- requestpacket.header.adapterId = modesArray[i].adapterId;
- requestpacket.header.id = modesArray[i].id;
-
- if (NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket) == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorSupported)
- {
- setpacket.enableAdvancedColor = enabled ? 1U : 0;
- NativeMethods.DisplayConfigSetDeviceInfo(ref setpacket);
-
- IsHDRActive = enabled;
- }
- }
- }
- }
- }
- }
-
- if (err != NativeConstants.ERROR_SUCCESS)
- {
- throw new Win32Exception(err);
- }
- }
-
public static LUID GetAdapterId(string displayName = null)
{
return ExecuteForModeConfig((modeInfo) =>
@@ -164,6 +104,13 @@ namespace ColorControl.Shared.Native
return true;
}
+ public static bool UninstallColorProfile(string profileName)
+ {
+ var name = Path.GetFileName(profileName);
+ return NativeMethods.UninstallColorProfileW(null, name, true);
+ }
+
+
private static bool MinRequiredVersion = true;
public static bool AddDisplayColorProfile(string displayName, string profileName, bool setAsDefault = true)
@@ -386,56 +333,22 @@ namespace ColorControl.Shared.Native
);
}
- private static T ExecuteForModeConfig(DisplayConfigModeInfoDelegate infoDelegate, string displayName = null)
+ public static ColorParams GetColorParams(string displayName = null)
{
- uint pathCount, modeCount;
-
- var err = NativeMethods.GetDisplayConfigBufferSizes(QueryDisplayFlags.OnlyActivePaths, out pathCount, out modeCount);
- if (err == NativeConstants.ERROR_SUCCESS)
+ return ExecuteForModeConfig((modeInfo) =>
{
- var pathsArray = new DisplayConfigPathInfo[pathCount];
- var modesArray = new DisplayConfigModeInfo[modeCount];
-
- DisplayConfigTopologyId displayTopology;
-
- err = NativeMethods.QueryDisplayConfig(QueryDisplayFlags.DatabaseCurrent, ref pathCount, pathsArray, ref modeCount, modesArray, out displayTopology);
- if (err == NativeConstants.ERROR_SUCCESS)
+ if (modeInfo.infoType == DisplayConfigModeInfoType.Source)
{
- foreach (var path in pathsArray)
- {
- if (!MatchDisplayNames(path, displayName))
- {
- continue;
- }
-
- for (int i = 0; i < modeCount; i++)
- {
- var result = infoDelegate(modesArray[i]);
-
- if (!result.Item2)
- {
- return result.Item1;
- }
- }
- }
+ return (GetColorParamsForDisplayConfig(modeInfo), false);
}
- }
- if (err != NativeConstants.ERROR_SUCCESS)
- {
- throw new Win32Exception(err);
- }
-
- return default;
+ return (new ColorParams(), true);
+ }, displayName
+ );
}
- public static bool IsHDREnabled(string displayName = null)
+ private static T ExecuteForModeConfig(DisplayConfigModeInfoDelegate infoDelegate, string displayName = null)
{
- if (IsHDRActive.HasValue)
- {
- return IsHDRActive.Value;
- }
-
uint pathCount, modeCount;
var err = NativeMethods.GetDisplayConfigBufferSizes(QueryDisplayFlags.OnlyActivePaths, out pathCount, out modeCount);
@@ -454,30 +367,116 @@ namespace ColorControl.Shared.Native
continue;
}
- var requestpacket = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO();
- requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
- requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
- requestpacket.header.size = Marshal.SizeOf();
+ var modes = modesArray.Where(m => m.id == path.sourceInfo.id || m.id == path.targetInfo.id);
- for (int i = 0; i < modeCount; i++)
+ foreach (var mode in modes)
{
- if (modesArray[i].infoType == DisplayConfigModeInfoType.Target)
+ var result = infoDelegate(mode);
+
+ if (!result.Item2)
{
- requestpacket.header.adapterId = modesArray[i].adapterId;
- requestpacket.header.id = modesArray[i].id;
-
- err = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket);
-
- IsHDRActive = err == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorEnabled;
-
- return IsHDRActive.Value;
+ return result.Item1;
}
}
}
}
}
- return false;
+ if (err != NativeConstants.ERROR_SUCCESS)
+ {
+ throw new Win32Exception(err);
+ }
+
+ return default;
+ }
+
+ public static void SetHDRState(bool enabled, string displayName = null)
+ {
+ ExecuteForModeConfig((modeInfo) =>
+ {
+ if (modeInfo.infoType == DisplayConfigModeInfoType.Target)
+ {
+ var requestpacket = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO();
+ requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
+ requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
+ requestpacket.header.size = Marshal.SizeOf();
+ requestpacket.header.adapterId = modeInfo.adapterId;
+ requestpacket.header.id = modeInfo.id;
+
+ if (NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket) == NativeConstants.ERROR_SUCCESS && requestpacket.advancedColorSupported)
+ {
+ var setpacket = new DISPLAYCONFIG_SET_ADVANCED_COLOR_INFO();
+ setpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
+ setpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE;
+ setpacket.header.size = Marshal.SizeOf();
+ setpacket.header.adapterId = modeInfo.adapterId;
+ setpacket.header.id = modeInfo.id;
+
+ setpacket.enableAdvancedColor = enabled ? 1U : 0;
+ NativeMethods.DisplayConfigSetDeviceInfo(ref setpacket);
+
+ if (displayName != null)
+ {
+ _IsHDREnabledPerDisplay[displayName] = enabled;
+ }
+ _IsHDREnabledPerDisplay[""] = _IsHDREnabledPerDisplay.Any(kv => kv.Value);
+ }
+
+ return (false, false);
+ }
+
+ return (false, true);
+ }, displayName
+ );
+ }
+
+ public static (bool isSupported, bool isEnabled) GetAdvancedColorSupportedAndEnabled(string displayName = null)
+ {
+ return ExecuteForModeConfig((modeInfo) =>
+ {
+ if (modeInfo.infoType == DisplayConfigModeInfoType.Target)
+ {
+ var requestpacket = new DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO();
+ requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
+ requestpacket.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO;
+ requestpacket.header.size = Marshal.SizeOf();
+ requestpacket.header.adapterId = modeInfo.adapterId;
+ requestpacket.header.id = modeInfo.id;
+
+ var err = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket);
+
+ var result = err == NativeConstants.ERROR_SUCCESS ? (requestpacket.advancedColorSupported, requestpacket.advancedColorEnabled) : (false, false);
+
+ return (result, false);
+ }
+
+ return ((false, false), true);
+ }, displayName
+ );
+ }
+
+ private static Dictionary _IsHDREnabledPerDisplay = new Dictionary();
+
+ public static bool IsHDREnabled(string displayName = null)
+ {
+ if (_IsHDREnabledPerDisplay.Any())
+ {
+ if (displayName == null)
+ {
+ return _IsHDREnabledPerDisplay.Any(kv => kv.Value);
+ }
+
+ if (_IsHDREnabledPerDisplay.TryGetValue(displayName, out var value))
+ {
+ return value;
+ }
+ }
+
+ var isEnabled = GetAdvancedColorSupportedAndEnabled(displayName).isEnabled;
+
+ _IsHDREnabledPerDisplay[displayName ?? ""] = isEnabled;
+
+ return isEnabled;
}
private static bool MatchDisplayNames(DisplayConfigPathInfo path, string displayName)
@@ -528,6 +527,30 @@ namespace ColorControl.Shared.Native
return 0;
}
+ private static ColorParams GetColorParamsForDisplayConfig(DisplayConfigModeInfo displayConfigModeInfo)
+ {
+ var requestpacket = new DISPLAYCONFIG_GET_DISPLAY_INFO();
+ requestpacket.header = new DISPLAYCONFIG_DEVICE_INFO_HEADER();
+ unchecked
+ {
+ requestpacket.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)DISPLAYCONFIG_DEVICE_INFO_GET_DISPLAY_INFO;
+ }
+ requestpacket.header.size = Marshal.SizeOf();
+ requestpacket.header.adapterId = displayConfigModeInfo.adapterId;
+ requestpacket.header.id = displayConfigModeInfo.id;
+
+ var error = NativeMethods.DisplayConfigGetDeviceInfo(ref requestpacket);
+
+ if (error == NativeConstants.ERROR_SUCCESS)
+ {
+ return requestpacket.colorParams;
+ }
+
+ //throw new Win32Exception(error);
+
+ return new ColorParams();
+ }
+
private static bool EqualDisplayNames(string displayName1, string displayName2)
{
displayName1 = displayName1.Replace("\\", string.Empty);
@@ -559,6 +582,8 @@ namespace ColorControl.Shared.Native
DISPLAYCONFIG_DEVICE_INFO_SET_SDR_WHITE_LEVEL = 12,
}
+ private const uint DISPLAYCONFIG_DEVICE_INFO_GET_DISPLAY_INFO = 0xFFFFFFFE;
+
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_DEVICE_INFO_HEADER
{
@@ -880,6 +905,36 @@ namespace ColorControl.Shared.Native
DISPLAYCONFIG_COLOR_ENCODING_INTENSITY = 4,
}
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ColorParams
+ {
+ // to get actual values: divide by 2^10 (precision equivalent to edid values)
+ // on newer w11, the divisor is 2^20 instead
+ public uint RedPointX;
+ public uint RedPointY;
+ public uint GreenPointX;
+ public uint GreenPointY;
+ public uint BluePointX;
+ public uint BluePointY;
+ public uint WhitePointX;
+ public uint WhitePointY;
+ // rest stored as nits * 10000
+ public uint MinLuminance;
+ public uint MaxLuminance;
+ public uint MaxFullFrameLuminance;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct DISPLAYCONFIG_GET_DISPLAY_INFO
+ {
+ public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1964)]
+ public byte[] Stuff1;
+ public ColorParams colorParams;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
+ public byte[] Stuff2;
+ }
+
[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_GET_ADVANCED_COLOR_INFO
{
@@ -1046,9 +1101,13 @@ namespace ColorControl.Shared.Native
[DllImport("user32")]
public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SDR_WHITE_LEVEL requestPacket);
+ [DllImport("user32")]
+ public static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_GET_DISPLAY_INFO requestPacket);
[DllImport("mscms", CharSet = CharSet.Unicode)]
public static extern bool InstallColorProfileW(string machineName, string profilename);
+ [DllImport("mscms", CharSet = CharSet.Unicode)]
+ public static extern bool UninstallColorProfileW(string machineName, string profilename, bool delete);
[DllImport("mscms", CharSet = CharSet.Unicode)]
public static extern int ColorProfileSetDisplayDefaultAssociation(WCS_PROFILE_MANAGEMENT_SCOPE scope, string profilename, COLORPROFILETYPE profiletype, COLORPROFILESUBTYPE profilesubtype, LUID targetadapterid, uint sourceid);
diff --git a/Shared/Native/CmsFunctions.cs b/Shared/Native/CmsFunctions.cs
new file mode 100644
index 0000000..1a9dd29
--- /dev/null
+++ b/Shared/Native/CmsFunctions.cs
@@ -0,0 +1,87 @@
+namespace ColorControl.Shared.Native;
+
+public static class CmsFunctions
+{
+
+ private const double PQ_M1 = 0.1593017578125;
+ private const double PQ_M2 = 78.84375;
+ private const double PQ_C1 = 0.8359375;
+ private const double PQ_C2 = 18.8515625;
+ private const double PQ_C3 = 18.6875;
+
+ public static double PqEotf(double signalValue)
+ {
+ var signalValuePow = Math.Pow(signalValue, 1D / PQ_M2);
+
+ var value1 = Math.Max(signalValuePow - PQ_C1, 0);
+ var value2 = PQ_C2 - PQ_C3 * signalValuePow;
+
+ var luminance = 10000D * Math.Pow(value1 / value2, 1D / PQ_M1);
+
+ return luminance;
+ }
+
+ public static double InvPqEotf(double luminance)
+ {
+ var normalizedLuminance = luminance / 10000D;
+
+ var luminancePow = Math.Pow(normalizedLuminance, PQ_M1);
+
+ var value1 = PQ_C1 + PQ_C2 * luminancePow;
+ var value2 = 1 + PQ_C3 * luminancePow;
+
+ var signalValue = Math.Pow(value1 / value2, PQ_M2);
+
+ return signalValue;
+ }
+
+ public static double SrgbInvEotf(double luminance, double whiteLuminance, double blackLuminance, double gamma = 2.4)
+ {
+ //const double X1 = 0.0404482362771082;
+ const double X2 = 0.00313066844250063;
+
+ var x = (luminance - blackLuminance) / (whiteLuminance - blackLuminance);
+
+ if (x > 1)
+ {
+ x = 1;
+ }
+ else if (x < 0)
+ {
+ x = 0;
+ }
+ else if (x <= X2)
+ {
+ x *= 12.92;
+ }
+ else
+ {
+ x = 1.055 * Math.Pow(x, 1 / gamma) - 0.055;
+ }
+
+ return x;
+ }
+
+ public static (double, double) SrgbAcm(int signalValue, double whiteLuminance, double blackLuminance = 0, double gamma = 2.2)
+ {
+ var b = signalValue / 1023;
+ var c = PqEotf(b);
+ var d = SrgbInvEotf(c, whiteLuminance, blackLuminance);
+ var e = blackLuminance + (whiteLuminance - blackLuminance) * Math.Pow(d, gamma);
+ var f = InvPqEotf(Math.Max(0, e));
+ var x = f + Math.Min(1, c / whiteLuminance) * (b - f);
+
+ return (b, x);
+ }
+
+ public static double GammaRgb(double signalValue, double gamma = 2.4)
+ {
+ return signalValue <= 0.0031308 ? signalValue * 12.92 : 1.055 * Math.Pow(signalValue, 1.0 / gamma) - 0.055;
+ }
+
+ public static double RgbToLinear(double signalValue, double gamma = 2.4)
+ {
+ return signalValue <= 0.04045 ? signalValue / 12.92 : Math.Pow((signalValue + 0.055) / 1.055, gamma);
+ }
+
+}
diff --git a/Shared/Services/WinApiAdminService.cs b/Shared/Services/WinApiAdminService.cs
index 536c98f..0e59692 100644
--- a/Shared/Services/WinApiAdminService.cs
+++ b/Shared/Services/WinApiAdminService.cs
@@ -1,4 +1,5 @@
using ColorControl.Shared.Contracts;
+using ColorControl.Shared.Native;
using Microsoft.Win32;
using Microsoft.Win32.TaskScheduler;
using NWin32;
@@ -336,6 +337,18 @@ namespace ColorControl.Shared.Services
}
}
+ public bool UninstallColorProfile(string filename)
+ {
+ if (!_winApiService.IsAdministrator())
+ {
+ return _rpcService.Call(nameof(UninstallColorProfile), filename);
+ }
+
+ var deleteResult = CCD.UninstallColorProfile(filename);
+
+ return deleteResult;
+ }
+
private static bool UpdateShortcut(string path, string arguments, bool removeArguments = false)
{
if (File.Exists(path))
diff --git a/Shared/XForms/BaseViewModel.cs b/Shared/XForms/BaseViewModel.cs
new file mode 100644
index 0000000..88dadbd
--- /dev/null
+++ b/Shared/XForms/BaseViewModel.cs
@@ -0,0 +1,52 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Runtime.CompilerServices;
+
+namespace ColorControl.Shared.XForms;
+
+public abstract class BaseViewModel : IDataErrorInfo, INotifyPropertyChanged
+{
+ public virtual string Error => null;
+ public bool IsValid { get; set; }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
+
+ public virtual string this[string columnName]
+ {
+ get
+ {
+ var validationResults = new List();
+
+ var fieldValid = Validator.TryValidateProperty(GetType().GetProperty(columnName).GetValue(this)
+ , new ValidationContext(this)
+ {
+ MemberName = columnName
+ }
+ , validationResults);
+
+ bool isValid;
+ if (fieldValid)
+ {
+ var allValidationResults = new List();
+ isValid = PreValidate() && Validator.TryValidateObject(this, new ValidationContext(this), allValidationResults, true);
+ }
+ else
+ {
+ isValid = false;
+ }
+ if (isValid != IsValid)
+ {
+ IsValid = isValid;
+ OnPropertyChanged("IsValid");
+ }
+
+ return fieldValid ? null : validationResults.First().ErrorMessage;
+ }
+ }
+
+ public virtual bool PreValidate()
+ {
+ return true;
+ }
+}
diff --git a/novideo_srgb/Novideo.cs b/novideo_srgb/Novideo.cs
index d116ef2..5895561 100644
--- a/novideo_srgb/Novideo.cs
+++ b/novideo_srgb/Novideo.cs
@@ -271,6 +271,10 @@ namespace novideo_srgb
}
catch
{
+ if (display == null)
+ {
+ throw new InvalidOperationException("No display");
+ }
return new EDID(display.Output.PhysicalGPU.ReadEDIDData(display.Output));
}
}