HandBrake/win/CS/HandBrakeWPF/ViewModels/PictureSettingsViewModel.cs

1032 lines
41 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PictureSettingsViewModel.cs" company="HandBrake Project (http://handbrake.fr)">
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
// </copyright>
// <summary>
// The Picture Settings View Model
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrakeWPF.ViewModels
{
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using HandBrake.App.Core.Utilities;
using HandBrake.Interop.Interop;
using HandBrake.Interop.Interop.Interfaces.Model.Picture;
using HandBrakeWPF.EventArgs;
using HandBrakeWPF.Model;
using HandBrakeWPF.Model.Filters;
using HandBrakeWPF.Model.Picture;
using HandBrakeWPF.Properties;
using HandBrakeWPF.Services.Interfaces;
using HandBrakeWPF.Services.Presets.Model;
using HandBrakeWPF.Services.Scan.Model;
using HandBrakeWPF.ViewModelItems.Filters;
using HandBrakeWPF.ViewModels.Interfaces;
using HandBrakeWPF.Views;
using EncodeTask = Services.Encode.Model.EncodeTask;
using Size = Model.Picture.Size;
public class PictureSettingsViewModel : ViewModelBase, IPictureSettingsViewModel
{
private readonly IWindowManager windowManager;
private bool heightControlEnabled = true;
private string sourceInfo;
private Size sourceParValues;
private Size sourceResolution;
private bool widthControlEnabled = true;
private DelayedActionProcessor delayedPreviewprocessor = new DelayedActionProcessor();
private Title currentTitle;
private Source scannedSource;
private PictureSettingsResLimitModes selectedPictureSettingsResLimitMode;
public PictureSettingsViewModel(IStaticPreviewViewModel staticPreviewViewModel, IWindowManager windowManager)
{
this.windowManager = windowManager;
this.StaticPreviewViewModel = staticPreviewViewModel;
this.StaticPreviewViewModel.SetPictureSettingsInstance(this);
this.sourceResolution = new Size(0, 0);
this.Task = new EncodeTask();
this.PaddingFilter = new PadFilter(this.Task, () => this.OnFilterChanged(null));
this.RotateFlipFilter = new RotateFlipFilter(this.Task, e => this.OnFlipRotateChanged(e));
this.Init();
}
/* Events */
public event EventHandler<TabStatusEventArgs> TabStatusChanged;
/* Properties */
public IStaticPreviewViewModel StaticPreviewViewModel { get; set; }
public BindingList<AnamorphicMode> AnamorphicModes { get; } = new BindingList<AnamorphicMode> { AnamorphicMode.None, AnamorphicMode.Automatic, AnamorphicMode.Custom };
public PadFilter PaddingFilter { get; set; }
public RotateFlipFilter RotateFlipFilter { get; set; }
public string DisplaySize { get; set; }
public string OutputAspect { get; set; }
public bool HeightControlEnabled
{
get => this.heightControlEnabled;
set
{
this.heightControlEnabled = value;
this.NotifyOfPropertyChange(() => this.HeightControlEnabled);
}
}
public string SourceInfo
{
get => this.sourceInfo;
set
{
this.sourceInfo = value;
this.NotifyOfPropertyChange(() => this.SourceInfo);
}
}
public EncodeTask Task { get; set; }
public bool WidthControlEnabled
{
get => this.widthControlEnabled;
set
{
this.widthControlEnabled = value;
this.NotifyOfPropertyChange(() => this.WidthControlEnabled);
}
}
public int? MaxHeight
{
get => this.Task.MaxHeight;
set
{
if (value != this.Task.MaxHeight)
{
this.Task.MaxHeight = value;
this.NotifyOfPropertyChange(() => this.MaxHeight);
this.OnTabStatusChanged(null);
}
}
}
public int? MaxWidth
{
get => this.Task.MaxWidth;
set
{
if (value != this.Task.MaxWidth)
{
this.Task.MaxWidth = value;
this.NotifyOfPropertyChange(() => this.MaxWidth);
this.OnTabStatusChanged(null);
}
}
}
public BindingList<PictureSettingsResLimitModes> ResolutionLimitModes => new BindingList<PictureSettingsResLimitModes>
{
PictureSettingsResLimitModes.Size8K,
PictureSettingsResLimitModes.Size4K,
PictureSettingsResLimitModes.Size1080p,
PictureSettingsResLimitModes.Size720p,
PictureSettingsResLimitModes.Size576p,
PictureSettingsResLimitModes.Size480p,
PictureSettingsResLimitModes.Custom,
};
public PictureSettingsResLimitModes SelectedPictureSettingsResLimitMode
{
get => this.selectedPictureSettingsResLimitMode;
set
{
if (value == this.selectedPictureSettingsResLimitMode)
{
return;
}
this.selectedPictureSettingsResLimitMode = value;
this.NotifyOfPropertyChange(() => this.SelectedPictureSettingsResLimitMode);
this.IsCustomMaxRes = value == PictureSettingsResLimitModes.Custom;
this.NotifyOfPropertyChange(() => this.IsCustomMaxRes);
// Enforce the new limit
ResLimit limit = EnumHelper<PictureSettingsResLimitModes>.GetAttribute<ResLimit, PictureSettingsResLimitModes>(value);
if (value == PictureSettingsResLimitModes.Custom && this.Task.MaxWidth == null && this.Task.MaxHeight == null)
{
// Default to 4K if null!
limit = EnumHelper<PictureSettingsResLimitModes>.GetAttribute<ResLimit, PictureSettingsResLimitModes>(PictureSettingsResLimitModes.Size4K);
}
if (limit != null)
{
this.Task.MaxWidth = limit.Width;
this.Task.MaxHeight = limit.Height;
}
if (value == PictureSettingsResLimitModes.None)
{
this.Task.MaxWidth = null;
this.Task.MaxHeight = null;
}
this.NotifyOfPropertyChange(() => this.MaxWidth);
this.NotifyOfPropertyChange(() => this.MaxHeight);
this.RecalculatePictureSettingsProperties(ChangedPictureField.ResolutionLimit);
this.OnTabStatusChanged(null);
}
}
public bool IsCustomMaxRes { get; private set; }
public bool OptimalSize
{
get => this.Task.OptimalSize;
set
{
this.Task.OptimalSize = value;
this.UpdateVisibleControls();
this.Task.Width = this.sourceResolution.Width;
this.NotifyOfPropertyChange(() => this.OptimalSize);
this.RecalculatePictureSettingsProperties(ChangedPictureField.OptimalSize);
}
}
public bool AllowUpscaling
{
get => this.Task.AllowUpscaling;
set
{
this.Task.AllowUpscaling = value;
this.UpdateVisibleControls();
this.RecalculatePictureSettingsProperties(ChangedPictureField.AllowUpscale);
this.NotifyOfPropertyChange(() => this.AllowUpscaling);
}
}
/* Task Properties */
public int CropBottom
{
get => this.Task.Cropping.Bottom;
set
{
if (!ValidCropTB(value, this.CropTop))
{
return; // Don't accept user input.
}
this.Task.Cropping.Bottom = value;
this.NotifyOfPropertyChange(() => this.CropBottom);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Crop);
}
}
public int CropLeft
{
get => this.Task.Cropping.Left;
set
{
if (!ValidCropLR(value, this.CropRight))
{
return; // Don't accept user input.
}
this.Task.Cropping.Left = value;
this.NotifyOfPropertyChange(() => this.CropLeft);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Crop);
}
}
public int CropRight
{
get => this.Task.Cropping.Right;
set
{
if (!ValidCropLR(value, this.CropLeft))
{
return; // Don't accept user input.
}
this.Task.Cropping.Right = value;
this.NotifyOfPropertyChange(() => this.CropRight);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Crop);
}
}
public int CropTop
{
get => this.Task.Cropping.Top;
set
{
if (!ValidCropTB(value, this.CropBottom))
{
return; // Don't accept user input.
}
this.Task.Cropping.Top = value;
this.NotifyOfPropertyChange(() => this.CropTop);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Crop);
}
}
public int MaxCropLR => currentTitle?.Resolution.Width - 8 ?? 0;
public int MaxCropTB => currentTitle?.Resolution.Height - 8 ?? 0;
public BindingList<CropMode> CropModes { get; } = new BindingList<CropMode> { CropMode.Automatic, CropMode.Loose, CropMode.None, CropMode.Custom };
public CropMode SelectedCropMode
{
get
{
return (CropMode)this.Task.Cropping.CropMode;
}
set
{
this.Task.Cropping.CropMode = (int)value;
this.NotifyOfPropertyChange(() => this.SelectedCropMode);
this.OnTabStatusChanged(null);
if (value != CropMode.Custom && this.currentTitle != null)
{
if (value == CropMode.Automatic)
{
this.Task.Cropping.Top = currentTitle.AutoCropDimensions.Top;
this.Task.Cropping.Bottom = currentTitle.AutoCropDimensions.Bottom;
this.Task.Cropping.Left = currentTitle.AutoCropDimensions.Left;
this.Task.Cropping.Right = currentTitle.AutoCropDimensions.Right;
}
else if (value == CropMode.Loose)
{
this.Task.Cropping.Top = currentTitle.LooseCropDimensions.Top;
this.Task.Cropping.Bottom = currentTitle.LooseCropDimensions.Bottom;
this.Task.Cropping.Left = currentTitle.LooseCropDimensions.Left;
this.Task.Cropping.Right = currentTitle.LooseCropDimensions.Right;
}
else
{
this.Task.Cropping.Top = 0;
this.Task.Cropping.Bottom = 0;
this.Task.Cropping.Left = 0;
this.Task.Cropping.Right = 0;
}
}
this.NotifyOfPropertyChange(() => this.CropLeft);
this.NotifyOfPropertyChange(() => this.CropRight);
this.NotifyOfPropertyChange(() => this.CropTop);
this.NotifyOfPropertyChange(() => this.CropBottom);
this.NotifyOfPropertyChange(() => this.IsCustomCrop);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Crop);
}
}
public bool IsCustomCrop => this.SelectedCropMode == CropMode.Custom;
public int DisplayWidth
{
get
{
if (this.Task.DisplayWidth.HasValue && !double.IsInfinity(this.Task.DisplayWidth.Value))
{
if (int.TryParse(Math.Round(this.Task.DisplayWidth.Value, 0).ToString(CultureInfo.InvariantCulture), out int value))
{
return value;
}
}
return 0;
}
set
{
if (!object.Equals(this.Task.DisplayWidth, value))
{
this.Task.DisplayWidth = value;
this.NotifyOfPropertyChange(() => this.DisplayWidth);
this.RecalculatePictureSettingsProperties(ChangedPictureField.DisplayWidth);
}
}
}
public int DisplayHeight { get; set; }
public int Width
{
get => this.Task.Width.HasValue ? this.Task.Width.Value : 0;
set
{
if (!object.Equals(this.Task.Width, value))
{
this.Task.Width = value;
this.NotifyOfPropertyChange(() => this.Width);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Width);
}
}
}
public int Height
{
get => this.Task.Height.HasValue ? this.Task.Height.Value : 0;
set
{
if (!object.Equals(this.Task.Height, value))
{
this.Task.Height = value;
this.NotifyOfPropertyChange(() => this.Height);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Height);
}
}
}
public bool MaintainAspectRatio
{
get => this.Task.KeepDisplayAspect;
set
{
this.Task.KeepDisplayAspect = value;
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
this.RecalculatePictureSettingsProperties(ChangedPictureField.MaintainAspectRatio);
}
}
public int ParHeight
{
get => this.Task.PixelAspectY;
set
{
if (!object.Equals(this.Task.PixelAspectY, value))
{
this.Task.PixelAspectY = value;
this.NotifyOfPropertyChange(() => this.ParHeight);
this.RecalculatePictureSettingsProperties(ChangedPictureField.ParH);
}
}
}
public int ParWidth
{
get => this.Task.PixelAspectX;
set
{
if (!object.Equals(this.Task.PixelAspectX, value))
{
this.Task.PixelAspectX = value;
this.NotifyOfPropertyChange(() => this.ParWidth);
this.RecalculatePictureSettingsProperties(ChangedPictureField.ParW);
}
}
}
public AnamorphicMode SelectedAnamorphicMode
{
get => (AnamorphicMode)this.Task.Anamorphic;
set
{
if (!object.Equals(this.SelectedAnamorphicMode, value))
{
this.Task.Anamorphic = (Anamorphic)value;
this.NotifyOfPropertyChange(() => this.SelectedAnamorphicMode);
this.RecalculatePictureSettingsProperties(ChangedPictureField.Anamorphic);
this.OnTabStatusChanged(null);
this.IsPixelAspectSettable = value == AnamorphicMode.Custom;
this.NotifyOfPropertyChange(() => this.IsPixelAspectSettable);
}
}
}
public bool IsPixelAspectSettable { get; set; }
/* Public Tab API Implementation */
public void SetPreset(Preset preset, EncodeTask task)
{
this.Task = task;
// Cropping
if ((CropMode)preset.Task.Cropping.CropMode == CropMode.Custom)
{
this.SelectedCropMode = CropMode.Custom;
this.Task.Cropping.Left = preset.Task.Cropping.Left;
this.Task.Cropping.Right = preset.Task.Cropping.Right;
this.Task.Cropping.Top = preset.Task.Cropping.Top;
this.Task.Cropping.Bottom = preset.Task.Cropping.Bottom;
this.NotifyOfPropertyChange(() => this.CropLeft);
this.NotifyOfPropertyChange(() => this.CropRight);
this.NotifyOfPropertyChange(() => this.CropTop);
this.NotifyOfPropertyChange(() => this.CropBottom);
}
else
{
this.SelectedCropMode = (CropMode)preset.Task.Cropping.CropMode;
}
// Padding and Rotate Filters
this.PaddingFilter.SetPreset(preset, task);
this.RotateFlipFilter?.SetPreset(preset, task);
// Picture Sizes and Anamorphic
this.SelectedAnamorphicMode = (AnamorphicMode)preset.Task.Anamorphic;
this.OptimalSize = preset.Task.OptimalSize;
this.AllowUpscaling = preset.Task.AllowUpscaling;
// Set the Maintain Aspect ratio.
this.MaintainAspectRatio = preset.Task.KeepDisplayAspect;
// Setup the Maximum Width / Height
this.MaxWidth = preset.Task.MaxWidth;
this.MaxHeight = preset.Task.MaxHeight;
this.SetSelectedPictureSettingsResLimitMode();
// Set the width, then check the height doesn't breach the max height and correct if necessary.
int width = this.GetModulusValue(this.GetRes((this.sourceResolution.Width - this.CropLeft - this.CropRight), this.MaxWidth));
int height = this.GetModulusValue(this.GetRes((this.sourceResolution.Height - this.CropTop - this.CropBottom), this.MaxHeight));
// Set the backing fields to avoid triggering re-calculation until both are set.
this.Task.Width = width;
this.Task.Height = height;
// Trigger a calculation
this.RecalculatePictureSettingsProperties(ChangedPictureField.Width);
// Update the UI
this.NotifyOfPropertyChange(() => this.Width);
this.NotifyOfPropertyChange(() => this.Height);
// Custom Anamorphic
if (preset.Task.Anamorphic == Anamorphic.Custom)
{
this.DisplayWidth = preset.Task.DisplayWidth != null ? int.Parse(preset.Task.DisplayWidth.ToString()) : 0;
this.ParWidth = preset.Task.PixelAspectX == 0 ? 1 : preset.Task.PixelAspectX;
this.ParHeight = preset.Task.PixelAspectY == 0 ? 1 : preset.Task.PixelAspectY;
}
this.NotifyOfPropertyChange(() => this.Task);
this.UpdateVisibleControls();
}
public void UpdateTask(EncodeTask task)
{
this.Task = task;
this.SetSelectedPictureSettingsResLimitMode();
this.PaddingFilter.UpdateTask(task);
this.RotateFlipFilter?.UpdateTask(task);
this.NotifyOfPropertyChange(() => this.Width);
this.NotifyOfPropertyChange(() => this.Height);
this.NotifyOfPropertyChange(() => this.SelectedAnamorphicMode);
this.NotifyOfPropertyChange(() => this.CropTop);
this.NotifyOfPropertyChange(() => this.CropBottom);
this.NotifyOfPropertyChange(() => this.CropLeft);
this.NotifyOfPropertyChange(() => this.CropRight);
this.NotifyOfPropertyChange(() => this.SelectedCropMode);
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
this.NotifyOfPropertyChange(() => this.DisplayWidth);
this.NotifyOfPropertyChange(() => this.ParWidth);
this.NotifyOfPropertyChange(() => this.ParHeight);
this.NotifyOfPropertyChange(() => this.MaxWidth);
this.NotifyOfPropertyChange(() => this.MaxHeight);
this.NotifyOfPropertyChange(() => this.IsCustomCrop);
this.UpdateVisibleControls();
}
public void SetSource(Source source, Title title, Preset preset, EncodeTask task)
{
this.currentTitle = title;
this.Task = task;
this.PaddingFilter.SetSource(source, title, preset, task);
this.RotateFlipFilter?.SetSource(source, title, preset, task);
this.scannedSource = source;
if (title != null)
{
// Set cached info
this.sourceParValues = title.ParVal;
this.sourceResolution = title.Resolution;
// Update the cropping values, preferring those in the presets.
if ((CropMode)preset.Task.Cropping.CropMode == CropMode.Custom)
{
this.Task.Cropping.Left = preset.Task.Cropping.Left;
this.Task.Cropping.Right = preset.Task.Cropping.Right;
this.Task.Cropping.Top = preset.Task.Cropping.Top;
this.Task.Cropping.Bottom = preset.Task.Cropping.Bottom;
this.SelectedCropMode = CropMode.Custom;
}
else
{
if ((CropMode)preset.Task.Cropping.CropMode == CropMode.Automatic)
{
this.Task.Cropping.Top = title.AutoCropDimensions.Top;
this.Task.Cropping.Bottom = title.AutoCropDimensions.Bottom;
this.Task.Cropping.Left = title.AutoCropDimensions.Left;
this.Task.Cropping.Right = title.AutoCropDimensions.Right;
}
else if ((CropMode)preset.Task.Cropping.CropMode == CropMode.Loose)
{
this.Task.Cropping.Top = title.LooseCropDimensions.Top;
this.Task.Cropping.Bottom = title.LooseCropDimensions.Bottom;
this.Task.Cropping.Left = title.LooseCropDimensions.Left;
this.Task.Cropping.Right = title.LooseCropDimensions.Right;
}
else
{
this.Task.Cropping.Top = 0;
this.Task.Cropping.Bottom = 0;
this.Task.Cropping.Left = 0;
this.Task.Cropping.Right = 0;
}
this.SelectedCropMode = (CropMode)preset.Task.Cropping.CropMode;
}
// Set the W/H
// Set the width, then check the height doesn't breach the max height and correct if necessary.
this.Task.Width = this.GetModulusValue(this.GetRes((this.sourceResolution.Width - this.CropLeft - this.CropRight), this.MaxWidth));
this.Task.Height = this.GetModulusValue(this.GetRes((this.sourceResolution.Height - this.CropTop - this.CropBottom), this.MaxHeight));
// Set Screen Controls
// "Storage Size: {0}x{1} Display Size: {2}x{3} Aspect Ratio: {4}",
double sourceDisplayWidth = (double)title.Resolution.Width * title.ParVal.Width / title.ParVal.Height;
this.SourceInfo = string.Format(
Resources.PictureSettingsViewModel_SourceInfo,
title.Resolution.Width,
title.Resolution.Height,
sourceDisplayWidth,
title.Resolution.Height,
HandBrakePictureHelpers.GetNiceDisplayAspect(sourceDisplayWidth, title.Resolution.Height));
// Force a re-calc. This will handle MaxWidth / Height corrections.
this.RecalculatePictureSettingsProperties(ChangedPictureField.Width);
}
this.NotifyOfPropertyChange(() => this.Task);
}
public bool MatchesPreset(Preset preset)
{
if ((AnamorphicMode)preset.Task.Anamorphic != this.SelectedAnamorphicMode)
{
return false;
}
if (preset.Task.MaxHeight != this.MaxHeight)
{
return false;
}
if (preset.Task.MaxWidth != this.MaxWidth)
{
return false;
}
if ((CropMode)preset.Task.Cropping.CropMode != this.SelectedCropMode)
{
return false;
}
if (!PaddingFilter.MatchesPreset(preset))
{
return false;
}
if (!RotateFlipFilter.MatchesPreset(preset))
{
return false;
}
return true;
}
/* Protected and Private Methods */
public void OpenPreviewWindow()
{
if (!string.IsNullOrEmpty(this.Task.Source) && !this.StaticPreviewViewModel.IsOpen)
{
this.StaticPreviewViewModel.IsOpen = true;
this.StaticPreviewViewModel.UpdatePreviewFrame(this.currentTitle, this.Task, this.scannedSource);
this.windowManager.ShowWindow<StaticPreviewView>(this.StaticPreviewViewModel);
}
else if (this.StaticPreviewViewModel.IsOpen)
{
Window window = Application.Current.Windows.Cast<Window>().FirstOrDefault(x => x.GetType() == typeof(StaticPreviewView));
window?.Focus();
}
}
protected virtual void OnTabStatusChanged(TabStatusEventArgs e)
{
this.TabStatusChanged?.Invoke(this, e);
}
protected virtual void OnFilterChanged(TabStatusEventArgs e)
{
RecalculatePictureSettingsProperties(ChangedPictureField.Padding);
this.TabStatusChanged?.Invoke(this, e);
}
protected virtual void OnFlipRotateChanged(FlipRotationCommand e)
{
this.HandleRotationFlipChange(e);
this.TabStatusChanged?.Invoke(this, new TabStatusEventArgs(null));
}
private void Init()
{
this.Task.KeepDisplayAspect = true;
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
// Default the Max Width / Height to 4K format
this.MaxHeight = 2160;
this.MaxWidth = 3480;
this.SetSelectedPictureSettingsResLimitMode();
}
private PictureSettingsTitle GetPictureTitleInfo()
{
PictureSettingsTitle title = new PictureSettingsTitle
{
Width = this.sourceResolution.Width,
Height = this.sourceResolution.Height,
ParW = this.sourceParValues.Width,
ParH = this.sourceParValues.Height,
};
return title;
}
private PictureSettingsJob GetPictureSettings(ChangedPictureField changedField)
{
PictureSettingsJob job = new PictureSettingsJob
{
Width = this.Width,
Height = this.Height,
ItuPar = false,
ParW = this.SelectedAnamorphicMode == AnamorphicMode.None ? 1 : this.ParWidth,
ParH = this.SelectedAnamorphicMode == AnamorphicMode.None ? 1 : this.ParHeight,
MaxWidth = this.MaxWidth.HasValue ? this.MaxWidth.Value : 0,
MaxHeight = this.MaxHeight.HasValue ? this.MaxHeight.Value : 0,
KeepDisplayAspect = this.MaintainAspectRatio,
AnamorphicMode = (Anamorphic)this.SelectedAnamorphicMode,
Crop = new Cropping(this.CropTop, this.CropBottom, this.CropLeft, this.CropRight, (int)CropMode.Custom),
Pad = new Padding(this.PaddingFilter.Top, this.PaddingFilter.Bottom, this.PaddingFilter.Left, this.PaddingFilter.Right),
RotateAngle = this.RotateFlipFilter.SelectedRotation,
Hflip = this.RotateFlipFilter.FlipVideo ? 1 : 0,
DarWidth = this.DisplayWidth,
DarHeight = this.DisplayHeight
};
if (this.SelectedAnamorphicMode == AnamorphicMode.Custom)
{
if (changedField == ChangedPictureField.DisplayWidth)
{
var displayWidth = this.DisplayWidth;
job.ParW = (int)displayWidth; // num
job.ParH = job.Width; // den
}
}
// Reduce the Par W/H if we can. Don't do it while the user is altering the PAR controls through as it will mess with the result.
if (changedField != ChangedPictureField.ParH && changedField != ChangedPictureField.ParW)
{
long x, y;
HandBrakeUtils.Reduce(job.ParW, job.ParH, out x, out y);
job.ParW = (int)y;
job.ParH = (int)x;
}
return job;
}
/// <summary>
/// Recalculate the picture settings when the user changes a particular field defined in the ChangedPictureField enum.
/// The properties in this class are dumb. They simply call this method if there is a change.
/// It is the job of this method to update all affected private fields and raise change notifications.
/// </summary>
/// <param name="changedField">
/// The changed field.
/// </param>
private void RecalculatePictureSettingsProperties(ChangedPictureField changedField)
{
// Sanity Check
if (this.currentTitle == null)
{
return;
}
// Step 1, Update what controls are visible.
this.UpdateVisibleControls();
// Step 2, Set sensible defaults
if (changedField == ChangedPictureField.Anamorphic && (this.SelectedAnamorphicMode == AnamorphicMode.None))
{
this.Task.Width = this.sourceResolution.Width > this.MaxWidth
? this.MaxWidth
: this.sourceResolution.Width;
this.Task.KeepDisplayAspect = true;
}
// Choose which setting to keep.
HandBrakePictureHelpers.KeepSetting setting = 0;
switch (changedField)
{
case ChangedPictureField.Width:
setting = HandBrakePictureHelpers.KeepSetting.HB_KEEP_WIDTH;
break;
case ChangedPictureField.Height:
setting = HandBrakePictureHelpers.KeepSetting.HB_KEEP_HEIGHT;
break;
case ChangedPictureField.DisplayWidth:
setting = HandBrakePictureHelpers.KeepSetting.HB_KEEP_DISPLAY_WIDTH;
break;
case ChangedPictureField.ParW:
case ChangedPictureField.ParH:
setting = HandBrakePictureHelpers.KeepSetting.HB_KEEP_HEIGHT;
break;
case ChangedPictureField.Padding:
setting = HandBrakePictureHelpers.KeepSetting.HB_KEEP_PAD;
break;
}
if (this.MaintainAspectRatio)
{
setting |= HandBrakePictureHelpers.KeepSetting.HB_KEEP_DISPLAY_ASPECT;
}
// Step 2, For the changed field, call hb_set_anamorphic_size and process the results.
HandBrakePictureHelpers.FlagsSetting flag = 0;
if (this.AllowUpscaling)
{
flag = HandBrakePictureHelpers.FlagsSetting.HB_GEO_SCALE_UP;
}
if (this.OptimalSize)
{
flag |= HandBrakePictureHelpers.FlagsSetting.HB_GEO_SCALE_BEST;
}
AnamorphicResult result = HandBrakePictureHelpers.GetAnamorphicSize(this.GetPictureSettings(changedField), this.GetPictureTitleInfo(), setting, flag);
this.Task.Width = result.OutputWidth;
this.Task.Height = result.OutputHeight;
this.Task.PixelAspectX = result.OutputParWidth;
this.Task.PixelAspectY = result.OutputParHeight;
if (this.Task.PixelAspectX == 0)
{
this.Task.PixelAspectX = 1;
}
if (this.Task.PixelAspectY == 0)
{
this.Task.PixelAspectY = 1;
}
this.ApplyPad(this.PaddingFilter.Mode, result); // Update for display purposes.
double dispWidth = Math.Round((double)result.OutputWidth * result.OutputParWidth / result.OutputParHeight, 0);
this.Task.DisplayWidth = dispWidth;
this.DisplayHeight = result.OutputHeight;
// Step 3, Set the display width label to indicate the output.
this.DisplaySize = this.sourceResolution == null || this.sourceResolution.IsEmpty
? string.Empty
: string.Format(Resources.PictureSettingsViewModel_StorageDisplayLabel, result.OutputWidth, result.OutputHeight);
this.NotifyOfPropertyChange(() => this.DisplaySize);
this.OutputAspect = string.Format(Resources.PictureSettingsViewModel_AspectRatioLabel, HandBrakePictureHelpers.GetNiceDisplayAspect(dispWidth, result.OutputHeight));
this.NotifyOfPropertyChange(() => this.OutputAspect);
// Step 4, Force an update on all the UI elements.
this.NotifyOfPropertyChange(() => this.Width);
this.NotifyOfPropertyChange(() => this.Height);
this.NotifyOfPropertyChange(() => this.DisplayWidth);
this.NotifyOfPropertyChange(() => this.ParWidth);
this.NotifyOfPropertyChange(() => this.ParHeight);
this.NotifyOfPropertyChange(() => this.CropTop);
this.NotifyOfPropertyChange(() => this.CropBottom);
this.NotifyOfPropertyChange(() => this.CropLeft);
this.NotifyOfPropertyChange(() => this.CropRight);
this.NotifyOfPropertyChange(() => this.MaintainAspectRatio);
// Step 5, Update the Preview
if (delayedPreviewprocessor != null && this.Task != null && this.StaticPreviewViewModel != null && this.StaticPreviewViewModel.IsOpen)
{
delayedPreviewprocessor.PerformTask(() => this.StaticPreviewViewModel.UpdatePreviewFrame(this.currentTitle, this.Task, this.scannedSource), 800);
}
this.OnTabStatusChanged(new TabStatusEventArgs("picture", ChangedOption.Dimensions));
}
private void ApplyPad(PaddingMode mode, AnamorphicResult result)
{
if (mode == PaddingMode.Custom)
{
result.OutputWidth = result.OutputWidth + this.PaddingFilter.Left + this.PaddingFilter.Right;
result.OutputHeight = result.OutputHeight + this.PaddingFilter.Top + this.PaddingFilter.Bottom;
}
}
private void UpdateVisibleControls()
{
this.WidthControlEnabled = true;
this.HeightControlEnabled = true;
if (OptimalSize)
{
this.WidthControlEnabled = false;
this.HeightControlEnabled = false;
}
}
private int GetModulusValue(double value)
{
double remainder = value % 2;
if (remainder.Equals(0.0d))
{
return (int)Math.Abs(value);
}
double result = remainder >= 1
? value + (2 - remainder)
: value - remainder;
return (int)Math.Abs(result);
}
private int GetRes(int value, int? max)
{
return max.HasValue ? (value > max.Value ? max.Value : value) : value;
}
private void SetSelectedPictureSettingsResLimitMode()
{
if (this.MaxWidth == null && this.MaxHeight == null)
{
this.SelectedPictureSettingsResLimitMode = PictureSettingsResLimitModes.None;
return;
}
// Look for a matching resolution.
foreach (PictureSettingsResLimitModes limit in EnumHelper<PictureSettingsResLimitModes>.GetEnumList())
{
ResLimit resLimit = EnumHelper<PictureSettingsResLimitModes>.GetAttribute<ResLimit, PictureSettingsResLimitModes>(limit);
if (resLimit != null)
{
if (resLimit.Width == this.MaxWidth && resLimit.Height == this.MaxHeight)
{
this.SelectedPictureSettingsResLimitMode = limit;
return;
}
}
}
if (this.MaxWidth.HasValue || this.MaxHeight.HasValue)
{
this.SelectedPictureSettingsResLimitMode = PictureSettingsResLimitModes.Custom;
}
}
private bool ValidCropTB(int value, int value2)
{
int totalCrop = value + value2;
if (totalCrop >= this.currentTitle.Resolution.Height)
{
return false;
}
return true;
}
private bool ValidCropLR(int value, int value2)
{
int totalCrop = value + value2;
if (totalCrop >= this.currentTitle.Resolution.Width)
{
return false;
}
return true;
}
private void HandleRotationFlipChange(FlipRotationCommand command)
{
if (this.currentTitle == null)
{
return;
}
PictureSettingsJob job = this.GetPictureSettings(command.ChangedField);
job.PreviousHflip = command.PreviousHflip;
job.PreviousRotation = command.PreviousRotation;
RotateResult result = HandBrakePictureHelpers.RotateGeometry(job);
this.Task.Cropping.Top = result.CropTop;
this.Task.Cropping.Bottom = result.CropBottom;
this.Task.Cropping.Left = result.CropLeft;
this.Task.Cropping.Right = result.CropRight;
this.NotifyOfPropertyChange(() => this.CropTop);
this.NotifyOfPropertyChange(() => this.CropBottom);
this.NotifyOfPropertyChange(() => this.CropLeft);
this.NotifyOfPropertyChange(() => this.CropRight);
this.PaddingFilter.SetRotationValues(result.PadTop, result.PadBottom, result.PadLeft, result.PadRight);
this.Task.Width = result.Width;
this.Task.Height = result.Height;
this.Task.PixelAspectX = result.ParNum;
this.Task.PixelAspectY = result.ParDen;
this.NotifyOfPropertyChange(() => this.Width);
this.NotifyOfPropertyChange(() => this.Height);
this.NotifyOfPropertyChange(() => this.ParWidth);
this.NotifyOfPropertyChange(() => this.ParHeight);
RecalculatePictureSettingsProperties(ChangedPictureField.Rotate);
}
}
}