Merge remote-tracking branch 'origind-dev/master'

Add align to video function
This commit is contained in:
Charlie Jiang 2019-05-09 16:26:08 -04:00 committed by arch1t3cht
parent 1772dd17ae
commit cc8e857849
17 changed files with 587 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

View File

@ -51,6 +51,11 @@ button/bugtracker_button_24.png
button/bugtracker_button_32.png
button/bugtracker_button_48.png
button/bugtracker_button_64.png
button/button_align_16.png
button/button_align_24.png
button/button_align_32.png
button/button_align_48.png
button/button_align_64.png
button/button_audio_commit_16.png
button/button_audio_commit_24.png
button/button_audio_commit_32.png

View File

@ -43,6 +43,12 @@ public:
/// Get the currently selected color
agi::Color GetColor() { return colour; }
void SetColor(agi::Color color)
{
colour = color;
UpdateBitmap();
}
};
struct ColorValidator final : public wxValidator {

View File

@ -235,6 +235,18 @@ struct time_snap_scene final : public validate_video_loaded {
}
};
struct time_align_subtitle_to_point final : public validate_video_loaded {
CMD_NAME("time/align")
CMD_ICON(button_align)
STR_MENU("Align subtitle to video")
STR_DISP("Align subtitle to video")
STR_HELP("Align subtitle to video by key points.")
void operator()(agi::Context* c) override {
c->videoController->Stop();
ShowAlignToVideoDialog(c);
}
};
struct time_add_lead_both final : public Command {
CMD_NAME("time/lead/both")
STR_MENU("Add lead in and out")
@ -393,6 +405,7 @@ namespace cmd {
reg(agi::make_unique<time_snap_end_video>());
reg(agi::make_unique<time_snap_scene>());
reg(agi::make_unique<time_snap_start_video>());
reg(agi::make_unique<time_align_subtitle_to_point>());
reg(agi::make_unique<time_start_decrease>());
reg(agi::make_unique<time_start_increase>());
}

378
src/dialog_align.cpp Normal file
View File

@ -0,0 +1,378 @@
// Copyright (c) 2019, Charlie Jiang
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
#include "ass_dialogue.h"
#include "ass_file.h"
#include "compat.h"
#include "dialog_manager.h"
#include "format.h"
#include "include/aegisub/context.h"
#include "video_frame.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "project.h"
#include "selection_controller.h"
#include "video_controller.h"
#include "async_video_provider.h"
#include "colour_button.h"
#include "image_position_picker.h"
#include <cmath>
#include <libaegisub/ass/time.h>
#include <libaegisub/vfr.h>
#include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#if BOOST_VERSION >= 106900
#include <boost/gil.hpp>
#else
#include <boost/gil/gil_all.hpp>
#endif
namespace {
class DialogAlignToVideo final : public wxDialog {
agi::Context* context;
AsyncVideoProvider* provider;
wxImage preview_image;
VideoFrame current_frame;
int current_n_frame;
ImagePositionPicker* preview_frame;
ColourButton* selected_color;
wxTextCtrl* selected_x;
wxTextCtrl* selected_y;
wxTextCtrl* selected_tolerance;
void update_from_textbox();
void update_from_textbox(wxCommandEvent&);
bool check_exists(int pos, int x, int y, int* lrud, double* orig, unsigned char tolerance);
void process(wxCommandEvent&);
public:
DialogAlignToVideo(agi::Context* context);
~DialogAlignToVideo();
};
DialogAlignToVideo::DialogAlignToVideo(agi::Context* context)
: wxDialog(context->parent, -1, _("Align subtitle to video by key point"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxRESIZE_BORDER)
, context(context), provider(context->project->VideoProvider())
{
auto add_with_label = [&](wxSizer * sizer, wxString const& label, wxWindow * ctrl) {
sizer->Add(new wxStaticText(this, -1, label), 0, wxLEFT | wxRIGHT | wxCENTER, 3);
sizer->Add(ctrl, 1, wxLEFT);
};
auto tolerance = OPT_GET("Tool/Align to Video/Tolerance")->GetInt();
auto maximized = OPT_GET("Tool/Align to Video/Maximized")->GetBool();
current_n_frame = context->videoController->GetFrameN();
current_frame = *context->project->VideoProvider()->GetFrame(current_n_frame, 0, true);
preview_image = GetImage(current_frame);
preview_frame = new ImagePositionPicker(this, preview_image, [&](int x, int y, unsigned char r, unsigned char g, unsigned char b) -> void {
selected_x->ChangeValue(wxString::Format(wxT("%i"), x));
selected_y->ChangeValue(wxString::Format(wxT("%i"), y));
selected_color->SetColor(agi::Color(r, g, b));
});
selected_color = new ColourButton(this, wxSize(55, 16), true, agi::Color("FFFFFF"));
selected_color->SetToolTip(_("The key color to be followed."));
selected_x = new wxTextCtrl(this, -1, "0");
selected_x->SetToolTip(_("The x coord of the key point."));
selected_y = new wxTextCtrl(this, -1, "0");
selected_y->SetToolTip(_("The y coord of the key point."));
selected_tolerance = new wxTextCtrl(this, -1, wxString::Format(wxT("%i"), int(tolerance)));
selected_tolerance->SetToolTip(_("Max tolerance of the color."));
selected_x->Bind(wxEVT_TEXT, &DialogAlignToVideo::update_from_textbox, this);
selected_y->Bind(wxEVT_TEXT, &DialogAlignToVideo::update_from_textbox, this);
update_from_textbox();
wxFlexGridSizer* right_sizer = new wxFlexGridSizer(4, 2, 5, 5);
add_with_label(right_sizer, _("X"), selected_x);
add_with_label(right_sizer, _("Y"), selected_y);
add_with_label(right_sizer, _("Color"), selected_color);
add_with_label(right_sizer, _("Tolerance"), selected_tolerance);
right_sizer->AddGrowableCol(1, 1);
wxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
main_sizer->Add(preview_frame, 1, (wxALL & ~wxRIGHT) | wxEXPAND, 5);
main_sizer->Add(right_sizer, 0, wxALIGN_LEFT, 5);
wxSizer* dialog_sizer = new wxBoxSizer(wxVERTICAL);
dialog_sizer->Add(main_sizer, wxSizerFlags(1).Border(wxALL & ~wxBOTTOM).Expand());
dialog_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Right().Border());
SetSizerAndFit(dialog_sizer);
SetSize(1024, 700);
CenterOnParent();
Bind(wxEVT_BUTTON, &DialogAlignToVideo::process, this, wxID_OK);
SetIcon(GETICON(button_align_16));
if (maximized)
wxDialog::Maximize(true);
}
DialogAlignToVideo::~DialogAlignToVideo()
{
long lt;
if (!selected_tolerance->GetValue().ToLong(&lt))
return;
if (lt < 0 || lt > 255)
return;
OPT_SET("Tool/Align to Video/Tolerance")->SetInt(lt);
}
void rgb2lab(unsigned char r, unsigned char g, unsigned char b, double* lab)
{
double R = static_cast<double>(r) / 255.0;
double G = static_cast<double>(g) / 255.0;
double B = static_cast<double>(b) / 255.0;
double X = 0.412453 * R + 0.357580 * G + 0.180423 * B;
double Y = 0.212671 * R + 0.715160 * G + 0.072169 * B;
double Z = 0.019334 * R + 0.119193 * G + 0.950227 * B;
double xr = X / 0.950456, yr = Y / 1.000, zr = Z / 1.088854;
if (yr > 0.008856) {
lab[0] = 116.0 * pow(yr, 1.0 / 3.0) - 16.0;
}
else {
lab[0] = 903.3 * yr;
}
double fxr, fyr, fzr;
if (xr > 0.008856)
fxr = pow(xr, 1.0 / 3.0);
else
fxr = 7.787 * xr + 16.0 / 116.0;
if (yr > 0.008856)
fyr = pow(yr, 1.0 / 3.0);
else
fyr = 7.787 * yr + 16.0 / 116.0;
if (zr > 0.008856)
fzr = pow(zr, 1.0 / 3.0);
else
fzr = 7.787 * zr + 16.0 / 116.0;
lab[1] = 500.0 * (fxr - fyr);
lab[2] = 200.0 * (fyr - fzr);
}
template<typename T>
bool check_point(boost::gil::pixel<unsigned char, T> & pixel, double orig[3], unsigned char tolerance)
{
double lab[3];
// in pixel: B,G,R
rgb2lab(pixel[2], pixel[1], pixel[0], lab);
auto diff = sqrt(pow(lab[0] - orig[0], 2) + pow(lab[1] - orig[1], 2) + pow(lab[2] - orig[2], 2));
return diff < tolerance;
}
template<typename T>
bool calculate_point(boost::gil::image_view<T> view, int x, int y, double orig[3], unsigned char tolerance, int* ret)
{
auto origin = *view.at(x, y);
if (!check_point(origin, orig, tolerance))
return false;
auto w = view.width();
auto h = view.height();
int l = x, r = x, u = y, d = y;
for (int i = x + 1; i < w; i++)
{
auto p = *view.at(i, y);
if (!check_point(p, orig, tolerance))
{
r = i;
break;
}
}
for (int i = x - 1; i >= 0; i--)
{
auto p = *view.at(i, y);
if (!check_point(p, orig, tolerance))
{
l = i;
break;
}
}
for (int i = y + 1; i < h; i++)
{
auto p = *view.at(x, i);
if (!check_point(p, orig, tolerance))
{
d = i;
break;
}
}
for (int i = y - 1; i >= 0; i--)
{
auto p = *view.at(x, i);
if (!check_point(p, orig, tolerance))
{
u = i;
break;
}
}
ret[0] = l;
ret[1] = r;
ret[2] = u;
ret[3] = d;
return true;
}
void DialogAlignToVideo::process(wxCommandEvent & evt)
{
auto n_frames = provider->GetFrameCount();
auto w = provider->GetWidth();
auto h = provider->GetHeight();
long lx, ly, lt;
if (!selected_x->GetValue().ToLong(&lx) || !selected_y->GetValue().ToLong(&ly) || !selected_tolerance->GetValue().ToLong(&lt))
{
wxMessageBox(_("Bad x or y position or tolerance value!"));
evt.Skip();
return;
}
if (lx < 0 || ly < 0 || lx >= w || ly >= h)
{
wxMessageBox(wxString::Format(_("Bad x or y position! Require: 0 <= x < %i, 0 <= y < %i"), w, h));
evt.Skip();
return;
}
if (lt < 0 || lt > 255)
{
wxMessageBox(_("Bad tolerance value! Require: 0 <= torlerance <= 255"));
evt.Skip();
return;
}
int x = int(lx), y = int(ly);
unsigned char tolerance = unsigned char(lt);
auto color = selected_color->GetColor();
auto r = color.r;
auto b = color.b;
auto g = color.g;
double lab[3];
rgb2lab(r, g, b, lab);
int pos = current_n_frame;
auto frame = provider->GetFrame(pos, -1, true);
auto view = interleaved_view(frame->width, frame->height, reinterpret_cast<boost::gil::bgra8_pixel_t*>(frame->data.data()), frame->pitch);
if (frame->flipped)
y = frame->height - y;
int lrud[4];
calculate_point(view, x, y, lab, tolerance, lrud);
// find forward
#define CHECK_EXISTS_POS check_exists(pos, x, y, lrud, lab, tolerance)
while (pos >= 0)
{
if (CHECK_EXISTS_POS)
pos -= 2;
else break;
}
pos++;
pos = std::max(0, pos);
auto left = CHECK_EXISTS_POS ? pos : pos + 1;
pos = current_n_frame;
while (pos < n_frames)
{
if (CHECK_EXISTS_POS)
pos += 2;
else break;
}
pos--;
pos = std::min(pos, n_frames - 1);
auto right = CHECK_EXISTS_POS ? pos : pos - 1;
auto timecode = context->project->Timecodes();
auto line = context->selectionController->GetActiveLine();
line->Start = timecode.TimeAtFrame(left);
line->End = timecode.TimeAtFrame(right + 1); // exclusive
context->ass->Commit(_("Align to video by key point"), AssFile::COMMIT_DIAG_TIME);
Close();
}
bool DialogAlignToVideo::check_exists(int pos, int x, int y, int* lrud, double* orig, unsigned char tolerance)
{
auto frame = provider->GetFrame(pos, -1, true);
auto view = interleaved_view(frame->width, frame->height, reinterpret_cast<boost::gil::bgra8_pixel_t*>(frame->data.data()), frame->pitch);
if (frame->flipped)
y = frame->height - y;
int actual[4];
if (!calculate_point(view, x, y, orig, tolerance, actual)) return false;
int dl = abs(actual[0] - lrud[0]);
int dr = abs(actual[1] - lrud[1]);
int du = abs(actual[2] - lrud[2]);
int dd = abs(actual[3] - lrud[3]);
return dl <= 5 && dr <= 5 && du <= 5 && dd <= 5;
}
void DialogAlignToVideo::update_from_textbox()
{
long lx, ly;
int w = preview_image.GetWidth(), h = preview_image.GetHeight();
if (!selected_x->GetValue().ToLong(&lx) || !selected_y->GetValue().ToLong(&ly))
return;
if (lx < 0 || ly < 0 || lx >= w || ly >= h)
return;
int x = int(lx);
int y = int(ly);
auto r = preview_image.GetRed(x, y);
auto g = preview_image.GetGreen(x, y);
auto b = preview_image.GetBlue(x, y);
selected_color->SetColor(agi::Color(r, g, b));
}
void DialogAlignToVideo::update_from_textbox(wxCommandEvent & evt)
{
update_from_textbox();
}
}
void ShowAlignToVideoDialog(agi::Context * c)
{
c->dialog->Show<DialogAlignToVideo>(c);
}

View File

@ -74,3 +74,4 @@ void ShowSpellcheckerDialog(agi::Context *c);
void ShowStyleManagerDialog(agi::Context *c);
void ShowTimingProcessorDialog(agi::Context *c);
void ShowVideoDetailsDialog(agi::Context *c);
void ShowAlignToVideoDialog(agi::Context* c);

View File

@ -29,6 +29,11 @@
#include <wx/msgdlg.h>
namespace {
const char* added_hotkeys_cj[][3] = {
{"time/align", "Video", "KP_TAB"},
{nullptr}
};
const char *added_hotkeys_7035[][3] = {
{"audio/play/line", "Audio", "R"},
{nullptr}
@ -81,6 +86,11 @@ void init() {
auto migrations = OPT_GET("App/Hotkey Migrations")->GetListString();
if (boost::find(migrations, "cj") == end(migrations)) {
migrate_hotkeys(added_hotkeys_cj);
migrations.emplace_back("cj");
}
if (boost::find(migrations, "7035") == end(migrations)) {
migrate_hotkeys(added_hotkeys_7035);
migrations.emplace_back("7035");

View File

@ -0,0 +1,127 @@
#include "image_position_picker.h"
BEGIN_EVENT_TABLE(ImagePositionPicker, wxPanel)
// some useful events
/*
EVT_MOTION(ImagePositionPicker::mouseMoved)
EVT_LEFT_DOWN(ImagePositionPicker::mouseDown)
EVT_LEFT_UP(ImagePositionPicker::mouseReleased)
EVT_RIGHT_DOWN(ImagePositionPicker::rightClick)
EVT_LEAVE_WINDOW(ImagePositionPicker::mouseLeftWindow)
EVT_KEY_DOWN(ImagePositionPicker::keyPressed)
EVT_KEY_UP(ImagePositionPicker::keyReleased)
EVT_MOUSEWHEEL(ImagePositionPicker::mouseWheelMoved)
*/
// catch paint events
EVT_PAINT(ImagePositionPicker::paintEvent)
//Size event
EVT_SIZE(ImagePositionPicker::OnSize)
EVT_MOUSE_EVENTS(ImagePositionPicker::OnMouseEvent)
END_EVENT_TABLE()
// some useful events
/*
void ImagePositionPicker::mouseMoved(wxMouseEvent& event) {}
void ImagePositionPicker::mouseDown(wxMouseEvent& event) {}
void ImagePositionPicker::mouseWheelMoved(wxMouseEvent& event) {}
void ImagePositionPicker::mouseReleased(wxMouseEvent& event) {}
void ImagePositionPicker::rightClick(wxMouseEvent& event) {}
void ImagePositionPicker::mouseLeftWindow(wxMouseEvent& event) {}
void ImagePositionPicker::keyPressed(wxKeyEvent& event) {}
void ImagePositionPicker::keyReleased(wxKeyEvent& event) {}
*/
ImagePositionPicker::ImagePositionPicker(wxWindow* parent, wxImage i, updator upd) : wxPanel(parent)
{
image = i;
prevW = -1;
prevH = -1;
w = image.GetWidth();
h = image.GetHeight();
update = upd;
}
/*
* Called by the system of by wxWidgets when the panel needs
* to be redrawn. You can also trigger this call by
* calling Refresh()/Update().
*/
void ImagePositionPicker::paintEvent(wxPaintEvent& evt)
{
// depending on your system you may need to look at double-buffered dcs
wxPaintDC dc(this);
render(dc);
}
/*
* Alternatively, you can use a clientDC to paint on the panel
* at any time. Using this generally does not free you from
* catching paint events, since it is possible that e.g. the window
* manager throws away your drawing when the window comes to the
* background, and expects you will redraw it when the window comes
* back (by sending a paint event).
*/
void ImagePositionPicker::paintNow()
{
// depending on your system you may need to look at double-buffered dcs
wxClientDC dc(this);
render(dc);
}
/*
* Here we do the actual rendering. I put it in a separate
* method so that it can work no matter what type of DC
* (e.g. wxPaintDC or wxClientDC) is used.
*/
void ImagePositionPicker::render(wxDC& dc)
{
int neww, newh;
dc.GetSize(&neww, &newh);
if (neww != prevW || newh != prevH)
{
// keep the image proportionate
int ww, hh;
if (double(neww) / w >= double(newh) / h) // too long
{
ww = newh * w / h;
hh = newh;
}
else
{
ww = neww;
hh = neww * h / w;
}
resized = wxBitmap(image.Scale(ww, hh /*, wxIMAGE_QUALITY_HIGH*/));
prevW = ww;
prevH = hh;
dc.DrawBitmap(resized, 0, 0, false);
}
else {
dc.DrawBitmap(resized, 0, 0, false);
}
}
/*
* Here we call refresh to tell the panel to draw itself again.
* So when the user resizes the image panel the image should be resized too.
*/
void ImagePositionPicker::OnSize(wxSizeEvent& event) {
Refresh();
//skip the event.
event.Skip();
}
void ImagePositionPicker::OnMouseEvent(wxMouseEvent& evt)
{
wxPoint pos = evt.GetPosition();
if (evt.Dragging() || evt.LeftDown() || evt.LeftUp())
{
int x = pos.x * w / prevW;
int y = pos.y * h / prevH;
if (x >= 0 && x < w && y >= 0 && y < h)
update(x, y, image.GetRed(x, y), image.GetGreen(x, y), image.GetBlue(x, y));
}
}

View File

@ -0,0 +1,37 @@
#include <wx/wx.h>
#include <wx/sizer.h>
#include "gl_wrap.h"
typedef std::function<void(double, double, unsigned char, unsigned char, unsigned char)> updator;
class ImagePositionPicker : public wxPanel
{
wxImage image;
wxBitmap resized;
int prevW, prevH, w, h;
updator update;
public:
ImagePositionPicker(wxWindow* parent, wxImage i, updator upd);
void paintEvent(wxPaintEvent & evt);
void paintNow();
void OnSize(wxSizeEvent& event);
void OnMouseEvent(wxMouseEvent& evt);
void render(wxDC& dc);
// some useful events
/*
void mouseMoved(wxMouseEvent& event);
void mouseDown(wxMouseEvent& event);
void mouseWheelMoved(wxMouseEvent& event);
void mouseReleased(wxMouseEvent& event);
void rightClick(wxMouseEvent& event);
void mouseLeftWindow(wxMouseEvent& event);
void keyPressed(wxKeyEvent& event);
void keyReleased(wxKeyEvent& event);
*/
DECLARE_EVENT_TABLE()
};

View File

@ -575,6 +575,10 @@
},
"Visual" : {
"Autohide": false
},
"Align to Video" : {
"Tolerance" : 20,
"Maximized" : true
}
},

View File

@ -286,6 +286,9 @@
],
"video/frame/prev/large" : [
"Alt-Left"
],
"time/align" : [
"KP_TAB"
]
},
"Translation Assistant" : {

View File

@ -42,6 +42,7 @@
"subtitle/select/visible",
"time/snap/scene",
"time/frame/current",
"time/align",
"",
"tool/style/manager",
"subtitle/properties",

View File

@ -56,6 +56,7 @@ aegisub_src = files(
'context.cpp',
'crash_writer.cpp',
'dialog_about.cpp',
'dialog_align.cpp',
'dialog_attachments.cpp',
'dialog_automation.cpp',
'dialog_autosave.cpp',
@ -97,6 +98,7 @@ aegisub_src = files(
'help_button.cpp',
'hotkey.cpp',
'hotkey_data_view_model.cpp',
'image_position_picker.cpp',
'initial_line_state.cpp',
'main.cpp',
'menu.cpp',