Support fractional degree rotation

Fixes #97
This commit is contained in:
Ben Olden-Cooligan 2023-02-28 15:48:21 -08:00
parent b7c4a121a0
commit da2e6bd391
10 changed files with 99 additions and 55 deletions

View File

@ -9,10 +9,10 @@ namespace NAPS2.EtoForms.Ui;
public class AdvancedProfileForm : EtoDialogBase
{
private readonly CheckBox _maximumQuality = new() { Text = UiStrings.MaximumQuality };
private readonly SliderWithTextBox _quality = new() { MinValue = 0, MaxValue = 100, TickFrequency = 25 };
private readonly SliderWithTextBox _quality = new(new SliderWithTextBox.IntConstraints(0, 100, 25));
private readonly CheckBox _excludeBlank = new() { Text = UiStrings.ExcludeBlankPages };
private readonly SliderWithTextBox _whiteThreshold = new() { MinValue = 0, MaxValue = 100, TickFrequency = 10 };
private readonly SliderWithTextBox _coverageThreshold = new() { MinValue = 0, MaxValue = 100, TickFrequency = 10 };
private readonly SliderWithTextBox _whiteThreshold = new(new SliderWithTextBox.IntConstraints(0, 100, 10));
private readonly SliderWithTextBox _coverageThreshold = new(new SliderWithTextBox.IntConstraints(0, 100, 10));
private readonly CheckBox _deskew = new() { Text = UiStrings.DeskewScannedPages };
private readonly CheckBox _brightContAfterScan = new() { Text = UiStrings.BrightnessContrastAfterScan };
private readonly CheckBox _offsetWidth = new() { Text = UiStrings.OffsetWidth };
@ -104,7 +104,7 @@ public class AdvancedProfileForm : EtoDialogBase
private void UpdateValues(ScanProfile scanProfile)
{
_maximumQuality.Checked = scanProfile.MaxQuality;
_quality.Value = scanProfile.Quality;
_quality.IntValue = scanProfile.Quality;
_brightContAfterScan.Checked = scanProfile.BrightnessContrastAfterScan;
_deskew.Checked = scanProfile.AutoDeskew;
_offsetWidth.Checked = scanProfile.WiaOffsetWidth;
@ -114,8 +114,8 @@ public class AdvancedProfileForm : EtoDialogBase
_flipDuplexed.Checked = scanProfile.FlipDuplexedPages;
_twainImpl.SelectedIndex = (int) scanProfile.TwainImpl;
_excludeBlank.Checked = scanProfile.ExcludeBlankPages;
_whiteThreshold.Value = scanProfile.BlankPageWhiteThreshold;
_coverageThreshold.Value = scanProfile.BlankPageCoverageThreshold;
_whiteThreshold.IntValue = scanProfile.BlankPageWhiteThreshold;
_coverageThreshold.IntValue = scanProfile.BlankPageCoverageThreshold;
}
private void UpdateEnabled()
@ -132,7 +132,7 @@ public class AdvancedProfileForm : EtoDialogBase
private void SaveSettings()
{
ScanProfile!.Quality = _quality.Value;
ScanProfile!.Quality = _quality.IntValue;
ScanProfile.MaxQuality = _maximumQuality.IsChecked();
ScanProfile.BrightnessContrastAfterScan = _brightContAfterScan.IsChecked();
ScanProfile.AutoDeskew = _deskew.IsChecked();
@ -149,8 +149,8 @@ public class AdvancedProfileForm : EtoDialogBase
ScanProfile.TwainImpl = (TwainImpl) _twainImpl.SelectedIndex;
}
ScanProfile.ExcludeBlankPages = _excludeBlank.IsChecked();
ScanProfile.BlankPageWhiteThreshold = _whiteThreshold.Value;
ScanProfile.BlankPageCoverageThreshold = _coverageThreshold.Value;
ScanProfile.BlankPageWhiteThreshold = _whiteThreshold.IntValue;
ScanProfile.BlankPageCoverageThreshold = _coverageThreshold.IntValue;
}
private void MaximumQuality_CheckedChanged(object? sender, EventArgs e)

View File

@ -20,6 +20,6 @@ public class BlackWhiteForm : ImageFormBase
protected override IEnumerable<Transform> Transforms =>
new Transform[]
{
new BlackWhiteTransform(_thresholdSlider.Value)
new BlackWhiteTransform(_thresholdSlider.IntValue)
};
}

View File

@ -22,7 +22,7 @@ public class BrightContForm : ImageFormBase
protected override IEnumerable<Transform> Transforms =>
new Transform[]
{
new BrightnessTransform(_brightnessSlider.Value),
new TrueContrastTransform(_contrastSlider.Value)
new BrightnessTransform(_brightnessSlider.IntValue),
new TrueContrastTransform(_contrastSlider.IntValue)
};
}

View File

@ -222,8 +222,8 @@ public class EditProfileForm : EtoDialogBase
_paperSource.SelectedIndex = (int) ScanProfile.PaperSource;
_bitDepth.SelectedIndex = (int) ScanProfile.BitDepth;
_resolution.SelectedIndex = (int) ScanProfile.Resolution;
_contrastSlider.Value = ScanProfile.Contrast;
_brightnessSlider.Value = ScanProfile.Brightness;
_contrastSlider.IntValue = ScanProfile.Contrast;
_brightnessSlider.IntValue = ScanProfile.Brightness;
UpdatePageSizeList();
SelectPageSize();
_scale.SelectedIndex = (int) ScanProfile.AfterScanScale;
@ -379,8 +379,8 @@ public class EditProfileForm : EtoDialogBase
AfterScanScale = (ScanScale) _scale.SelectedIndex,
BitDepth = (ScanBitDepth) _bitDepth.SelectedIndex,
Brightness = _brightnessSlider.Value,
Contrast = _contrastSlider.Value,
Brightness = _brightnessSlider.IntValue,
Contrast = _contrastSlider.IntValue,
PageAlign = (ScanHorizontalAlign) _horAlign.SelectedIndex,
PageSize = pageSize.Type,
CustomPageSizeName = pageSize.CustomName,

View File

@ -22,7 +22,7 @@ public class HueSatForm : ImageFormBase
protected override IEnumerable<Transform> Transforms =>
new Transform[]
{
new HueTransform(_hueSlider.Value),
new SaturationTransform(_saturationSlider.Value)
new HueTransform(_hueSlider.IntValue),
new SaturationTransform(_saturationSlider.IntValue)
};
}

View File

@ -150,7 +150,7 @@ public abstract class ImageFormBase : EtoDialogBase
{
foreach (var slider in Sliders)
{
slider.Value = 0;
slider.IntValue = 0;
}
}

View File

@ -10,7 +10,7 @@ public class ImageSettingsForm : EtoDialogBase
{
private readonly FilePathWithPlaceholders _defaultFilePath;
private readonly CheckBox _skipSavePrompt = new() { Text = UiStrings.SkipSavePrompt };
private readonly SliderWithTextBox _jpegQuality = new() { MinValue = 0, MaxValue = 100, TickFrequency = 25 };
private readonly SliderWithTextBox _jpegQuality = new(new SliderWithTextBox.IntConstraints(0, 100, 25));
private readonly CheckBox _singlePageTiff = new() { Text = UiStrings.SinglePageFiles };
private readonly DropDown _compression = C.EnumDropDown<TiffCompression>();
private readonly CheckBox _rememberSettings = new() { Text = UiStrings.RememberTheseSettings };
@ -70,7 +70,7 @@ public class ImageSettingsForm : EtoDialogBase
{
_defaultFilePath.Text = config.Get(c => c.ImageSettings.DefaultFileName);
_skipSavePrompt.Checked = config.Get(c => c.ImageSettings.SkipSavePrompt);
_jpegQuality.Value = config.Get(c => c.ImageSettings.JpegQuality);
_jpegQuality.IntValue = config.Get(c => c.ImageSettings.JpegQuality);
_singlePageTiff.Checked = config.Get(c => c.ImageSettings.SinglePageTiff);
_compression.SelectedIndex = (int) config.Get(c => c.ImageSettings.TiffCompression);
_rememberSettings.Checked = config.Get(c => c.RememberImageSettings);
@ -87,7 +87,7 @@ public class ImageSettingsForm : EtoDialogBase
{
DefaultFileName = _defaultFilePath.Text,
SkipSavePrompt = _skipSavePrompt.IsChecked(),
JpegQuality = _jpegQuality.Value,
JpegQuality = _jpegQuality.IntValue,
TiffCompression = (TiffCompression) _compression.SelectedIndex,
SinglePageTiff = _singlePageTiff.IsChecked()
};

View File

@ -8,7 +8,7 @@ public class RotateForm : ImageFormBase
{
private const int MIN_LINE_DISTANCE = 50;
private readonly SliderWithTextBox _angleSlider = new() { MinValue = -1800, MaxValue = 1800, TickFrequency = 450 };
private readonly SliderWithTextBox _angleSlider = new(new SliderWithTextBox.DecimalConstraints(-180, 180, 45, 1));
private bool _guideExists;
private PointF _guideStart, _guideEnd;
@ -29,7 +29,7 @@ public class RotateForm : ImageFormBase
protected override IEnumerable<Transform> Transforms =>
new Transform[]
{
new RotationTransform(_angleSlider.Value / 10.0)
new RotationTransform((double) _angleSlider.DecimalValue)
};
private void Overlay_MouseDown(object? sender, MouseEventArgs e)
@ -56,7 +56,7 @@ public class RotateForm : ImageFormBase
{
angle += 90.0;
}
var oldAngle = _angleSlider.Value / 10.0;
var oldAngle = (double) _angleSlider.DecimalValue;
var newAngle = angle + oldAngle;
while (newAngle > 180.0)
{
@ -66,7 +66,7 @@ public class RotateForm : ImageFormBase
{
newAngle += 360.0;
}
_angleSlider.Value = (int)Math.Round(newAngle * 10);
_angleSlider.DecimalValue = (decimal) newAngle;
}
Overlay.Invalidate();
}
@ -85,7 +85,7 @@ public class RotateForm : ImageFormBase
if (_guideExists)
{
e.Graphics.AntiAlias = true;
e.Graphics.DrawLine(new Pen(new Color(0, 0,0 )), _guideStart, _guideEnd);
e.Graphics.DrawLine(new Pen(new Color(0, 0, 0)), _guideStart, _guideEnd);
}
}
}

View File

@ -20,6 +20,6 @@ public class SharpenForm : ImageFormBase
protected override IEnumerable<Transform> Transforms =>
new Transform[]
{
new SharpenTransform(_sharpenSlider.Value)
new SharpenTransform(_sharpenSlider.IntValue)
};
}

View File

@ -6,45 +6,48 @@ namespace NAPS2.EtoForms.Widgets;
public class SliderWithTextBox
{
private readonly Slider _slider = new() { MinValue = -1000, MaxValue = 1000, TickFrequency = 200 };
private readonly NumericMaskedTextBox<int> _textBox = new() { Text = 0.ToString("G") };
public static readonly Constraints DefaultConstraints = new IntConstraints(-1000, 1000, 200);
private readonly Constraints _constraints;
private readonly Slider _slider = new();
private readonly TextBox _textBox;
private int _valueCache;
public SliderWithTextBox()
public SliderWithTextBox() : this(DefaultConstraints)
{
_slider.ValueChanged += (_, _) => { Value = _slider.Value; };
}
public SliderWithTextBox(Constraints constraints)
{
_constraints = constraints;
_textBox = constraints.IsInteger
? new NumericMaskedTextBox<int>()
: new NumericMaskedTextBox<double>();
_slider.MinValue = constraints.SliderMinValue;
_slider.MaxValue = constraints.SliderMaxValue;
_slider.TickFrequency = constraints.SliderTickFrequency;
_textBox.Text = 0.ToString("G");
_slider.ValueChanged += (_, _) => { IntValue = _slider.Value; };
_textBox.TextChanged += (_, _) =>
{
if (int.TryParse(_textBox.Text, out int value))
if (double.TryParse(_textBox.Text, out double value))
{
if (value >= MinValue && value <= MaxValue)
if (!constraints.IsInteger)
{
Value = value;
value *= constraints.Multiplier;
}
value = Math.Round(value);
if (value >= constraints.SliderMinValue && value <= constraints.SliderMaxValue)
{
IntValue = (int) value;
}
}
};
}
public int MinValue
{
get => _slider.MinValue;
set => _slider.MinValue = value;
}
public int MaxValue
{
get => _slider.MaxValue;
set => _slider.MaxValue = value;
}
public int TickFrequency
{
get => _slider.TickFrequency;
set => _slider.TickFrequency = value;
}
public int Value
public int IntValue
{
get => _valueCache;
set
@ -52,11 +55,19 @@ public class SliderWithTextBox
if (value == _valueCache) return;
_valueCache = value;
_slider.Value = value;
_textBox.Text = value.ToString("G");
_textBox.Text = _constraints.IsInteger
? value.ToString("G")
: (value / (decimal) _constraints.Multiplier).ToString("G");
ValueChanged?.Invoke();
}
}
public decimal DecimalValue
{
get => IntValue / (decimal) _constraints.Multiplier;
set => IntValue = (int) Math.Round(value * _constraints.Multiplier);
}
public bool Enabled
{
get => _slider.Enabled;
@ -89,4 +100,37 @@ public class SliderWithTextBox
.Align(EtoPlatform.Current.IsWinForms ? LayoutAlignment.Leading : LayoutAlignment.Center)
);
}
public abstract class Constraints
{
public bool IsInteger { get; protected init; }
public int Multiplier { get; protected init; }
public int SliderMinValue { get; protected init; }
public int SliderMaxValue { get; protected init; }
public int SliderTickFrequency { get; protected init; }
}
public class IntConstraints : Constraints
{
public IntConstraints(int minValue, int maxValue, int tickFrequency)
{
IsInteger = true;
Multiplier = 1;
SliderMinValue = minValue;
SliderMaxValue = maxValue;
SliderTickFrequency = tickFrequency;
}
}
public class DecimalConstraints : Constraints
{
public DecimalConstraints(decimal minValue, decimal maxValue, decimal tickFrequency, int decimalPlaces)
{
IsInteger = false;
Multiplier = (int) Math.Pow(10, decimalPlaces);
SliderMinValue = (int) (minValue * Multiplier);
SliderMaxValue = (int) (maxValue * Multiplier);
SliderTickFrequency = (int) (tickFrequency * Multiplier);
}
}
}