From: Carl Hetherington Date: Sat, 1 Jan 2022 21:20:51 +0000 (+0000) Subject: Primitive auto-crop (#1477). X-Git-Tag: v2.16.10~22 X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=commitdiff_plain;h=d5c059a2ff9bab5c2973db6bc4860591679dd42b Primitive auto-crop (#1477). --- diff --git a/src/lib/config.cc b/src/lib/config.cc index 36aae76de..d11a60c94 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -188,6 +188,7 @@ Config::set_defaults () _write_kdms_to_disk = true; _email_kdms = false; _default_kdm_type = dcp::Formulation::MODIFIED_TRANSITIONAL_1; + _auto_crop_threshold = 0.1; _allowed_dcp_frame_rates.clear (); _allowed_dcp_frame_rates.push_back (24); @@ -579,6 +580,7 @@ try _write_kdms_to_disk = f.optional_bool_child("WriteKDMsToDisk").get_value_or(true); _email_kdms = f.optional_bool_child("EmailKDMs").get_value_or(false); _default_kdm_type = dcp::string_to_formulation(f.optional_string_child("DefaultKDMType").get_value_or("modified-transitional-1")); + _auto_crop_threshold = f.optional_number_child("AutoCropThreshold").get_value_or(0.1); if (boost::filesystem::exists (_cinemas_file)) { cxml::Document f ("Cinemas"); @@ -1014,6 +1016,7 @@ Config::write_config () const root->add_child("WriteKDMsToDisk")->add_child_text(_write_kdms_to_disk ? "1" : "0"); root->add_child("EmailKDMs")->add_child_text(_email_kdms ? "1" : "0"); root->add_child("DefaultKDMType")->add_child_text(dcp::formulation_to_string(_default_kdm_type)); + root->add_child("AutoCropThreshold")->add_child_text(raw_convert(_auto_crop_threshold)); auto target = config_write_file(); diff --git a/src/lib/config.h b/src/lib/config.h index 1d40a3b15..40497e219 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -91,6 +91,7 @@ public: HISTORY, SHOW_EXPERIMENTAL_AUDIO_PROCESSORS, AUDIO_MAPPING, + AUTO_CROP_THRESHOLD, OTHER }; @@ -571,6 +572,9 @@ public: return _default_kdm_type; } + double auto_crop_threshold () const { + return _auto_crop_threshold; + } /* SET (mostly) */ @@ -1096,6 +1100,10 @@ public: maybe_set (_default_kdm_type, type); } + void set_auto_crop_threshold (double threshold) { + maybe_set (_auto_crop_threshold, threshold, AUTO_CROP_THRESHOLD); + } + void changed (Property p = OTHER); boost::signals2::signal Changed; /** Emitted if read() failed on an existing Config file. There is nothing @@ -1316,6 +1324,7 @@ private: bool _write_kdms_to_disk; bool _email_kdms; dcp::Formulation _default_kdm_type; + double _auto_crop_threshold; static int const _current_version; diff --git a/src/lib/player.cc b/src/lib/player.cc index 619a3583c..b2db2b3ef 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1432,6 +1432,22 @@ Player::content_time_to_dcp (shared_ptr content, ContentTime t) } +optional +Player::dcp_to_content_time (shared_ptr content, DCPTime t) +{ + boost::mutex::scoped_lock lm (_mutex); + + for (auto i: _pieces) { + if (i->content == content) { + return dcp_to_content_time (i, t); + } + } + + /* We couldn't find this content; perhaps things are being changed over */ + return {}; +} + + shared_ptr Player::playlist () const { diff --git a/src/lib/player.h b/src/lib/player.h index b70ea05dd..7d99c22c6 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -101,6 +101,7 @@ public: void set_dcp_decode_reduction (boost::optional reduction); boost::optional content_time_to_dcp (std::shared_ptr content, dcpomatic::ContentTime t); + boost::optional dcp_to_content_time (std::shared_ptr content, dcpomatic::DCPTime t); boost::signals2::signal Change; diff --git a/src/lib/rect.h b/src/lib/rect.h index 5f807f499..a01e0f885 100644 --- a/src/lib/rect.h +++ b/src/lib/rect.h @@ -119,6 +119,13 @@ public: }; +template +bool operator== (Rect const& a, Rect const& b) +{ + return a.x == b.x && a.y == b.y && a.width == b.width && a.height == b.height; +} + + } diff --git a/src/lib/video_content.cc b/src/lib/video_content.cc index 86efeee7b..de3d3f3a2 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -529,6 +529,12 @@ VideoContent::set_length (Frame len) maybe_set (_length, len, ContentProperty::LENGTH); } +void +VideoContent::set_crop (Crop c) +{ + maybe_set (_crop, c, VideoContentProperty::CROP); +} + void VideoContent::set_left_crop (int c) { diff --git a/src/lib/video_content.h b/src/lib/video_content.h index 3ec884578..7214d35e4 100644 --- a/src/lib/video_content.h +++ b/src/lib/video_content.h @@ -89,6 +89,7 @@ public: void set_frame_type (VideoFrameType); + void set_crop (Crop crop); void set_left_crop (int); void set_right_crop (int); void set_top_crop (int); diff --git a/src/wx/auto_crop_dialog.cc b/src/wx/auto_crop_dialog.cc new file mode 100644 index 000000000..bfc08beb3 --- /dev/null +++ b/src/wx/auto_crop_dialog.cc @@ -0,0 +1,76 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "auto_crop_dialog.h" +#include "dcpomatic_spin_ctrl.h" +#include "wx_util.h" +#include "lib/config.h" +#include "lib/types.h" + + +AutoCropDialog::AutoCropDialog (wxWindow* parent, Crop crop) + : TableDialog (parent, _("Auto crop"), 2, 1, true) +{ + add (_("Left"), true); + _left = add(new SpinCtrl(this, DCPOMATIC_SPIN_CTRL_WIDTH)); + add (_("Right"), true); + _right = add(new SpinCtrl(this, DCPOMATIC_SPIN_CTRL_WIDTH)); + add (_("Top"), true); + _top = add(new SpinCtrl(this, DCPOMATIC_SPIN_CTRL_WIDTH)); + add (_("Bottom"), true); + _bottom = add(new SpinCtrl(this, DCPOMATIC_SPIN_CTRL_WIDTH)); + add (_("Threshold"), true); + _threshold = add(new SpinCtrl(this, DCPOMATIC_SPIN_CTRL_WIDTH)); + + _left->SetRange(0, 4096); + _right->SetRange(0, 4096); + _top->SetRange(0, 4096); + _bottom->SetRange(0, 4096); + + set (crop); + _threshold->SetValue (std::round(Config::instance()->auto_crop_threshold() * 100)); + + layout (); + + _left->Bind (wxEVT_SPINCTRL, [this](wxSpinEvent&) { Changed(get()); }); + _right->Bind (wxEVT_SPINCTRL, [this](wxSpinEvent&) { Changed(get()); }); + _top->Bind (wxEVT_SPINCTRL, [this](wxSpinEvent&) { Changed(get()); }); + _bottom->Bind (wxEVT_SPINCTRL, [this](wxSpinEvent&) { Changed(get()); }); + _threshold->Bind (wxEVT_SPINCTRL, [](wxSpinEvent& ev) { Config::instance()->set_auto_crop_threshold(ev.GetPosition() / 100.0); }); +} + + +Crop +AutoCropDialog::get () const +{ + return Crop(_left->GetValue(), _right->GetValue(), _top->GetValue(), _bottom->GetValue()); +} + + +void +AutoCropDialog::set (Crop crop) +{ + _left->SetValue (crop.left); + _right->SetValue (crop.right); + _top->SetValue (crop.top); + _bottom->SetValue (crop.bottom); +} + diff --git a/src/wx/auto_crop_dialog.h b/src/wx/auto_crop_dialog.h new file mode 100644 index 000000000..0615c14bb --- /dev/null +++ b/src/wx/auto_crop_dialog.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "table_dialog.h" +#include "lib/types.h" +#include + + +class SpinCtrl; + + +class AutoCropDialog : public TableDialog +{ +public: + AutoCropDialog (wxWindow* parent, Crop crop); + + Crop get () const; + void set (Crop crop); + + boost::signals2::signal Changed; + +private: + SpinCtrl* _left; + SpinCtrl* _right; + SpinCtrl* _top; + SpinCtrl* _bottom; + SpinCtrl* _threshold; +}; + diff --git a/src/wx/content_menu.cc b/src/wx/content_menu.cc index ea455df95..6fd5f9969 100644 --- a/src/wx/content_menu.cc +++ b/src/wx/content_menu.cc @@ -19,9 +19,11 @@ */ +#include "auto_crop_dialog.h" #include "content_advanced_dialog.h" #include "content_menu.h" #include "content_properties_dialog.h" +#include "film_viewer.h" #include "repeat_dialog.h" #include "timeline_audio_content_view.h" #include "timeline_video_content_view.h" @@ -36,9 +38,11 @@ #include "lib/exceptions.h" #include "lib/ffmpeg_content.h" #include "lib/film.h" +#include "lib/guess_crop.h" #include "lib/image_content.h" #include "lib/job_manager.h" #include "lib/playlist.h" +#include "lib/video_content.h" #include #include #include @@ -61,6 +65,7 @@ using boost::optional; #if BOOST_VERSION >= 106100 using namespace boost::placeholders; #endif +using namespace dcpomatic; enum { @@ -71,6 +76,7 @@ enum { ID_properties, ID_advanced, ID_re_examine, + ID_auto_crop, ID_kdm, ID_ov, ID_choose_cpl, @@ -79,15 +85,17 @@ enum { }; -ContentMenu::ContentMenu (wxWindow* p) +ContentMenu::ContentMenu (wxWindow* p, weak_ptr viewer) : _menu (new wxMenu) , _parent (p) , _pop_up_open (false) + , _viewer (viewer) { _repeat = _menu->Append (ID_repeat, _("Repeat...")); _join = _menu->Append (ID_join, _("Join")); _find_missing = _menu->Append (ID_find_missing, _("Find missing...")); _re_examine = _menu->Append (ID_re_examine, _("Re-examine...")); + _auto_crop = _menu->Append (ID_auto_crop, _("Auto-crop...")); _properties = _menu->Append (ID_properties, _("Properties...")); _advanced = _menu->Append (ID_advanced, _("Advanced settings...")); _menu->AppendSeparator (); @@ -105,6 +113,7 @@ ContentMenu::ContentMenu (wxWindow* p) _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::properties, this), ID_properties); _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::advanced, this), ID_advanced); _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::re_examine, this), ID_re_examine); + _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::auto_crop, this), ID_auto_crop); _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::kdm, this), ID_kdm); _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::ov, this), ID_ov); _parent->Bind (wxEVT_MENU, boost::bind (&ContentMenu::set_dcp_settings, this), ID_set_dcp_settings); @@ -139,6 +148,7 @@ ContentMenu::popup (weak_ptr film, ContentList c, TimelineContentViewList _properties->Enable (_content.size() == 1); _advanced->Enable (_content.size() == 1); _re_examine->Enable (!_content.empty ()); + _auto_crop->Enable (_content.size() == 1); if (_content.size() == 1) { auto dcp = dynamic_pointer_cast (_content.front()); @@ -525,3 +535,84 @@ ContentMenu::cpl_selected (wxCommandEvent& ev) DCPOMATIC_ASSERT (film); JobManager::instance()->add (make_shared(film, dcp)); } + + +void +ContentMenu::auto_crop () +{ + DCPOMATIC_ASSERT (_content.size() == 1); + + auto film = _film.lock (); + DCPOMATIC_ASSERT (film); + auto viewer = _viewer.lock (); + DCPOMATIC_ASSERT (viewer); + + auto update_viewer = [this](Crop crop) { + auto film = _film.lock(); + DCPOMATIC_ASSERT (film); + auto viewer = _viewer.lock (); + DCPOMATIC_ASSERT (viewer); + auto const content = _content.front(); + auto const current_crop = content->video->actual_crop(); + viewer->set_crop_guess ( + Rect( + static_cast(std::max(0, crop.left - current_crop.left)) / content->video->size().width, + static_cast(std::max(0, crop.top - current_crop.top)) / content->video->size().height, + 1.0f - (static_cast(std::max(0, crop.left - current_crop.left + crop.right - current_crop.right)) / content->video->size().width), + 1.0f - (static_cast(std::max(0, crop.top - current_crop.top + crop.bottom - current_crop.bottom)) / content->video->size().height) + )); + }; + + auto guess_crop_for_content = [this, film, viewer]() { + auto position = viewer->position_in_content(_content.front()).get_value_or( + ContentTime::from_frames(_content.front()->video->length(), _content.front()->video_frame_rate().get_value_or(24)) + ); + return guess_crop(film, _content.front(), Config::instance()->auto_crop_threshold(), position); + }; + + /* Make an initial guess in the view and open the dialog */ + + auto const crop = guess_crop_for_content (); + update_viewer (crop); + + if (_auto_crop_dialog) { + _auto_crop_dialog->Destroy(); + _auto_crop_dialog = nullptr; + } + _auto_crop_dialog = new AutoCropDialog (_parent, crop); + _auto_crop_dialog->Show (); + + /* Update the dialog and view when the crop threshold changes */ + _auto_crop_config_connection = Config::instance()->Changed.connect([this, guess_crop_for_content, update_viewer](Config::Property property) { + auto film = _film.lock(); + DCPOMATIC_ASSERT (film); + if (property == Config::AUTO_CROP_THRESHOLD) { + auto const crop = guess_crop_for_content(); + _auto_crop_dialog->set(crop); + update_viewer(crop); + } + }); + + /* Also update the dialog and view when we're looking at a different frame */ + _auto_crop_viewer_connection = viewer->ImageChanged.connect([this, guess_crop_for_content, update_viewer](shared_ptr) { + auto const crop = guess_crop_for_content(); + _auto_crop_dialog->set(crop); + update_viewer(crop); + }); + + /* Handle the user closing the dialog (with OK or cancel) */ + _auto_crop_dialog->Bind (wxEVT_BUTTON, [this, viewer](wxCommandEvent& ev) { + if (ev.GetId() == wxID_OK) { + _content.front()->video->set_crop(_auto_crop_dialog->get()); + } + _auto_crop_dialog->Show (false); + viewer->unset_crop_guess (); + _auto_crop_config_connection.disconnect (); + _auto_crop_viewer_connection.disconnect (); + }); + + /* Update the view when something in the dialog is changed */ + _auto_crop_dialog->Changed.connect([this, update_viewer](Crop crop) { + update_viewer (crop); + }); +} diff --git a/src/wx/content_menu.h b/src/wx/content_menu.h index 910afc720..6e1641e66 100644 --- a/src/wx/content_menu.h +++ b/src/wx/content_menu.h @@ -32,15 +32,17 @@ LIBDCP_ENABLE_WARNINGS #include +class AutoCropDialog; class DCPContent; class Film; +class FilmViewer; class Job; class ContentMenu { public: - explicit ContentMenu (wxWindow* p); + ContentMenu (wxWindow* parent, std::weak_ptr viewer); ContentMenu (ContentMenu const &) = delete; ContentMenu& operator= (ContentMenu const &) = delete; @@ -54,6 +56,7 @@ private: void properties (); void advanced (); void re_examine (); + void auto_crop (); void kdm (); void ov (); void set_dcp_settings (); @@ -67,6 +70,7 @@ private: std::weak_ptr _film; wxWindow* _parent; bool _pop_up_open; + std::weak_ptr _viewer; ContentList _content; TimelineContentViewList _views; wxMenuItem* _repeat; @@ -75,11 +79,17 @@ private: wxMenuItem* _properties; wxMenuItem* _advanced; wxMenuItem* _re_examine; + wxMenuItem* _auto_crop; wxMenuItem* _kdm; wxMenuItem* _ov; wxMenuItem* _choose_cpl; wxMenuItem* _set_dcp_settings; wxMenuItem* _remove; + + AutoCropDialog* _auto_crop_dialog = nullptr; + boost::signals2::scoped_connection _auto_crop_config_connection; + boost::signals2::scoped_connection _auto_crop_viewer_connection; }; + #endif diff --git a/src/wx/content_panel.cc b/src/wx/content_panel.cc index f93b0d471..8f0994919 100644 --- a/src/wx/content_panel.cc +++ b/src/wx/content_panel.cc @@ -85,7 +85,7 @@ ContentPanel::ContentPanel (wxNotebook* n, shared_ptr film, weak_ptr +FilmViewer::position_in_content (shared_ptr content) const +{ + return _player->dcp_to_content_time (content, position()); +} + + DCPTime FilmViewer::one_video_frame () const { @@ -795,3 +802,21 @@ FilmViewer::set_optimise_for_j2k (bool o) _video_view->set_optimise_for_j2k (o); } + +void +FilmViewer::set_crop_guess (Rect crop) +{ + if (crop != _crop_guess) { + _crop_guess = crop; + _video_view->update (); + } +} + + +void +FilmViewer::unset_crop_guess () +{ + _crop_guess = {}; + _video_view->update (); +} + diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h index 2373af122..1fa85a621 100644 --- a/src/wx/film_viewer.h +++ b/src/wx/film_viewer.h @@ -80,6 +80,7 @@ public: dcpomatic::DCPTime position () const { return _video_view->position(); } + boost::optional position_in_content (std::shared_ptr content) const; dcpomatic::DCPTime one_video_frame () const; void start (); @@ -99,6 +100,8 @@ public: void set_eyes (Eyes e); void set_pad_black (bool p); void set_optimise_for_j2k (bool o); + void set_crop_guess (dcpomatic::Rect crop); + void unset_crop_guess (); void slow_refresh (); @@ -133,6 +136,9 @@ public: } void finished (); void image_changed (std::shared_ptr video); + boost::optional> crop_guess () const { + return _crop_guess; + } bool pending_idle_get () const { return _idle_get; @@ -206,5 +212,7 @@ private: /** true if an get() is required next time we are idle */ bool _idle_get = false; + boost::optional> _crop_guess; + boost::signals2::scoped_connection _config_changed_connection; }; diff --git a/src/wx/gl_video_view.cc b/src/wx/gl_video_view.cc index ed8b8dbb2..be13cea43 100644 --- a/src/wx/gl_video_view.cc +++ b/src/wx/gl_video_view.cc @@ -195,12 +195,14 @@ static constexpr char fragment_source[] = "\n" "uniform sampler2D texture_sampler;\n" /* type = 0: draw outline content rectangle - * type = 1: draw XYZ image - * type = 2: draw RGB image + * type = 1: draw crop guess rectangle + * type = 2: draw XYZ image + * type = 3: draw RGB image * See FragmentType enum below. */ "uniform int type = 0;\n" "uniform vec4 outline_content_colour;\n" +"uniform vec4 crop_guess_colour;\n" "uniform mat4 colour_conversion;\n" "\n" "out vec4 FragColor;\n" @@ -262,6 +264,9 @@ static constexpr char fragment_source[] = " FragColor = outline_content_colour;\n" " break;\n" " case 1:\n" +" FragColor = crop_guess_colour;\n" +" break;\n" +" case 2:\n" " FragColor = texture_bicubic(texture_sampler, TexCoord);\n" " FragColor.x = pow(FragColor.x, IN_GAMMA) / DCI_COEFFICIENT;\n" " FragColor.y = pow(FragColor.y, IN_GAMMA) / DCI_COEFFICIENT;\n" @@ -271,7 +276,7 @@ static constexpr char fragment_source[] = " FragColor.y = pow(FragColor.y, OUT_GAMMA);\n" " FragColor.z = pow(FragColor.z, OUT_GAMMA);\n" " break;\n" -" case 2:\n" +" case 3:\n" " FragColor = texture_bicubic(texture_sampler, TexCoord);\n" " break;\n" " }\n" @@ -281,8 +286,9 @@ static constexpr char fragment_source[] = enum class FragmentType { OUTLINE_CONTENT = 0, - XYZ_IMAGE = 1, - RGB_IMAGE = 2, + CROP_GUESS = 1, + XYZ_IMAGE = 2, + RGB_IMAGE = 3, }; @@ -307,6 +313,8 @@ static constexpr int indices_subtitle_texture_offset = indices_video_texture_off static constexpr int indices_subtitle_texture_number = 6; static constexpr int indices_outline_content_offset = indices_subtitle_texture_offset + indices_subtitle_texture_number; static constexpr int indices_outline_content_number = 8; +static constexpr int indices_crop_guess_offset = indices_outline_content_offset + indices_outline_content_number; +static constexpr int indices_crop_guess_number = 8; static constexpr unsigned int indices[] = { 0, 1, 3, // video texture triangle #1 @@ -317,12 +325,17 @@ static constexpr unsigned int indices[] = { 9, 10, // outline content line #2 10, 11, // outline content line #3 11, 8, // outline content line #4 + 12, 13, // crop guess line #1 + 13, 14, // crop guess line #2 + 14, 15, // crop guess line #3 + 15, 12, // crop guess line #4 }; /* Offsets of things in the GL_ARRAY_BUFFER */ static constexpr int array_buffer_video_offset = 0; static constexpr int array_buffer_subtitle_offset = array_buffer_video_offset + 4 * 5 * sizeof(float); static constexpr int array_buffer_outline_content_offset = array_buffer_subtitle_offset + 4 * 5 * sizeof(float); +static constexpr int array_buffer_crop_guess_offset = array_buffer_outline_content_offset + 4 * 5 * sizeof(float); void @@ -440,6 +453,7 @@ GLVideoView::setup_shaders () _fragment_type = glGetUniformLocation (program, "type"); check_gl_error ("glGetUniformLocation"); set_outline_content_colour (program); + set_crop_guess_colour (program); auto conversion = dcp::ColourConversion::rec709_to_xyz(); boost::numeric::ublas::matrix matrix = conversion.xyz_to_rgb (); @@ -454,7 +468,7 @@ GLVideoView::setup_shaders () check_gl_error ("glGetUniformLocation"); glUniformMatrix4fv (colour_conversion, 1, GL_TRUE, gl_matrix); - glLineWidth (1.0f); + glLineWidth (2.0f); check_gl_error ("glLineWidth"); glEnable (GL_BLEND); check_gl_error ("glEnable"); @@ -462,7 +476,7 @@ GLVideoView::setup_shaders () check_gl_error ("glBlendFunc"); /* Reserve space for the GL_ARRAY_BUFFER */ - glBufferData(GL_ARRAY_BUFFER, 12 * 5 * sizeof(float), nullptr, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, 16 * 5 * sizeof(float), nullptr, GL_STATIC_DRAW); check_gl_error ("glBufferData"); } @@ -478,6 +492,17 @@ GLVideoView::set_outline_content_colour (GLuint program) } +void +GLVideoView::set_crop_guess_colour (GLuint program) +{ + auto uniform = glGetUniformLocation (program, "crop_guess_colour"); + check_gl_error ("glGetUniformLocation"); + auto colour = crop_guess_colour (); + glUniform4f (uniform, colour.Red() / 255.0f, colour.Green() / 255.0f, colour.Blue() / 255.0f, 1.0f); + check_gl_error ("glUniform4f"); +} + + void GLVideoView::draw () { @@ -512,6 +537,11 @@ GLVideoView::draw () glDrawElements (GL_LINES, indices_outline_content_number, GL_UNSIGNED_INT, reinterpret_cast(indices_outline_content_offset * sizeof(int))); check_gl_error ("glDrawElements"); } + if (auto guess = _viewer->crop_guess()) { + glUniform1i(_fragment_type, 1); + glDrawElements (GL_LINES, indices_crop_guess_number, GL_UNSIGNED_INT, reinterpret_cast(indices_crop_guess_offset * sizeof(int))); + check_gl_error ("glDrawElements"); + } glFlush(); check_gl_error ("glFlush"); @@ -554,6 +584,7 @@ GLVideoView::set_image (shared_ptr pv) auto const inter_position = player_video().first->inter_position(); auto const inter_size = player_video().first->inter_size(); auto const out_size = player_video().first->out_size(); + auto const crop_guess = _viewer->crop_guess(); auto x_offset = std::max(0, (canvas_width - out_size.width) / 2); auto y_offset = std::max(0, (canvas_height - out_size.height) / 2); @@ -563,6 +594,7 @@ GLVideoView::set_image (shared_ptr pv) _last_inter_position.set_next (inter_position); _last_inter_size.set_next (inter_size); _last_out_size.set_next (out_size); + _last_crop_guess.set_next (crop_guess); class Rectangle { @@ -632,8 +664,9 @@ GLVideoView::set_image (shared_ptr pv) float _vertices[20]; }; - if (_last_canvas_size.changed() || _last_inter_position.changed() || _last_inter_size.changed() || _last_out_size.changed()) { + auto const sizing_changed = _last_canvas_size.changed() || _last_inter_position.changed() || _last_inter_size.changed() || _last_out_size.changed(); + if (sizing_changed) { const auto video = _optimise_for_j2k ? Rectangle(canvas_size, inter_position.x + x_offset, inter_position.y + y_offset, inter_size) : Rectangle(canvas_size, x_offset, y_offset, out_size); @@ -646,6 +679,17 @@ GLVideoView::set_image (shared_ptr pv) check_gl_error ("glBufferSubData (outline_content)"); } + if ((sizing_changed || _last_crop_guess.changed()) && crop_guess) { + auto const crop_guess_rectangle = Rectangle( + canvas_size, + inter_position.x + x_offset + inter_size.width * crop_guess->x, + inter_position.y + y_offset + inter_size.height * crop_guess->y, + dcp::Size(inter_size.width * crop_guess->width, inter_size.height * crop_guess->height) + ); + glBufferSubData (GL_ARRAY_BUFFER, array_buffer_crop_guess_offset, crop_guess_rectangle.size(), crop_guess_rectangle.vertices()); + check_gl_error ("glBufferSubData (crop_guess_rectangle)"); + } + if (_have_subtitle_to_render) { const auto subtitle = Rectangle(canvas_size, inter_position.x + x_offset + text->position.x, inter_position.y + y_offset + text->position.y, text->image->size()); glBufferSubData (GL_ARRAY_BUFFER, array_buffer_subtitle_offset, subtitle.size(), subtitle.vertices()); diff --git a/src/wx/gl_video_view.h b/src/wx/gl_video_view.h index 273d50dc8..fd9154a73 100644 --- a/src/wx/gl_video_view.h +++ b/src/wx/gl_video_view.h @@ -100,6 +100,7 @@ private: void size_changed (wxSizeEvent const &); void setup_shaders (); void set_outline_content_colour (GLuint program); + void set_crop_guess_colour (GLuint program); wxGLCanvas* _canvas; wxGLContext* _context; @@ -130,6 +131,7 @@ private: Last> _last_inter_position; Last _last_inter_size; Last _last_out_size; + Last>> _last_crop_guess; boost::atomic _canvas_size; std::unique_ptr _video_texture; diff --git a/src/wx/simple_video_view.cc b/src/wx/simple_video_view.cc index 468546534..f866bd4a8 100644 --- a/src/wx/simple_video_view.cc +++ b/src/wx/simple_video_view.cc @@ -119,6 +119,19 @@ SimpleVideoView::paint () dc.DrawRectangle (subs->x * out_size.width, subs->y * out_size.height, subs->width * out_size.width, subs->height * out_size.height); } + if (_viewer->crop_guess()) { + wxPen p (crop_guess_colour(), 2); + dc.SetPen (p); + dc.SetBrush (*wxTRANSPARENT_BRUSH); + auto const crop_guess = _viewer->crop_guess().get(); + dc.DrawRectangle ( + _inter_position.x + _inter_size.width * crop_guess.x, + _inter_position.y + _inter_size.height * crop_guess.y, + _inter_size.width * crop_guess.width, + _inter_size.height * crop_guess.height + ); + } + _state_timer.unset(); } diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc index c2c2a8552..d6d7f6e4c 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -81,7 +81,7 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr film, w , _left_down (false) , _down_view_position (0) , _first_move (false) - , _menu (this) + , _menu (this, viewer) , _snap (true) , _tool (SELECT) , _x_scroll_rate (16) diff --git a/src/wx/video_view.cc b/src/wx/video_view.cc index ac6357e62..c271cb65e 100644 --- a/src/wx/video_view.cc +++ b/src/wx/video_view.cc @@ -185,4 +185,3 @@ VideoView::pad_colour () const return wxColour(240, 240, 240); } } - diff --git a/src/wx/video_view.h b/src/wx/video_view.h index a1cc41f07..b815afb7c 100644 --- a/src/wx/video_view.h +++ b/src/wx/video_view.h @@ -149,6 +149,10 @@ protected: return wxColour(0, 255, 0); } + wxColour crop_guess_colour () const { + return wxColour(0, 0, 255); + } + int video_frame_rate () const { boost::mutex::scoped_lock lm (_mutex); return _video_frame_rate; diff --git a/src/wx/wscript b/src/wx/wscript index 276cfc6f0..8da9b546c 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -31,6 +31,7 @@ sources = """ audio_mapping_view.cc audio_panel.cc audio_plot.cc + auto_crop_dialog.cc barco_alchemy_certificate_panel.cc batch_job_view.cc check_box.cc