mirror of https://github.com/HandBrake/HandBrake
1032 lines
41 KiB
C#
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);
|
|
}
|
|
}
|
|
} |