Add piecewise tone mapping option (#454)

* Add piecewise tone mapping option

* Add curve like to tone mapping transfer function

* Fix conflicts

* Experiments

* Add piecewise tone mapped option to UI

* Fix IIcc context diff and reset cproj settings
This commit is contained in:
Victor Nicolás Lizarraga Ochoa authored and GitHub committed 2025-03-29 17:27:01 +01:00
1 parent 6e206a6728
commit 043e5e7ff2
9 files changed
+238 -13

No files matched your search

@@ -240,13 +240,42 @@
<div class="col">
<div class="mb-2">
<label class="form-label" for="brightnessBoost">Brightness boost (%)</label>
<input class="form-control" id="brightnessBoost" type="number" min="-100" max="100" @bind="ColorProfile.SDRBrightnessBoost" />
<input class="form-control" id="brightnessBoost" type="number" min="-100" max="100" @bind="ColorProfile.SDRBrightnessBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="shadowDetailBoost">Shadow detail boost (%)</label>
<input class="form-control" id="shadowDetailBoost" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.Piecewise)" />
<input class="form-control" id="shadowDetailBoost" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.Piecewise || ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="hdrluminance">Current HDR Luminance (nits):</label>
<input class="form-control" id="hdrluminance" type="number" min="400" max="10000" @bind="ColorProfile.ToneMappingFromLuminance" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="hdrluminanceoutput">HDR Luminance to convert (nits):</label>
<input class="form-control" id="hdrluminanceoutput" type="number" min="400" max="10000" @bind="ColorProfile.ToneMappingToLuminance" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="brightnessmultiplier">Brightness Multiplier</label>
<input class="form-control" id="brightnessmultiplier" type="number" min="0.04" max="25" step=".001" @bind="ColorProfile.HdrBrightnessMultiplier" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="gammamultiplier">Gamma Multiplier</label>
<input class="form-control" id="gammamultiplier" type="number" min="0.04" max="25" step=".001" @bind="ColorProfile.HdrGammaMultiplier" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
</div>
</div>
</div>
@@ -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);
+47 -2
View File
@@ -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))
{
+17 -4
View File
@@ -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 @@
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="412,236,0,0" TextWrapping="Wrap" Text="{Binding WhiteLuminance, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" Name="tbMaxLuminance" Height="18" InputScope="Number"/>
</Grid>
</GroupBox>
<GroupBox Header="Advanced HDR Settings" Padding="6" Height="133" Width="734" HorizontalAlignment="Left" Visibility="{Binding VisibilityHDRSettings}">
<Grid Height="103">
<GroupBox Header="Advanced HDR Settings" Padding="6" Height="199" Width="734" HorizontalAlignment="Left" Visibility="{Binding VisibilityHDRSettings}">
<Grid Height="150">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="179*"/>
<ColumnDefinition Width="735*"/>
@@ -66,7 +66,20 @@
<Label Content="SDR maximum brightness (nits):" HorizontalAlignment="Left" Margin="197,37,0,0" VerticalAlignment="Top" Width="208" Height="26" Grid.Column="1"/>
<TextBox IsEnabled="{Binding SDRSettingsEnabled}" Grid.Column="1" HorizontalAlignment="Left" Margin="405,41,0,0" TextWrapping="Wrap" Text="{Binding SDRMaxBrightness, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" x:Name="tbSDRMaxBrightness" Height="18" InputScope="Number"/>
<Label Content="Brightness boost (%):" HorizontalAlignment="Left" Margin="-5,64,0,0" VerticalAlignment="Top" Width="208" Height="26" Grid.ColumnSpan="2"/>
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="69,68,0,0" TextWrapping="Wrap" Text="{Binding SDRBrightnessBoost, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" x:Name="tbSDRMinBrightness_Copy6" Height="18" InputScope="Number"/>
<TextBox IsEnabled="{Binding BrightnessBoostSettingsEnabled}" Grid.Column="1" HorizontalAlignment="Left" Margin="69,68,0,0" TextWrapping="Wrap" Text="{Binding SDRBrightnessBoost, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" x:Name="tbSDRMinBrightness_Copy6" Height="18" InputScope="Number"/>
<Label Content="Current HDR Luminance (nits):" Margin="-5,94,507,0" VerticalAlignment="Top" Height="26" Grid.ColumnSpan="2"/>
<TextBox IsEnabled="{Binding ToneMappingSettingsEnabled}" Grid.Column="1" HorizontalAlignment="Left" Margin="69,98,0,0" TextWrapping="Wrap" Text="{Binding ToneMappingFromLuminance, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" x:Name="tbNitsFrom" Height="18" InputScope="Number"/>
<Label Content="HDR Luminance to convert (nits):" HorizontalAlignment="Left" Margin="194,94,0,0" VerticalAlignment="Top" Width="211" Height="26" Grid.Column="1"/>
<TextBox IsEnabled="{Binding ToneMappingSettingsEnabled}" Grid.Column="1" HorizontalAlignment="Left" Margin="405,98,0,0" TextWrapping="Wrap" Text="{Binding ToneMappingToLuminance, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" x:Name="tbNitsTo" Height="18" InputScope="Number"/>
<Label Content="Brightness multiplier:" HorizontalAlignment="Left" Margin="-5,120,0,4" Width="208" Grid.ColumnSpan="2"/>
<TextBox IsEnabled="{Binding ToneMappingSettingsEnabled}" Grid.Column="1" HorizontalAlignment="Left" Margin="69,128,0,0" TextWrapping="Wrap" Text="{Binding HdrBrightnessMultiplier, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" x:Name="tbhdrbrightness" Height="18" InputScope="Number"/>
<Label Content="Gamma multiplier:" HorizontalAlignment="Left" Margin="194,124,0,0" Width="211" Grid.Column="1"/>
<TextBox IsEnabled="{Binding ToneMappingSettingsEnabled}" Grid.Column="1" HorizontalAlignment="Left" Margin="405,128,0,0" TextWrapping="Wrap" Text="{Binding HdrGammaMultiplier, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" VerticalAlignment="Top" Width="120" x:Name="tbhdrgamma" Height="18" InputScope="Number"/>
</Grid>
</GroupBox>
<GroupBox Header="Advanced Profile Settings" Padding="6" Height="71" Width="734" HorizontalAlignment="Left">
@@ -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);
+3 -1
View File
@@ -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
+9 -1
View File
@@ -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; }
}
+116 -2
View File
@@ -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
}
}
}
}
@@ -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;