diff --git a/ColorControl.UI/Components/Pages/ColorProfile/ColorProfilePage.razor b/ColorControl.UI/Components/Pages/ColorProfile/ColorProfilePage.razor
index 8f3ffbf..9fafe5e 100644
--- a/ColorControl.UI/Components/Pages/ColorProfile/ColorProfilePage.razor
+++ b/ColorControl.UI/Components/Pages/ColorProfile/ColorProfilePage.razor
@@ -240,13 +240,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ColorControl/Services/Common/ColorProfileService.cs b/ColorControl/Services/Common/ColorProfileService.cs
index d0dc2c1..8ebde01 100644
--- a/ColorControl/Services/Common/ColorProfileService.cs
+++ b/ColorControl/Services/Common/ColorProfileService.cs
@@ -198,6 +198,11 @@ public class ColorProfileService
ColorGamut = colorProfile.ColorGamut,
Gamma = colorProfile.Gamma,
DevicePrimaries = colorProfile.DevicePrimaries.ToInternal(),
+
+ ToneMappingFromLuminance = colorProfile.ToneMappingFromLuminance,
+ ToneMappingToLuminance = colorProfile.ToneMappingToLuminance,
+ HdrBrightnessMultiplier = colorProfile.HdrBrightnessMultiplier,
+ HdrGammaMultiplier = colorProfile.HdrGammaMultiplier,
};
var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);
diff --git a/ColorControl/XForms/ColorProfileViewModel.cs b/ColorControl/XForms/ColorProfileViewModel.cs
index 1a89a3f..ed8c4f7 100644
--- a/ColorControl/XForms/ColorProfileViewModel.cs
+++ b/ColorControl/XForms/ColorProfileViewModel.cs
@@ -110,6 +110,22 @@ internal class ColorProfileViewModel : BaseViewModel
public bool SetMinMaxTml { get; set; } = true;
public bool PrimariesEnabled { get; set; } = true;
+ [Range(400, 10000)]
+ public double ToneMappingFromLuminance { get; set; } = 400;
+ [Range(400, 10000)]
+ public double ToneMappingToLuminance { get; set; } = 400;
+
+
+ public bool ToneMappingSettingsEnabled { get; set; }
+
+ public bool BrightnessBoostSettingsEnabled { get; set; }
+
+ [Range(.04, 25)]
+ public double HdrBrightnessMultiplier { get; set; } = 1;
+
+ [Range(.04, 25)]
+ public double HdrGammaMultiplier { get; set; } = 1;
+
public override string this[string columnName]
{
get
@@ -122,8 +138,37 @@ internal class ColorProfileViewModel : BaseViewModel
}
else if (columnName == nameof(SDRTransferFunction))
{
- SDRSettingsEnabled = SDRTransferFunction == SDRTransferFunction.PurePower;
- OnPropertyChanged(nameof(SDRSettingsEnabled));
+ if (SDRTransferFunction == SDRTransferFunction.PurePower)
+ {
+ SDRSettingsEnabled = true;
+ ToneMappingSettingsEnabled = false;
+ BrightnessBoostSettingsEnabled = true;
+ OnPropertyChanged(nameof(SDRSettingsEnabled));
+ OnPropertyChanged(nameof(ToneMappingSettingsEnabled));
+ OnPropertyChanged(nameof(BrightnessBoostSettingsEnabled));
+ }
+ else if (SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)
+ {
+
+ ToneMappingSettingsEnabled = true;
+ SDRSettingsEnabled = false;
+ BrightnessBoostSettingsEnabled = false;
+ OnPropertyChanged(nameof(ToneMappingSettingsEnabled));
+ OnPropertyChanged(nameof(SDRSettingsEnabled));
+ OnPropertyChanged(nameof(BrightnessBoostSettingsEnabled));
+ }
+ else
+ {
+ ToneMappingSettingsEnabled = false;
+ SDRSettingsEnabled = false;
+ BrightnessBoostSettingsEnabled = true;
+ OnPropertyChanged(nameof(ToneMappingSettingsEnabled));
+ OnPropertyChanged(nameof(SDRSettingsEnabled));
+ OnPropertyChanged(nameof(BrightnessBoostSettingsEnabled));
+ }
+
+
+
}
else if (columnName == nameof(SelectedExistingProfile))
{
diff --git a/ColorControl/XForms/ColorProfileWindow.xaml b/ColorControl/XForms/ColorProfileWindow.xaml
index bbc454d..ec35338 100644
--- a/ColorControl/XForms/ColorProfileWindow.xaml
+++ b/ColorControl/XForms/ColorProfileWindow.xaml
@@ -7,7 +7,7 @@
Title="Create Color Profile"
ResizeMode="CanResize"
Width="773"
- Height="791"
+ Height="877"
WindowStartupLocation="CenterOwner"
SnapsToDevicePixels="True"
Style="{DynamicResource CustomWindowStyle}"
@@ -52,8 +52,8 @@
-
-
+
+
@@ -66,7 +66,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ColorControl/XForms/ColorProfileWindow.xaml.cs b/ColorControl/XForms/ColorProfileWindow.xaml.cs
index fa8232a..79096df 100644
--- a/ColorControl/XForms/ColorProfileWindow.xaml.cs
+++ b/ColorControl/XForms/ColorProfileWindow.xaml.cs
@@ -116,6 +116,10 @@ namespace ColorControl.XForms
ColorGamut = _viewModel.ColorGamut,
Gamma = _viewModel.CustomGamma,
DevicePrimaries = _viewModel.GetDevicePrimaries(),
+ ToneMappingFromLuminance = _viewModel.ToneMappingFromLuminance,
+ ToneMappingToLuminance = _viewModel.ToneMappingToLuminance,
+ HdrBrightnessMultiplier = _viewModel.HdrBrightnessMultiplier,
+ HdrGammaMultiplier = _viewModel.HdrGammaMultiplier
};
var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);
diff --git a/MHC2Gen/DisplayEnums.cs b/MHC2Gen/DisplayEnums.cs
index 632e1e2..1bb5755 100644
--- a/MHC2Gen/DisplayEnums.cs
+++ b/MHC2Gen/DisplayEnums.cs
@@ -28,7 +28,9 @@ public enum SDRTransferFunction
[Description("Pure Power")]
PurePower = 1,
[Description("Piecewise")]
- Piecewise = 2
+ Piecewise = 2,
+ [Description("Tone Mapped Piecewise")]
+ ToneMappedPiecewise = 3
}
public enum ColorGamut
diff --git a/MHC2Gen/GenerateProfileCommand.cs b/MHC2Gen/GenerateProfileCommand.cs
index cc504a2..bf063cb 100644
--- a/MHC2Gen/GenerateProfileCommand.cs
+++ b/MHC2Gen/GenerateProfileCommand.cs
@@ -1,4 +1,6 @@
-namespace MHC2Gen;
+using System;
+
+namespace MHC2Gen;
public class GenerateProfileCommand
{
@@ -17,4 +19,10 @@ public class GenerateProfileCommand
public double SDRBrightnessBoost { get; set; }
public double ShadowDetailBoost { get; set; }
public double Gamma { get; set; } = 2.2;
+
+ public double ToneMappingFromLuminance { get; set; }
+ public double ToneMappingToLuminance { get; set; }
+
+ public double HdrGammaMultiplier { get; set; }
+ public double HdrBrightnessMultiplier { get; set; }
}
diff --git a/MHC2Gen/IccContext.cs b/MHC2Gen/IccContext.cs
index 0d0bfb9..0cf843b 100644
--- a/MHC2Gen/IccContext.cs
+++ b/MHC2Gen/IccContext.cs
@@ -127,6 +127,104 @@ namespace MHC2Gen
}
}
+ public void ApplyToneMappingCurve(double maxInputNits = 400, double maxOutputNits = 400, double curve_like = 400)
+ {
+ int lutSize = 1024;
+ bool lutExist = true;
+ if (RegammaLUT == null)
+ {
+ lutExist = false;
+ RegammaLUT = new double[3, lutSize];
+ }
+
+ for (int i = 0; i < lutSize; i++)
+ {
+ double N = lutExist ? RegammaLUT[0, i] : (double)i / (lutSize - 1);
+ double L = InversePQ(N) * 10000 * (maxInputNits / curve_like);
+ double numerator = L * (maxInputNits + (L / Math.Pow(maxOutputNits / curve_like, 2)));
+ double L_d = numerator / (maxInputNits + L);
+ double N_prime = PQ(L_d / 10000);
+
+ N_prime = Math.Max(0.0, Math.Min(1.0, N_prime));
+
+ for (int c = 0; c < 3; c++)
+ {
+ RegammaLUT[c, i] = N_prime;
+ }
+ }
+ }
+
+
+ public void ApplyToneMappingCurveGamma(double maxInputNits = 400, double maxOutputNits = 400, double curve_like = 400)
+ {
+ int lutSize = 1024;
+ bool lutExist = true;
+ if (RegammaLUT == null)
+ {
+ lutExist = false;
+ RegammaLUT = new double[3, lutSize];
+ }
+
+ double L_target = (1 * (PQ((curve_like) / 10000.0) / PQ((maxInputNits) / 10000.0)));
+ double L_target_prime = 1 * (PQ((curve_like) / 10000) / PQ((maxOutputNits) / 10000.0));
+ double difference = L_target_prime;
+ for (int i = 0; i < lutSize; i++)
+ {
+ double N = lutExist ? RegammaLUT[0, i] : (double)i / (lutSize - 1);
+ double N_converted = N * difference;
+ double L = InversePQ(N_converted) * 10000 * (maxInputNits / curve_like);
+
+ double N_prime = PQ(L / 10000);
+ N_prime = Math.Max(0.0, Math.Min(1.0, N_prime));
+ for (int c = 0; c < 3; c++)
+ {
+ RegammaLUT[c, i] = N_prime;
+ }
+ }
+ }
+
+ // PQ EOTF function: converts luminance (cd/m^2) to normalized signal value
+ private double PQ(double L)
+ {
+ double m1 = 0.1593017578125;
+ double m2 = 78.84375;
+ double c1 = 0.8359375;
+ double c2 = 18.8515625;
+ double c3 = 18.6875;
+
+ double Lm1 = Math.Pow(L, m1);
+ double numerator = c1 + c2 * Lm1;
+ double denominator = 1 + c3 * Lm1;
+ double N = Math.Pow(numerator / denominator, m2);
+
+ return N;
+ }
+
+ // Inverse PQ EOTF function: converts normalized signal value to luminance (cd/m^2)
+ private double InversePQ(double N)
+ {
+ double m1 = 0.1593017578125;
+ double m2 = 78.84375;
+ double c1 = 0.8359375;
+ double c2 = 18.8515625;
+ double c3 = 18.6875;
+
+ double N1_m2 = Math.Pow(N, 1.0 / m2);
+ double numerator = N1_m2 - c1;
+ double denominator = c2 - c3 * N1_m2;
+
+ double Lm1 = numerator / denominator;
+
+ // Ensure Lm1 is non-negative to avoid invalid values
+ Lm1 = Math.Max(Lm1, 0.0);
+
+ double L = Math.Pow(Lm1, 1.0 / m1);
+
+ return L;
+ }
+
+
+
public void ApplyGamma(double gamma = 2.2, double shadowDetailBoost = 0)
{
var lutSize = 1024;
@@ -995,7 +1093,23 @@ namespace MHC2Gen
else if (command.SDRTransferFunction == SDRTransferFunction.BT_1886)
{
MHC2.ApplySdrAcm(120, 0.03, 2.4, command.SDRBrightnessBoost, command.ShadowDetailBoost);
- }
+ }else if (command.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)
+ {
+
+ double from_nits = command.ToneMappingFromLuminance;
+
+ double to_nits = command.ToneMappingToLuminance;
+ double numerator = to_nits / from_nits;
+
+ double gamma_like = (command.ToneMappingToLuminance / command.HdrGammaMultiplier) / numerator;
+ double curve_like = (command.ToneMappingToLuminance / command.HdrBrightnessMultiplier) / numerator;
+
+ MHC2.ApplyToneMappingCurve(from_nits, to_nits, to_nits);
+
+ MHC2.ApplyToneMappingCurve(from_nits, from_nits, curve_like);
+ MHC2.ApplyToneMappingCurveGamma(from_nits, from_nits, gamma_like);
+
+ }
}
else
{
@@ -1233,4 +1347,4 @@ namespace MHC2Gen
}
}
-}
+}
\ No newline at end of file
diff --git a/Shared/Contracts/DisplayInfo/ColorProfileDto.cs b/Shared/Contracts/DisplayInfo/ColorProfileDto.cs
index 31e316c..b8af258 100644
--- a/Shared/Contracts/DisplayInfo/ColorProfileDto.cs
+++ b/Shared/Contracts/DisplayInfo/ColorProfileDto.cs
@@ -20,8 +20,13 @@ public class ColorProfileDto
public double SDRBrightnessBoost { get; set; }
public double ShadowDetailBoost { get; set; }
public double Gamma { get; set; } = 2.2;
+ public double ToneMappingFromLuminance { get; set; } = 400;
+ public double ToneMappingToLuminance { get; set; } = 400;
- public void UpdatePrimariesAndLuminance(DisplayColorInfo displayColorInfo)
+ public double HdrGammaMultiplier { get; set; } = 1;
+ public double HdrBrightnessMultiplier { get; set; } = 1;
+
+ public void UpdatePrimariesAndLuminance(DisplayColorInfo displayColorInfo)
{
BlackLuminance = displayColorInfo.BlackLuminance;
WhiteLuminance = displayColorInfo.WhiteLuminance;