mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-22 00:22:18 -05:00
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:
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);
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user