From cc8e85784973ca3a5c7dd971698480abaf1ad42e Mon Sep 17 00:00:00 2001 From: Charlie Jiang Date: Thu, 9 May 2019 16:26:08 -0400 Subject: [PATCH] Merge remote-tracking branch 'origind-dev/master' Add align to video function --- src/bitmaps/button/button_align_16.png | Bin 0 -> 353 bytes src/bitmaps/button/button_align_24.png | Bin 0 -> 571 bytes src/bitmaps/button/button_align_32.png | Bin 0 -> 690 bytes src/bitmaps/button/button_align_48.png | Bin 0 -> 1161 bytes src/bitmaps/button/button_align_64.png | Bin 0 -> 547 bytes src/bitmaps/manifest.respack | 5 + src/colour_button.h | 6 + src/command/time.cpp | 13 + src/dialog_align.cpp | 378 +++++++++++++++++++++++++ src/dialogs.h | 1 + src/hotkey.cpp | 10 + src/image_position_picker.cpp | 127 +++++++++ src/image_position_picker.h | 37 +++ src/libresrc/default_config.json | 4 + src/libresrc/default_hotkey.json | 3 + src/libresrc/default_toolbar.json | 1 + src/meson.build | 2 + 17 files changed, 587 insertions(+) create mode 100644 src/bitmaps/button/button_align_16.png create mode 100644 src/bitmaps/button/button_align_24.png create mode 100644 src/bitmaps/button/button_align_32.png create mode 100644 src/bitmaps/button/button_align_48.png create mode 100644 src/bitmaps/button/button_align_64.png create mode 100644 src/dialog_align.cpp create mode 100644 src/image_position_picker.cpp create mode 100644 src/image_position_picker.h diff --git a/src/bitmaps/button/button_align_16.png b/src/bitmaps/button/button_align_16.png new file mode 100644 index 0000000000000000000000000000000000000000..07ce86c1b127a1cb5711bebf1b67ccb99b70623d GIT binary patch literal 353 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!Ec@}jv*HQ$$$R;w`bOD;PevaWeYoeTf2S7 z&Ykb;+KUt!7AA(JswnepIeV+>^|!T;kN4OA{`U66%IE-50R|pbjvwEDzn$|m`hLtk zRuK{L$&DwP7_CECB@|3@W{0s&xwAFZaDq+I%PZ0gE;KK8_?FyZ!lPiq(73%%BA|6k zZbeV43Ul*?)34nd?i9aYxJjU4(Zj<}-#UnXyt=s|M?qOxS$ch&z5I=aPYDH&9_mjx zVLO30{eEL=Vgu_UCXQcPm+B1-YX1I@pEt4a`90Cw`FHl!l(7fQnJlZs&m7R}@Zssf xZ^0b$Ru4^P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0nkZAK~y+TWBmXB zKLa%Y6O^VHj2c83kYLdD-Ag>&-Q#MfLiwlwJ^FVoo4o((D|S|9Ap8HnzyDaon(J~Q zQYU5>TW0hz7)AX*yA&dV0)Pe~gLor$s00*k3&>?PjfQa1 z0J=dHLE1po2~BH(>|#ezUZdbozwWCF$rza!^MO)3gM?!$L?s%)2<+P%ymIG3*)X69 zG64d3cis976-5O^6}5kU|M>O$KQl8UJ39wd2m_3k*@OTC0{}7T!*frBZ>RtO002ov JPDHLkV1fYY?n3|o literal 0 HcmV?d00001 diff --git a/src/bitmaps/button/button_align_32.png b/src/bitmaps/button/button_align_32.png new file mode 100644 index 0000000000000000000000000000000000000000..958f4b34eb382f97943c26277c79d85bcf642221 GIT binary patch literal 690 zcmV;j0!{siP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0!K+iK~zXfWBmXB zKLafR6O^VI7`1>P7`1>PAU_{m+B$d1k^4-4zIkM~J1DS2MKJ-;0`kDT1U-le6}#U< zg|GqaL8QWn1*pc%hf0HifPerR11rEDM7oTOSD;J;un8&iHsty7>o1Va#>stl&E$1w z9s}8S(M8^FE@g3^U{&Y3CRl{!Pj`>sGmumY@+EWWS91I#!QxAfYz3zxubi4fIS62Q1t z&jxcD-hKHFVSIZ13Bte*pcY{O4^F582;lN7F4lsw-MbC~6)efLf^Zx5e}rhk3h?BE zgAn}?kg@pb|NpNTp*#kl4Vw#WA#D5>;3+EXcCON%? zfw1udd}Yyw`DdUkhF8;%f++j)?Vj8qM%mU)ii~)J9uA;B!2sK9ETAf2;4xVI^NA%} z@B9NYD2jtaJ>C$_C_pa0eYiLs$Xf==*qB8W!`dehRoDS`3%>5HW?-=X1LdIrUooh; zP@s_mmB0aTR5*XXFfj8#*{I;?x~ZGbykKKt6cpsw(e`n5Q-@060HZBCf?(7F0)T-5 Y0C1xRlIB)rD*ylh07*qoM6N<$g5zf=@Bjb+ literal 0 HcmV?d00001 diff --git a/src/bitmaps/button/button_align_48.png b/src/bitmaps/button/button_align_48.png new file mode 100644 index 0000000000000000000000000000000000000000..9d28cffad34b14a970c6f66148f86730efd2ce38 GIT binary patch literal 1161 zcmV;41a|w0P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1RhC5K~z{r?U;K^ zQ&$|vPfK4`sP+QoQ64@L5g97Vj0*;$GDwWkKNMfVY|b%_pvGlWnHq7~{-DMfW}I78 zW;9!tiLV$nQ9(tGbR*yc4WNP&)bNx}Ob_MBNo;}?uUUPNE^xt=3ayd zb5talLc;ax5HJzagmGX=fKk*9@o?gT9c8HGZJissyF}m6d~Kw zJ9d~W!ZPoAJv;DS*1Yvq4u}ytU%K32BGlj40hSa%*ne|+Z4{&;WXxQCzGCkfEw~8H zxQ#o$o;r-T6D-wlXX0)_lJB^fyt^5~5|C2V_LVU5dlc<2n+&EG{iKu4PQFWU1>xSS zmPnDcRTiG>#qB53L%}Bun`q&C1X&QN?%%&khvDC1%)Ro&McS$?^MrTR+ zK^XT`&rPAV0?b&q{}pX=H!VmL%o5_t&Q=yp^zE`i36gd;5V(xIBs17v)TX$2JvBqd zRUF?W5NmtX^4NiWciG?oVJw=DY!u4jnSQJjS_!8<8%?97_5YH?d`)sNm+aN8Kc*O9 z^moZ@g%s@Wx}5*%B5ObO?gg``Z8b9!VCeTrFFfO>X&(AP`Tx6&RYN`37tfk~sSP|Y zI+gGNsP^YV%cUN#rQm%)qg6_-PCr1HD6aMu*?x~-$v@r3_}L!tDC6?#{4T53qe;S0ltFz*w@HvV39jtoo&e#{c|04npRSi^{L{wVztLqIk!S?b}OAzAxEYkTu$O zyC9T%Nb~|0hgCyu6^R4SpIKW@Ly*vMBZtII`BpovohvbrCBn8kK1CYrqP|(2N{*U2 zCDwPU7UOOkoQss=3Gns)q?ba5G$ib@+S0{a&Ux>MfK+~y2nu*eO^0)-^Ot;kp`Bj0 zQjzfH`r6?R(Fq;^#c_5&yw%#L;a@g99#2?Us8$mZm&grvv&nx8vycU`kOi@j1+f@n bf)IKGkv!)D1ej0e00000NkvXXu0mjfC*2^6 literal 0 HcmV?d00001 diff --git a/src/bitmaps/button/button_align_64.png b/src/bitmaps/button/button_align_64.png new file mode 100644 index 0000000000000000000000000000000000000000..0d6039c0f20196b86105833560a4b72b2d7b5cb4 GIT binary patch literal 547 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!h8>hl?nh|9`X&#%!?c$n)|uyNCYXKOc> zDyFTtd*;C1KU)txdmj<1xr^}}H}mn8bH!&Jvw67DA*V*GY?5f2zvu-`ms>T{Q_U21 zTh8KDxe%&Q8T2oEE{~Z{LwLgDE8V?J0S&%inM>}#D^y%x-#_qSDF!-3_{V+XBk1uUs|cX54Lyl*3O z&HwkO&OB8R=sT14@AUFrXUg3=OxEQUt}L*Z=Hpw_Vvy&YmiI`DH~344!9zhsT^2dX zJvSOO8}B?3Fw(u6vyVsqq5AqqzW>!(&sh|+y0g81be_vVSMCsR;4l88zV#BRK?$~K eVLq+)KjREVtEGO6wweGVmci52&t;ucLK6V2*xU;M literal 0 HcmV?d00001 diff --git a/src/bitmaps/manifest.respack b/src/bitmaps/manifest.respack index 2dc110d3a..55c731bd9 100644 --- a/src/bitmaps/manifest.respack +++ b/src/bitmaps/manifest.respack @@ -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 diff --git a/src/colour_button.h b/src/colour_button.h index b2e0f9335..6234dc9fc 100644 --- a/src/colour_button.h +++ b/src/colour_button.h @@ -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 { diff --git a/src/command/time.cpp b/src/command/time.cpp index d703ebdc0..eec19c2d4 100644 --- a/src/command/time.cpp +++ b/src/command/time.cpp @@ -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()); reg(agi::make_unique()); reg(agi::make_unique()); + reg(agi::make_unique()); reg(agi::make_unique()); reg(agi::make_unique()); } diff --git a/src/dialog_align.cpp b/src/dialog_align.cpp new file mode 100644 index 000000000..004408bfa --- /dev/null +++ b/src/dialog_align.cpp @@ -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 + +#include +#include + +#include +#include +#include +#if BOOST_VERSION >= 106900 +#include +#else +#include +#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(<)) + 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(r) / 255.0; + double G = static_cast(g) / 255.0; + double B = static_cast(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 + bool check_point(boost::gil::pixel & 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 + bool calculate_point(boost::gil::image_view 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(<)) + { + 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(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(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(c); +} diff --git a/src/dialogs.h b/src/dialogs.h index 2e03d09e9..d781c6614 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -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); diff --git a/src/hotkey.cpp b/src/hotkey.cpp index 2ec8f50e4..deb624628 100644 --- a/src/hotkey.cpp +++ b/src/hotkey.cpp @@ -29,6 +29,11 @@ #include 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"); diff --git a/src/image_position_picker.cpp b/src/image_position_picker.cpp new file mode 100644 index 000000000..2bc9c6f89 --- /dev/null +++ b/src/image_position_picker.cpp @@ -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)); + } +} diff --git a/src/image_position_picker.h b/src/image_position_picker.h new file mode 100644 index 000000000..1f808db39 --- /dev/null +++ b/src/image_position_picker.h @@ -0,0 +1,37 @@ +#include +#include +#include "gl_wrap.h" + +typedef std::function 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() +}; diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 318f8d3ee..c86ef3609 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -575,6 +575,10 @@ }, "Visual" : { "Autohide": false + }, + "Align to Video" : { + "Tolerance" : 20, + "Maximized" : true } }, diff --git a/src/libresrc/default_hotkey.json b/src/libresrc/default_hotkey.json index b9460979e..d4b0af376 100644 --- a/src/libresrc/default_hotkey.json +++ b/src/libresrc/default_hotkey.json @@ -286,6 +286,9 @@ ], "video/frame/prev/large" : [ "Alt-Left" + ], + "time/align" : [ + "KP_TAB" ] }, "Translation Assistant" : { diff --git a/src/libresrc/default_toolbar.json b/src/libresrc/default_toolbar.json index 07afb90b0..7b85ee600 100644 --- a/src/libresrc/default_toolbar.json +++ b/src/libresrc/default_toolbar.json @@ -42,6 +42,7 @@ "subtitle/select/visible", "time/snap/scene", "time/frame/current", + "time/align", "", "tool/style/manager", "subtitle/properties", diff --git a/src/meson.build b/src/meson.build index 72587d366..495c3f418 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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',