diff --git a/aegisub/build/Aegisub/Aegisub.vcxproj b/aegisub/build/Aegisub/Aegisub.vcxproj index 10152cc75..a698646fb 100644 --- a/aegisub/build/Aegisub/Aegisub.vcxproj +++ b/aegisub/build/Aegisub/Aegisub.vcxproj @@ -209,6 +209,7 @@ + @@ -405,6 +406,7 @@ + diff --git a/aegisub/build/Aegisub/Aegisub.vcxproj.filters b/aegisub/build/Aegisub/Aegisub.vcxproj.filters index 8d778257d..5a7274c26 100644 --- a/aegisub/build/Aegisub/Aegisub.vcxproj.filters +++ b/aegisub/build/Aegisub/Aegisub.vcxproj.filters @@ -687,6 +687,9 @@ Automation\Lua + + Features\Resolution resampler + @@ -1238,6 +1241,9 @@ Automation\Lua + + Features\Resolution resampler + diff --git a/aegisub/src/Makefile b/aegisub/src/Makefile index c7acc3f58..6782a83c4 100644 --- a/aegisub/src/Makefile +++ b/aegisub/src/Makefile @@ -204,6 +204,7 @@ SRC += \ plugin_manager.cpp \ preferences.cpp \ preferences_base.cpp \ + resolution_resampler.cpp \ scintilla_text_ctrl.cpp \ scintilla_text_selection_controller.cpp \ search_replace_engine.cpp \ diff --git a/aegisub/src/ass_dialogue.cpp b/aegisub/src/ass_dialogue.cpp index 03a2721d8..8411b9ac9 100644 --- a/aegisub/src/ass_dialogue.cpp +++ b/aegisub/src/ass_dialogue.cpp @@ -285,34 +285,3 @@ AssEntry *AssDialogue::Clone() const { *const_cast(&clone->Id) = Id; return clone; } - -void AssDialogueBlockDrawing::TransformCoords(int mx, int my, double x, double y) { - // HACK: Implement a proper parser ffs!! - // Could use Spline but it'd be slower and this seems to work fine - bool is_x = true; - std::string final; - - for (auto const& cur : agi::Split(text, ' ')) { - if (std::all_of(begin(cur), end(cur), isdigit)) { - int val = boost::lexical_cast(agi::str(cur)); - if (is_x) - val = (int)((val + mx) * x + .5); - else - val = (int)((val + my) * y + .5); - final += std::to_string(val); - final += ' '; - } - else if (cur.size() == 1) { - char c = tolower(cur[0]); - if (c == 'm' || c == 'n' || c == 'l' || c == 'b' || c == 's' || c == 'p' || c == 'c') { - is_x = true; - final += c; - final += ' '; - } - } - } - - // Write back final - final.pop_back(); - text = final; -} diff --git a/aegisub/src/ass_dialogue.h b/aegisub/src/ass_dialogue.h index 0fb287a7f..e3b085640 100644 --- a/aegisub/src/ass_dialogue.h +++ b/aegisub/src/ass_dialogue.h @@ -97,11 +97,11 @@ public: class AssDialogueBlockDrawing : public AssDialogueBlock { public: + using AssDialogueBlock::text; int Scale; AssBlockType GetType() const override { return AssBlockType::DRAWING; } AssDialogueBlockDrawing(std::string const& text, int scale) : AssDialogueBlock(text), Scale(scale) { } - void TransformCoords(int trans_x,int trans_y,double mult_x,double mult_y); }; class AssDialogueBlockOverride : public AssDialogueBlock { diff --git a/aegisub/src/command/tool.cpp b/aegisub/src/command/tool.cpp index b142f0a33..9a14e56d8 100644 --- a/aegisub/src/command/tool.cpp +++ b/aegisub/src/command/tool.cpp @@ -51,6 +51,7 @@ #include "../dialog_translation.h" #include "../include/aegisub/context.h" #include "../options.h" +#include "../resolution_resampler.h" #include "../video_context.h" #include diff --git a/aegisub/src/dialog_resample.cpp b/aegisub/src/dialog_resample.cpp index 785436cf4..44e7abbca 100644 --- a/aegisub/src/dialog_resample.cpp +++ b/aegisub/src/dialog_resample.cpp @@ -21,20 +21,13 @@ #include "dialog_resample.h" -#include "ass_dialogue.h" #include "ass_file.h" -#include "ass_style.h" #include "include/aegisub/context.h" #include "help_button.h" #include "libresrc/libresrc.h" +#include "resolution_resampler.h" #include "video_context.h" -#include - -#include -#include -#include - #include #include #include @@ -144,113 +137,3 @@ void DialogResample::OnMarginChange(wxSpinCtrl *src, wxSpinCtrl *dst) { if (symmetrical->IsChecked()) dst->SetValue(src->GetValue()); } - -namespace { - struct resample_state { - const int *margin; - double rx; - double ry; - double ar; - }; - - void resample_tags(std::string const& name, AssOverrideParameter *cur, void *ud) { - resample_state *state = static_cast(ud); - - double resizer = 1.0; - int shift = 0; - - switch (cur->classification) { - case AssParameterClass::ABSOLUTE_SIZE: - resizer = state->ry; - break; - - case AssParameterClass::ABSOLUTE_POS_X: - resizer = state->rx; - shift = state->margin[LEFT]; - break; - - case AssParameterClass::ABSOLUTE_POS_Y: - resizer = state->ry; - shift = state->margin[TOP]; - break; - - case AssParameterClass::RELATIVE_SIZE_X: - resizer = state->ar; - break; - - case AssParameterClass::RELATIVE_SIZE_Y: - //resizer = ry; - break; - - case AssParameterClass::DRAWING: { - AssDialogueBlockDrawing block(cur->Get(), 1); - block.TransformCoords(state->margin[LEFT], state->margin[TOP], state->rx, state->ry); - cur->Set(block.GetText()); - return; - } - - default: - return; - } - - VariableDataType curType = cur->GetType(); - if (curType == VariableDataType::FLOAT) - cur->Set((cur->Get() + shift) * resizer); - else if (curType == VariableDataType::INT) - cur->Set((cur->Get() + shift) * resizer + 0.5); - } - - void resample_line(resample_state *state, AssEntry &line) { - AssDialogue *diag = dynamic_cast(&line); - if (diag && !(diag->Comment && (boost::starts_with(diag->Effect.get(), "template") || boost::starts_with(diag->Effect.get(), "code")))) { - boost::ptr_vector blocks(diag->ParseTags()); - - for (auto block : blocks | agi::of_type()) - block->ProcessParameters(resample_tags, state); - - for (auto drawing : blocks | agi::of_type()) - drawing->TransformCoords(state->margin[LEFT], state->margin[TOP], state->rx, state->ry); - - for (size_t i = 0; i < 3; ++i) - diag->Margin[i] = int((diag->Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5); - - diag->UpdateText(blocks); - } - else if (AssStyle *style = dynamic_cast(&line)) { - style->fontsize = int(style->fontsize * state->ry + 0.5); - style->outline_w *= state->ry; - style->shadow_w *= state->ry; - style->spacing *= state->rx; - style->scalex *= state->ar; - for (int i = 0; i < 3; i++) - style->Margin[i] = int((style->Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5); - style->UpdateData(); - } - } -} - -void ResampleResolution(AssFile *ass, ResampleSettings const& settings) { - int src_x, src_y; - ass->GetResolution(src_x, src_y); - - // Add margins to original resolution - src_x += settings.margin[LEFT] + settings.margin[RIGHT]; - src_y += settings.margin[TOP] + settings.margin[BOTTOM]; - - resample_state state = { - settings.margin, - double(settings.script_x) / double(src_x), - double(settings.script_y) / double(src_y), - 1.0 - }; - - if (settings.change_ar) - state.ar = state.rx / state.ry; - - for_each(ass->Line.begin(), ass->Line.end(), std::bind(resample_line, &state, std::placeholders::_1)); - - ass->SetScriptInfo("PlayResX", std::to_string(settings.script_x)); - ass->SetScriptInfo("PlayResY", std::to_string(settings.script_y)); - - ass->Commit(_("resolution resampling"), AssFile::COMMIT_SCRIPTINFO | AssFile::COMMIT_DIAG_FULL); -} diff --git a/aegisub/src/dialog_resample.h b/aegisub/src/dialog_resample.h index e1f01ecb9..3b55ef227 100644 --- a/aegisub/src/dialog_resample.h +++ b/aegisub/src/dialog_resample.h @@ -23,23 +23,7 @@ namespace agi { struct Context; } class AssFile; class wxCheckBox; class wxSpinCtrl; - -/// Configuration parameters for a resample -struct ResampleSettings { - /// Amount to add to each margin - int margin[4]; - /// New X resolution - int script_x; - /// New Y resolution - int script_y; - /// Should the aspect ratio of the subs be changed? - bool change_ar; -}; - -/// Resample the subtitles in the project -/// @param file Subtitles to resample -/// @param settings Resample configuration settings -void ResampleResolution(AssFile *file, ResampleSettings const& settings); +struct ResampleSettings; /// @class DialogResample /// @brief Configuration dialog for resolution resampling diff --git a/aegisub/src/resolution_resampler.cpp b/aegisub/src/resolution_resampler.cpp new file mode 100644 index 000000000..a3509dee4 --- /dev/null +++ b/aegisub/src/resolution_resampler.cpp @@ -0,0 +1,177 @@ +// Copyright (c) 2013, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include "config.h" + +#include "resolution_resampler.h" + +#include "ass_dialogue.h" +#include "ass_file.h" +#include "ass_style.h" + +#include +#include + +#include +#include +#include +#include + +enum { + LEFT = 0, + RIGHT = 1, + TOP = 2, + BOTTOM = 3 +}; + +namespace { + std::string transform_drawing(std::string const& drawing, int shift_x, int shift_y, double scale_x, double scale_y) { + bool is_x = true; + std::string final; + final.reserve(drawing.size()); + + for (auto const& cur : agi::Split(drawing, ' ')) { + if (std::all_of(begin(cur), end(cur), isdigit)) { + int val = boost::lexical_cast(agi::str(cur)); + if (is_x) + val = (int)((val + shift_x) * scale_x + .5); + else + val = (int)((val + shift_y) * scale_y + .5); + final += std::to_string(val); + final += ' '; + } + else if (cur.size() == 1) { + char c = tolower(cur[0]); + if (c == 'm' || c == 'n' || c == 'l' || c == 'b' || c == 's' || c == 'p' || c == 'c') { + is_x = true; + final += c; + final += ' '; + } + } + } + + final.pop_back(); + return final; + } + + struct resample_state { + const int *margin; + double rx; + double ry; + double ar; + }; + + void resample_tags(std::string const& name, AssOverrideParameter *cur, void *ud) { + resample_state *state = static_cast(ud); + + double resizer = 1.0; + int shift = 0; + + switch (cur->classification) { + case AssParameterClass::ABSOLUTE_SIZE: + resizer = state->ry; + break; + + case AssParameterClass::ABSOLUTE_POS_X: + resizer = state->rx; + shift = state->margin[LEFT]; + break; + + case AssParameterClass::ABSOLUTE_POS_Y: + resizer = state->ry; + shift = state->margin[TOP]; + break; + + case AssParameterClass::RELATIVE_SIZE_X: + resizer = state->ar; + break; + + case AssParameterClass::RELATIVE_SIZE_Y: + break; + + case AssParameterClass::DRAWING: { + cur->Set(transform_drawing( + cur->Get(), + state->margin[LEFT], state->margin[TOP], state->rx, state->ry)); + return; + } + + default: + return; + } + + VariableDataType curType = cur->GetType(); + if (curType == VariableDataType::FLOAT) + cur->Set((cur->Get() + shift) * resizer); + else if (curType == VariableDataType::INT) + cur->Set((cur->Get() + shift) * resizer + 0.5); + } + + void resample_line(resample_state *state, AssEntry &line) { + AssDialogue *diag = dynamic_cast(&line); + if (diag && !(diag->Comment && (boost::starts_with(diag->Effect.get(), "template") || boost::starts_with(diag->Effect.get(), "code")))) { + boost::ptr_vector blocks(diag->ParseTags()); + + for (auto block : blocks | agi::of_type()) + block->ProcessParameters(resample_tags, state); + + for (auto drawing : blocks | agi::of_type()) + drawing->text = transform_drawing(drawing->text, state->margin[LEFT], state->margin[TOP], state->rx, state->ry); + + for (size_t i = 0; i < 3; ++i) + diag->Margin[i] = int((diag->Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5); + + diag->UpdateText(blocks); + } + else if (AssStyle *style = dynamic_cast(&line)) { + style->fontsize = int(style->fontsize * state->ry + 0.5); + style->outline_w *= state->ry; + style->shadow_w *= state->ry; + style->spacing *= state->rx; + style->scalex *= state->ar; + for (int i = 0; i < 3; i++) + style->Margin[i] = int((style->Margin[i] + state->margin[i]) * (i < 2 ? state->rx : state->ry) + 0.5); + style->UpdateData(); + } + } +} + +void ResampleResolution(AssFile *ass, ResampleSettings const& settings) { + int src_x, src_y; + ass->GetResolution(src_x, src_y); + + // Add margins to original resolution + src_x += settings.margin[LEFT] + settings.margin[RIGHT]; + src_y += settings.margin[TOP] + settings.margin[BOTTOM]; + + resample_state state = { + settings.margin, + double(settings.script_x) / double(src_x), + double(settings.script_y) / double(src_y), + 1.0 + }; + + if (settings.change_ar) + state.ar = state.rx / state.ry; + + for (auto& line : ass->Line) + resample_line(&state, line); + + ass->SetScriptInfo("PlayResX", std::to_string(settings.script_x)); + ass->SetScriptInfo("PlayResY", std::to_string(settings.script_y)); + + ass->Commit(_("resolution resampling"), AssFile::COMMIT_SCRIPTINFO | AssFile::COMMIT_DIAG_FULL); +} diff --git a/aegisub/src/resolution_resampler.h b/aegisub/src/resolution_resampler.h new file mode 100644 index 000000000..0e19572c7 --- /dev/null +++ b/aegisub/src/resolution_resampler.h @@ -0,0 +1,34 @@ +// Copyright (c) 2013, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +class AssFile; + +/// Configuration parameters for a resample +struct ResampleSettings { + /// Amount to add to each margin + int margin[4]; + /// New X resolution + int script_x; + /// New Y resolution + int script_y; + /// Should the aspect ratio of the subs be changed? + bool change_ar; +}; + +/// Resample the subtitles in the project +/// @param file Subtitles to resample +/// @param settings Resample configuration settings +void ResampleResolution(AssFile *file, ResampleSettings const& settings);