Skip to content

Commit 043e5e7

Browse files
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
1 parent 6e206a6 commit 043e5e7

File tree

9 files changed

+238
-13
lines changed

9 files changed

+238
-13
lines changed

ColorControl.UI/Components/Pages/ColorProfile/ColorProfilePage.razor

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,13 +240,42 @@
240240
<div class="col">
241241
<div class="mb-2">
242242
<label class="form-label" for="brightnessBoost">Brightness boost (%)</label>
243-
<input class="form-control" id="brightnessBoost" type="number" min="-100" max="100" @bind="ColorProfile.SDRBrightnessBoost" />
243+
<input class="form-control" id="brightnessBoost" type="number" min="-100" max="100" @bind="ColorProfile.SDRBrightnessBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
244244
</div>
245245
</div>
246246
<div class="col">
247247
<div class="mb-2">
248248
<label class="form-label" for="shadowDetailBoost">Shadow detail boost (%)</label>
249-
<input class="form-control" id="shadowDetailBoost" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.Piecewise)" />
249+
<input class="form-control" id="shadowDetailBoost" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.Piecewise || ColorProfile.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)" />
250+
</div>
251+
</div>
252+
</div>
253+
254+
<div class="row">
255+
<div class="col">
256+
<div class="mb-2">
257+
<label class="form-label" for="hdrluminance">Current HDR Luminance (nits):</label>
258+
<input class="form-control" id="hdrluminance" type="number" min="400" max="10000" @bind="ColorProfile.ToneMappingFromLuminance" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
259+
</div>
260+
</div>
261+
<div class="col">
262+
<div class="mb-2">
263+
<label class="form-label" for="hdrluminanceoutput">HDR Luminance to convert (nits):</label>
264+
<input class="form-control" id="hdrluminanceoutput" type="number" min="400" max="10000" @bind="ColorProfile.ToneMappingToLuminance" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
265+
</div>
266+
</div>
267+
</div>
268+
<div class="row">
269+
<div class="col">
270+
<div class="mb-2">
271+
<label class="form-label" for="brightnessmultiplier">Brightness Multiplier</label>
272+
<input class="form-control" id="brightnessmultiplier" type="number" min="0.04" max="25" step=".001" @bind="ColorProfile.HdrBrightnessMultiplier" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
273+
</div>
274+
</div>
275+
<div class="col">
276+
<div class="mb-2">
277+
<label class="form-label" for="gammamultiplier">Gamma Multiplier</label>
278+
<input class="form-control" id="gammamultiplier" type="number" min="0.04" max="25" step=".001" @bind="ColorProfile.HdrGammaMultiplier" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.ToneMappedPiecewise)" />
250279
</div>
251280
</div>
252281
</div>

ColorControl/Services/Common/ColorProfileService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ public bool UpdateColorProfile(ColorProfileDto colorProfile, string displayName
198198
ColorGamut = colorProfile.ColorGamut,
199199
Gamma = colorProfile.Gamma,
200200
DevicePrimaries = colorProfile.DevicePrimaries.ToInternal(),
201+
202+
ToneMappingFromLuminance = colorProfile.ToneMappingFromLuminance,
203+
ToneMappingToLuminance = colorProfile.ToneMappingToLuminance,
204+
HdrBrightnessMultiplier = colorProfile.HdrBrightnessMultiplier,
205+
HdrGammaMultiplier = colorProfile.HdrGammaMultiplier,
201206
};
202207

203208
var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);

ColorControl/XForms/ColorProfileViewModel.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@ internal class ColorProfileViewModel : BaseViewModel
110110
public bool SetMinMaxTml { get; set; } = true;
111111
public bool PrimariesEnabled { get; set; } = true;
112112

113+
[Range(400, 10000)]
114+
public double ToneMappingFromLuminance { get; set; } = 400;
115+
[Range(400, 10000)]
116+
public double ToneMappingToLuminance { get; set; } = 400;
117+
118+
119+
public bool ToneMappingSettingsEnabled { get; set; }
120+
121+
public bool BrightnessBoostSettingsEnabled { get; set; }
122+
123+
[Range(.04, 25)]
124+
public double HdrBrightnessMultiplier { get; set; } = 1;
125+
126+
[Range(.04, 25)]
127+
public double HdrGammaMultiplier { get; set; } = 1;
128+
113129
public override string this[string columnName]
114130
{
115131
get
@@ -122,8 +138,37 @@ public override string this[string columnName]
122138
}
123139
else if (columnName == nameof(SDRTransferFunction))
124140
{
125-
SDRSettingsEnabled = SDRTransferFunction == SDRTransferFunction.PurePower;
126-
OnPropertyChanged(nameof(SDRSettingsEnabled));
141+
if (SDRTransferFunction == SDRTransferFunction.PurePower)
142+
{
143+
SDRSettingsEnabled = true;
144+
ToneMappingSettingsEnabled = false;
145+
BrightnessBoostSettingsEnabled = true;
146+
OnPropertyChanged(nameof(SDRSettingsEnabled));
147+
OnPropertyChanged(nameof(ToneMappingSettingsEnabled));
148+
OnPropertyChanged(nameof(BrightnessBoostSettingsEnabled));
149+
}
150+
else if (SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)
151+
{
152+
153+
ToneMappingSettingsEnabled = true;
154+
SDRSettingsEnabled = false;
155+
BrightnessBoostSettingsEnabled = false;
156+
OnPropertyChanged(nameof(ToneMappingSettingsEnabled));
157+
OnPropertyChanged(nameof(SDRSettingsEnabled));
158+
OnPropertyChanged(nameof(BrightnessBoostSettingsEnabled));
159+
}
160+
else
161+
{
162+
ToneMappingSettingsEnabled = false;
163+
SDRSettingsEnabled = false;
164+
BrightnessBoostSettingsEnabled = true;
165+
OnPropertyChanged(nameof(ToneMappingSettingsEnabled));
166+
OnPropertyChanged(nameof(SDRSettingsEnabled));
167+
OnPropertyChanged(nameof(BrightnessBoostSettingsEnabled));
168+
}
169+
170+
171+
127172
}
128173
else if (columnName == nameof(SelectedExistingProfile))
129174
{

ColorControl/XForms/ColorProfileWindow.xaml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Title="Create Color Profile"
88
ResizeMode="CanResize"
99
Width="773"
10-
Height="791"
10+
Height="877"
1111
WindowStartupLocation="CenterOwner"
1212
SnapsToDevicePixels="True"
1313
Style="{DynamicResource CustomWindowStyle}"
@@ -52,8 +52,8 @@
5252
<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"/>
5353
</Grid>
5454
</GroupBox>
55-
<GroupBox Header="Advanced HDR Settings" Padding="6" Height="133" Width="734" HorizontalAlignment="Left" Visibility="{Binding VisibilityHDRSettings}">
56-
<Grid Height="103">
55+
<GroupBox Header="Advanced HDR Settings" Padding="6" Height="199" Width="734" HorizontalAlignment="Left" Visibility="{Binding VisibilityHDRSettings}">
56+
<Grid Height="150">
5757
<Grid.ColumnDefinitions>
5858
<ColumnDefinition Width="179*"/>
5959
<ColumnDefinition Width="735*"/>
@@ -66,7 +66,20 @@
6666
<Label Content="SDR maximum brightness (nits):" HorizontalAlignment="Left" Margin="197,37,0,0" VerticalAlignment="Top" Width="208" Height="26" Grid.Column="1"/>
6767
<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"/>
6868
<Label Content="Brightness boost (%):" HorizontalAlignment="Left" Margin="-5,64,0,0" VerticalAlignment="Top" Width="208" Height="26" Grid.ColumnSpan="2"/>
69-
<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"/>
69+
<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"/>
70+
71+
<Label Content="Current HDR Luminance (nits):" Margin="-5,94,507,0" VerticalAlignment="Top" Height="26" Grid.ColumnSpan="2"/>
72+
<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"/>
73+
74+
<Label Content="HDR Luminance to convert (nits):" HorizontalAlignment="Left" Margin="194,94,0,0" VerticalAlignment="Top" Width="211" Height="26" Grid.Column="1"/>
75+
<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"/>
76+
77+
<Label Content="Brightness multiplier:" HorizontalAlignment="Left" Margin="-5,120,0,4" Width="208" Grid.ColumnSpan="2"/>
78+
<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"/>
79+
80+
<Label Content="Gamma multiplier:" HorizontalAlignment="Left" Margin="194,124,0,0" Width="211" Grid.Column="1"/>
81+
<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"/>
82+
7083
</Grid>
7184
</GroupBox>
7285
<GroupBox Header="Advanced Profile Settings" Padding="6" Height="71" Width="734" HorizontalAlignment="Left">

ColorControl/XForms/ColorProfileWindow.xaml.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ private void Button_Click(object sender, RoutedEventArgs e)
116116
ColorGamut = _viewModel.ColorGamut,
117117
Gamma = _viewModel.CustomGamma,
118118
DevicePrimaries = _viewModel.GetDevicePrimaries(),
119+
ToneMappingFromLuminance = _viewModel.ToneMappingFromLuminance,
120+
ToneMappingToLuminance = _viewModel.ToneMappingToLuminance,
121+
HdrBrightnessMultiplier = _viewModel.HdrBrightnessMultiplier,
122+
HdrGammaMultiplier = _viewModel.HdrGammaMultiplier
119123
};
120124

121125
var bytes = MHC2Wrapper.GenerateSdrAcmProfile(command);

MHC2Gen/DisplayEnums.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ public enum SDRTransferFunction
2828
[Description("Pure Power")]
2929
PurePower = 1,
3030
[Description("Piecewise")]
31-
Piecewise = 2
31+
Piecewise = 2,
32+
[Description("Tone Mapped Piecewise")]
33+
ToneMappedPiecewise = 3
3234
}
3335

3436
public enum ColorGamut

MHC2Gen/GenerateProfileCommand.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace MHC2Gen;
1+
using System;
2+
3+
namespace MHC2Gen;
24

35
public class GenerateProfileCommand
46
{
@@ -17,4 +19,10 @@ public class GenerateProfileCommand
1719
public double SDRBrightnessBoost { get; set; }
1820
public double ShadowDetailBoost { get; set; }
1921
public double Gamma { get; set; } = 2.2;
22+
23+
public double ToneMappingFromLuminance { get; set; }
24+
public double ToneMappingToLuminance { get; set; }
25+
26+
public double HdrGammaMultiplier { get; set; }
27+
public double HdrBrightnessMultiplier { get; set; }
2028
}

MHC2Gen/IccContext.cs

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,104 @@ public void ApplyPiecewise(double boostPercentage = 0)
127127
}
128128
}
129129

130+
public void ApplyToneMappingCurve(double maxInputNits = 400, double maxOutputNits = 400, double curve_like = 400)
131+
{
132+
int lutSize = 1024;
133+
bool lutExist = true;
134+
if (RegammaLUT == null)
135+
{
136+
lutExist = false;
137+
RegammaLUT = new double[3, lutSize];
138+
}
139+
140+
for (int i = 0; i < lutSize; i++)
141+
{
142+
double N = lutExist ? RegammaLUT[0, i] : (double)i / (lutSize - 1);
143+
double L = InversePQ(N) * 10000 * (maxInputNits / curve_like);
144+
double numerator = L * (maxInputNits + (L / Math.Pow(maxOutputNits / curve_like, 2)));
145+
double L_d = numerator / (maxInputNits + L);
146+
double N_prime = PQ(L_d / 10000);
147+
148+
N_prime = Math.Max(0.0, Math.Min(1.0, N_prime));
149+
150+
for (int c = 0; c < 3; c++)
151+
{
152+
RegammaLUT[c, i] = N_prime;
153+
}
154+
}
155+
}
156+
157+
158+
public void ApplyToneMappingCurveGamma(double maxInputNits = 400, double maxOutputNits = 400, double curve_like = 400)
159+
{
160+
int lutSize = 1024;
161+
bool lutExist = true;
162+
if (RegammaLUT == null)
163+
{
164+
lutExist = false;
165+
RegammaLUT = new double[3, lutSize];
166+
}
167+
168+
double L_target = (1 * (PQ((curve_like) / 10000.0) / PQ((maxInputNits) / 10000.0)));
169+
double L_target_prime = 1 * (PQ((curve_like) / 10000) / PQ((maxOutputNits) / 10000.0));
170+
double difference = L_target_prime;
171+
for (int i = 0; i < lutSize; i++)
172+
{
173+
double N = lutExist ? RegammaLUT[0, i] : (double)i / (lutSize - 1);
174+
double N_converted = N * difference;
175+
double L = InversePQ(N_converted) * 10000 * (maxInputNits / curve_like);
176+
177+
double N_prime = PQ(L / 10000);
178+
N_prime = Math.Max(0.0, Math.Min(1.0, N_prime));
179+
for (int c = 0; c < 3; c++)
180+
{
181+
RegammaLUT[c, i] = N_prime;
182+
}
183+
}
184+
}
185+
186+
// PQ EOTF function: converts luminance (cd/m^2) to normalized signal value
187+
private double PQ(double L)
188+
{
189+
double m1 = 0.1593017578125;
190+
double m2 = 78.84375;
191+
double c1 = 0.8359375;
192+
double c2 = 18.8515625;
193+
double c3 = 18.6875;
194+
195+
double Lm1 = Math.Pow(L, m1);
196+
double numerator = c1 + c2 * Lm1;
197+
double denominator = 1 + c3 * Lm1;
198+
double N = Math.Pow(numerator / denominator, m2);
199+
200+
return N;
201+
}
202+
203+
// Inverse PQ EOTF function: converts normalized signal value to luminance (cd/m^2)
204+
private double InversePQ(double N)
205+
{
206+
double m1 = 0.1593017578125;
207+
double m2 = 78.84375;
208+
double c1 = 0.8359375;
209+
double c2 = 18.8515625;
210+
double c3 = 18.6875;
211+
212+
double N1_m2 = Math.Pow(N, 1.0 / m2);
213+
double numerator = N1_m2 - c1;
214+
double denominator = c2 - c3 * N1_m2;
215+
216+
double Lm1 = numerator / denominator;
217+
218+
// Ensure Lm1 is non-negative to avoid invalid values
219+
Lm1 = Math.Max(Lm1, 0.0);
220+
221+
double L = Math.Pow(Lm1, 1.0 / m1);
222+
223+
return L;
224+
}
225+
226+
227+
130228
public void ApplyGamma(double gamma = 2.2, double shadowDetailBoost = 0)
131229
{
132230
var lutSize = 1024;
@@ -995,7 +1093,23 @@ public IccProfile CreateIcc(GenerateProfileCommand command)
9951093
else if (command.SDRTransferFunction == SDRTransferFunction.BT_1886)
9961094
{
9971095
MHC2.ApplySdrAcm(120, 0.03, 2.4, command.SDRBrightnessBoost, command.ShadowDetailBoost);
998-
}
1096+
}else if (command.SDRTransferFunction == SDRTransferFunction.ToneMappedPiecewise)
1097+
{
1098+
1099+
double from_nits = command.ToneMappingFromLuminance;
1100+
1101+
double to_nits = command.ToneMappingToLuminance;
1102+
double numerator = to_nits / from_nits;
1103+
1104+
double gamma_like = (command.ToneMappingToLuminance / command.HdrGammaMultiplier) / numerator;
1105+
double curve_like = (command.ToneMappingToLuminance / command.HdrBrightnessMultiplier) / numerator;
1106+
1107+
MHC2.ApplyToneMappingCurve(from_nits, to_nits, to_nits);
1108+
1109+
MHC2.ApplyToneMappingCurve(from_nits, from_nits, curve_like);
1110+
MHC2.ApplyToneMappingCurveGamma(from_nits, from_nits, gamma_like);
1111+
1112+
}
9991113
}
10001114
else
10011115
{
@@ -1233,4 +1347,4 @@ public IccProfile CreateCscIcc(RgbPrimaries? sourcePrimaries = null, string sour
12331347
}
12341348

12351349
}
1236-
}
1350+
}

Shared/Contracts/DisplayInfo/ColorProfileDto.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,13 @@ public class ColorProfileDto
2020
public double SDRBrightnessBoost { get; set; }
2121
public double ShadowDetailBoost { get; set; }
2222
public double Gamma { get; set; } = 2.2;
23+
public double ToneMappingFromLuminance { get; set; } = 400;
24+
public double ToneMappingToLuminance { get; set; } = 400;
2325

24-
public void UpdatePrimariesAndLuminance(DisplayColorInfo displayColorInfo)
26+
public double HdrGammaMultiplier { get; set; } = 1;
27+
public double HdrBrightnessMultiplier { get; set; } = 1;
28+
29+
public void UpdatePrimariesAndLuminance(DisplayColorInfo displayColorInfo)
2530
{
2631
BlackLuminance = displayColorInfo.BlackLuminance;
2732
WhiteLuminance = displayColorInfo.WhiteLuminance;

0 commit comments

Comments
 (0)