Make AR-changing resampling more user-friendly

Add modes where the borders to add or remove are automatically
calculated rather than forcing the user to do it manually, and hopefully
make it a bit clearer what exactly will happen.
This commit is contained in:
Thomas Goyne 2014-05-16 09:20:30 -07:00
parent 7e2780f57a
commit 0f030c45f3
5 changed files with 65 additions and 15 deletions

View file

@ -24,9 +24,10 @@
#include "help_button.h" #include "help_button.h"
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "resolution_resampler.h" #include "resolution_resampler.h"
#include "validators.h"
#include "video_context.h" #include "video_context.h"
#include <boost/rational.hpp> #include <boost/range/size.hpp>
#include <wx/checkbox.h> #include <wx/checkbox.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/spinctrl.h> #include <wx/spinctrl.h>
@ -88,9 +89,9 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
from_script = new wxButton(this, -1, _("From s&cript")); from_script = new wxButton(this, -1, _("From s&cript"));
from_script->Enable(false); from_script->Enable(false);
change_ar = new wxCheckBox(this, -1, _("&Change aspect ratio")); wxString ar_modes[] = {_("Stretch"), _("Add borders"), _("Remove borders"), _("Manual")};
change_ar->SetValidator(wxGenericValidator(&settings.change_ar)); ar_mode = new wxRadioBox(this, -1, _("Aspect Ratio Handling"), wxDefaultPosition,
change_ar->Enable(false); wxDefaultSize, boost::size(ar_modes), ar_modes, 1, 4, MakeEnumBinder(&settings.ar_mode));
// Position the controls // Position the controls
auto margin_sizer = new wxGridSizer(3, 3, 5, 5); auto margin_sizer = new wxGridSizer(3, 3, 5, 5);
@ -121,20 +122,24 @@ DialogResample::DialogResample(agi::Context *c, ResampleSettings &settings)
auto dest_res_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Destination Resolution")); auto dest_res_box = new wxStaticBoxSizer(wxVERTICAL, this, _("Destination Resolution"));
dest_res_box->Add(dest_res_sizer, wxSizerFlags(1).Expand().Border(wxBOTTOM)); dest_res_box->Add(dest_res_sizer, wxSizerFlags(1).Expand().Border(wxBOTTOM));
dest_res_box->Add(change_ar);
auto main_sizer = new wxBoxSizer(wxVERTICAL); auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(margin_box, wxSizerFlags(1).Expand().Border());
main_sizer->Add(source_res_sizer, wxSizerFlags(0).Expand().Border()); main_sizer->Add(source_res_sizer, wxSizerFlags(0).Expand().Border());
main_sizer->Add(dest_res_box, wxSizerFlags(0).Expand().Border()); main_sizer->Add(dest_res_box, wxSizerFlags(0).Expand().Border());
main_sizer->Add(ar_mode, wxSizerFlags(0).Expand().Border());
main_sizer->Add(margin_box, wxSizerFlags(1).Expand().Border());
main_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP), wxSizerFlags().Expand().Border(wxALL & ~wxTOP)); main_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL | wxHELP), wxSizerFlags().Expand().Border(wxALL & ~wxTOP));
SetSizerAndFit(main_sizer); SetSizerAndFit(main_sizer);
CenterOnParent(); CenterOnParent();
TransferDataToWindow();
UpdateButtons();
// Bind events // Bind events
using std::bind; using std::bind;
Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Resample resolution"), wxID_HELP); Bind(wxEVT_BUTTON, bind(&HelpButton::OpenPage, "Resample resolution"), wxID_HELP);
Bind(wxEVT_SPINCTRL, [=](wxCommandEvent&) { UpdateButtons(); }); Bind(wxEVT_SPINCTRL, [=](wxCommandEvent&) { UpdateButtons(); });
Bind(wxEVT_RADIOBOX, [=](wxCommandEvent&) { UpdateButtons(); });
from_video->Bind(wxEVT_BUTTON, &DialogResample::SetDestFromVideo, this); from_video->Bind(wxEVT_BUTTON, &DialogResample::SetDestFromVideo, this);
from_script->Bind(wxEVT_BUTTON, &DialogResample::SetSourceFromScript, this); from_script->Bind(wxEVT_BUTTON, &DialogResample::SetSourceFromScript, this);
symmetrical->Bind(wxEVT_CHECKBOX, &DialogResample::OnSymmetrical, this); symmetrical->Bind(wxEVT_CHECKBOX, &DialogResample::OnSymmetrical, this);
@ -157,9 +162,18 @@ void DialogResample::UpdateButtons() {
(dest_x->GetValue() != video_w || dest_y->GetValue() != video_h)); (dest_x->GetValue() != video_w || dest_y->GetValue() != video_h));
from_script->Enable(source_x->GetValue() != script_w || source_y->GetValue() != script_h); from_script->Enable(source_x->GetValue() != script_w || source_y->GetValue() != script_h);
boost::rational<int> source_ar(source_x->GetValue(), source_y->GetValue()); auto source_ar = double(source_x->GetValue()) / source_y->GetValue();
boost::rational<int> dest_ar(dest_x->GetValue(), dest_y->GetValue()); auto dest_ar = double(dest_x->GetValue()) / dest_y->GetValue();
change_ar->Enable(source_ar != dest_ar); bool ar_changed = abs(source_ar - dest_ar) / dest_ar > .01;
ar_mode->Enable(ar_changed);
bool margins = ar_changed && ar_mode->GetSelection() == (int)ResampleARMode::Manual;
symmetrical->Enable(margins);
margin_ctrl[LEFT]->Enable(margins);
margin_ctrl[TOP]->Enable(margins);
margin_ctrl[RIGHT]->Enable(margins && !symmetrical->GetValue());
margin_ctrl[BOTTOM]->Enable(margins && !symmetrical->GetValue());
} }
void DialogResample::OnSymmetrical(wxCommandEvent &) { void DialogResample::OnSymmetrical(wxCommandEvent &) {

View file

@ -22,6 +22,7 @@
namespace agi { struct Context; } namespace agi { struct Context; }
class AssFile; class AssFile;
class wxCheckBox; class wxCheckBox;
class wxRadioBox;
class wxSpinCtrl; class wxSpinCtrl;
struct ResampleSettings; struct ResampleSettings;
@ -42,7 +43,7 @@ class DialogResample final : public wxDialog {
wxSpinCtrl *dest_x; wxSpinCtrl *dest_x;
wxSpinCtrl *dest_y; wxSpinCtrl *dest_y;
wxCheckBox *symmetrical; wxCheckBox *symmetrical;
wxCheckBox *change_ar; wxRadioBox *ar_mode;
wxSpinCtrl *margin_ctrl[4]; wxSpinCtrl *margin_ctrl[4];
wxButton *from_script; wxButton *from_script;

View file

@ -154,6 +154,36 @@ namespace {
} }
void ResampleResolution(AssFile *ass, ResampleSettings settings) { void ResampleResolution(AssFile *ass, ResampleSettings settings) {
auto horizontal_stretch = 1.0;
auto old_ar = double(settings.source_x) / settings.source_y;
auto new_ar = double(settings.dest_x) / settings.dest_y;
bool border_horizontally = new_ar > old_ar;
// Don't convert aspect ratio if it's very close to correct
// (for reference, 848x480 <-> 1280x720 is .006)
if (abs(old_ar - new_ar) / new_ar > .01) {
switch (settings.ar_mode) {
case ResampleARMode::RemoveBorder:
border_horizontally = !border_horizontally;
case ResampleARMode::AddBorder:
if (border_horizontally) // Wider/Shorter
settings.margin[LEFT] = settings.margin[RIGHT] = (settings.source_y * new_ar - settings.source_x) / 2;
else // Taller/Narrower
settings.margin[TOP] = settings.margin[BOTTOM] = (settings.source_x / new_ar - settings.source_y) / 2;
break;
case ResampleARMode::Stretch:
horizontal_stretch = new_ar / old_ar;
break;
case ResampleARMode::Manual:
old_ar =
double(settings.source_x + settings.margin[LEFT] + settings.margin[RIGHT]) /
double(settings.source_y + settings.margin[TOP] + settings.margin[BOTTOM]);
if (abs(old_ar - new_ar) / new_ar > .01)
horizontal_stretch = new_ar / old_ar;
break;
}
}
// Add margins to original resolution // Add margins to original resolution
settings.source_x += settings.margin[LEFT] + settings.margin[RIGHT]; settings.source_x += settings.margin[LEFT] + settings.margin[RIGHT];
settings.source_y += settings.margin[TOP] + settings.margin[BOTTOM]; settings.source_y += settings.margin[TOP] + settings.margin[BOTTOM];
@ -162,12 +192,9 @@ void ResampleResolution(AssFile *ass, ResampleSettings settings) {
settings.margin, settings.margin,
double(settings.dest_x) / double(settings.source_x), double(settings.dest_x) / double(settings.source_x),
double(settings.dest_y) / double(settings.source_y), double(settings.dest_y) / double(settings.source_y),
1.0 horizontal_stretch
}; };
if (settings.change_ar)
state.ar = state.rx / state.ry;
for (auto& line : ass->Styles) for (auto& line : ass->Styles)
resample_style(&state, line); resample_style(&state, line);
for (auto& line : ass->Events) for (auto& line : ass->Events)

View file

@ -16,6 +16,13 @@
class AssFile; class AssFile;
enum class ResampleARMode {
Stretch,
AddBorder,
RemoveBorder,
Manual
};
/// Configuration parameters for a resample /// Configuration parameters for a resample
struct ResampleSettings { struct ResampleSettings {
int margin[4]; ///< Amount to add to each margin int margin[4]; ///< Amount to add to each margin
@ -23,7 +30,7 @@ struct ResampleSettings {
int source_y; ///< Original Y resolution int source_y; ///< Original Y resolution
int dest_x; ///< New X resolution int dest_x; ///< New X resolution
int dest_y; ///< New Y resolution int dest_y; ///< New Y resolution
bool change_ar; ///< Should the aspect ratio of the subs be changed? ResampleARMode ar_mode; ///< What to do if the old AR and new AR don't match
}; };
/// Resample the subtitles in the project /// Resample the subtitles in the project

View file

@ -76,6 +76,7 @@ class EnumBinder final : public wxValidator {
T *value; T *value;
wxObject *Clone() const override { return new EnumBinder<T>(value); } wxObject *Clone() const override { return new EnumBinder<T>(value); }
bool Validate(wxWindow *) override { return true; }
bool TransferFromWindow() override { bool TransferFromWindow() override {
if (wxRadioBox *rb = dynamic_cast<wxRadioBox*>(GetWindow())) if (wxRadioBox *rb = dynamic_cast<wxRadioBox*>(GetWindow()))