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;