diff options
| author | Carl Hetherington <cth@carlh.net> | 2022-01-01 21:20:51 +0000 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2022-01-16 00:38:53 +0100 |
| commit | 7b66e9c9de6df12f49b358d9496a9e64b68db550 (patch) | |
| tree | a7174cc9c15afb6ee7c8a462b4899d4e86ab4e82 | |
| parent | 348c4507bc8d37de6da9f4b2ecd1ae1b180c47d2 (diff) | |
Primitive auto-crop (#1477).
| -rw-r--r-- | src/lib/config.cc | 3 | ||||
| -rw-r--r-- | src/lib/config.h | 10 | ||||
| -rw-r--r-- | src/lib/player.cc | 18 | ||||
| -rw-r--r-- | src/lib/player.h | 3 | ||||
| -rw-r--r-- | src/lib/rect.h | 7 | ||||
| -rw-r--r-- | src/lib/video_content.cc | 6 | ||||
| -rw-r--r-- | src/lib/video_content.h | 1 | ||||
| -rw-r--r-- | src/wx/auto_crop_dialog.cc | 76 | ||||
| -rw-r--r-- | src/wx/auto_crop_dialog.h | 47 | ||||
| -rw-r--r-- | src/wx/content_menu.cc | 93 | ||||
| -rw-r--r-- | src/wx/content_menu.h | 18 | ||||
| -rw-r--r-- | src/wx/content_panel.cc | 2 | ||||
| -rw-r--r-- | src/wx/film_viewer.cc | 27 | ||||
| -rw-r--r-- | src/wx/film_viewer.h | 8 | ||||
| -rw-r--r-- | src/wx/gl_video_view.cc | 60 | ||||
| -rw-r--r-- | src/wx/gl_video_view.h | 2 | ||||
| -rw-r--r-- | src/wx/simple_video_view.cc | 13 | ||||
| -rw-r--r-- | src/wx/timeline.cc | 2 | ||||
| -rw-r--r-- | src/wx/video_view.cc | 1 | ||||
| -rw-r--r-- | src/wx/video_view.h | 4 | ||||
| -rw-r--r-- | src/wx/wscript | 1 |
21 files changed, 385 insertions, 17 deletions
diff --git a/src/lib/config.cc b/src/lib/config.cc index bf294aec9..da7082a8e 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -183,6 +183,7 @@ Config::set_defaults () _audio_mapping = boost::none; _custom_languages.clear (); _add_files_path = boost::none; + _auto_crop_threshold = 0.1; _allowed_dcp_frame_rates.clear (); _allowed_dcp_frame_rates.push_back (24); @@ -583,6 +584,7 @@ try } _add_files_path = f.optional_string_child("AddFilesPath"); + _auto_crop_threshold = f.optional_number_child<double>("AutoCropThreshold").get_value_or(0.1); if (boost::filesystem::exists (_cinemas_file)) { cxml::Document f ("Cinemas"); @@ -1014,6 +1016,7 @@ Config::write_config () const /* [XML] AddFilesPath The default path that will be offered in the picker when adding files to a film. */ root->add_child("AddFilesPath")->add_child_text(_add_files_path->string()); } + root->add_child("AutoCropThreshold")->add_child_text(raw_convert<string>(_auto_crop_threshold)); auto target = config_write_file(); diff --git a/src/lib/config.h b/src/lib/config.h index 5289656e3..bdf6c8395 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -84,6 +84,7 @@ public: HISTORY, SHOW_EXPERIMENTAL_AUDIO_PROCESSORS, AUDIO_MAPPING, + AUTO_CROP_THRESHOLD, OTHER }; @@ -547,6 +548,10 @@ public: return _add_files_path; } + double auto_crop_threshold () const { + return _auto_crop_threshold; + } + /* SET (mostly) */ void set_master_encoding_threads (int n) { @@ -1055,6 +1060,10 @@ public: changed (); } + void set_auto_crop_threshold (double threshold) { + maybe_set (_auto_crop_threshold, threshold, AUTO_CROP_THRESHOLD); + } + void changed (Property p = OTHER); boost::signals2::signal<void (Property)> Changed; /** Emitted if read() failed on an existing Config file. There is nothing @@ -1268,6 +1277,7 @@ private: boost::optional<AudioMapping> _audio_mapping; std::vector<dcp::LanguageTag> _custom_languages; boost::optional<boost::filesystem::path> _add_files_path; + double _auto_crop_threshold; static int const _current_version; diff --git a/src/lib/player.cc b/src/lib/player.cc index 01413115b..f1fd060f4 100644 --- a/src/lib/player.cc +++ b/src/lib/player.cc @@ -1384,7 +1384,7 @@ Player::set_dcp_decode_reduction (optional<int> reduction) optional<DCPTime> -Player::content_time_to_dcp (shared_ptr<Content> content, ContentTime t) +Player::content_time_to_dcp (shared_ptr<const Content> content, ContentTime t) { boost::mutex::scoped_lock lm (_mutex); @@ -1399,6 +1399,22 @@ Player::content_time_to_dcp (shared_ptr<Content> content, ContentTime t) } +optional<ContentTime> +Player::dcp_to_content_time (shared_ptr<const Content> 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<const Playlist> Player::playlist () const { diff --git a/src/lib/player.h b/src/lib/player.h index 0d0116e92..3bd293889 100644 --- a/src/lib/player.h +++ b/src/lib/player.h @@ -101,7 +101,8 @@ public: void set_play_referenced (); void set_dcp_decode_reduction (boost::optional<int> reduction); - boost::optional<dcpomatic::DCPTime> content_time_to_dcp (std::shared_ptr<Content> content, dcpomatic::ContentTime t); + boost::optional<dcpomatic::DCPTime> content_time_to_dcp (std::shared_ptr<const Content> content, dcpomatic::ContentTime t); + boost::optional<dcpomatic::ContentTime> dcp_to_content_time (std::shared_ptr<const Content> content, dcpomatic::DCPTime t); boost::signals2::signal<void (ChangeType, int, bool)> 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 <class T> +bool operator== (Rect<T> const& a, Rect<T> 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 27eb04fe0..1b24101f3 100644 --- a/src/lib/video_content.cc +++ b/src/lib/video_content.cc @@ -532,6 +532,12 @@ VideoContent::set_length (Frame len) void +VideoContent::set_crop (Crop c) +{ + maybe_set (_crop, c, VideoContentProperty::CROP); +} + +void VideoContent::set_left_crop (int c) { maybe_set (_crop.left, c, VideoContentProperty::CROP); diff --git a/src/lib/video_content.h b/src/lib/video_content.h index 2adf941d9..d16af14d8 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 <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#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 <cth@carlh.net> + + 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 <http://www.gnu.org/licenses/>. + +*/ + + +#include "table_dialog.h" +#include "lib/types.h" +#include <boost/signals2.hpp> + + +class SpinCtrl; + + +class AutoCropDialog : public TableDialog +{ +public: + AutoCropDialog (wxWindow* parent, Crop crop); + + Crop get () const; + void set (Crop crop); + + boost::signals2::signal<void (Crop)> 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 5d208acc5..48d5354ef 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/ffmpeg_content.h" #include "lib/film.h" #include "lib/find_missing.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 <dcp/cpl.h> #include <dcp/decrypted_kdm.h> #include <dcp/exceptions.h> @@ -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<FilmViewer> 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> 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<DCPContent> (_content.front()); @@ -481,3 +491,84 @@ ContentMenu::cpl_selected (wxCommandEvent& ev) DCPOMATIC_ASSERT (film); JobManager::instance()->add (make_shared<ExamineContentJob>(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<float>( + static_cast<float>(std::max(0, crop.left - current_crop.left)) / content->video->size().width, + static_cast<float>(std::max(0, crop.top - current_crop.top)) / content->video->size().height, + 1.0f - (static_cast<float>(std::max(0, crop.left - current_crop.left + crop.right - current_crop.right)) / content->video->size().width), + 1.0f - (static_cast<float>(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<PlayerVideo>) { + 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 03f657d38..147f95946 100644 --- a/src/wx/content_menu.h +++ b/src/wx/content_menu.h @@ -18,22 +18,28 @@ */ + #ifndef DCPOMATIC_CONTENT_MENU_H #define DCPOMATIC_CONTENT_MENU_H + #include "timeline_content_view.h" #include "lib/types.h" #include <wx/wx.h> #include <memory> + +class AutoCropDialog; +class DCPContent; class Film; +class FilmViewer; class Job; -class DCPContent; + class ContentMenu { public: - explicit ContentMenu (wxWindow* p); + ContentMenu (wxWindow* parent, std::weak_ptr<FilmViewer> viewer); ContentMenu (ContentMenu const &) = delete; ContentMenu& operator= (ContentMenu const &) = delete; @@ -47,6 +53,7 @@ private: void properties (); void advanced (); void re_examine (); + void auto_crop (); void kdm (); void ov (); void set_dcp_settings (); @@ -59,6 +66,7 @@ private: std::weak_ptr<Film> _film; wxWindow* _parent; bool _pop_up_open; + std::weak_ptr<FilmViewer> _viewer; ContentList _content; TimelineContentViewList _views; wxMenuItem* _repeat; @@ -67,11 +75,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 b2b72b216..e2a236937 100644 --- a/src/wx/content_panel.cc +++ b/src/wx/content_panel.cc @@ -84,7 +84,7 @@ ContentPanel::ContentPanel (wxNotebook* n, shared_ptr<Film> film, weak_ptr<FilmV _splitter = new LimitedSplitter (n); _top_panel = new wxPanel (_splitter); - _menu = new ContentMenu (_splitter); + _menu = new ContentMenu (_splitter, _film_viewer); { auto s = new wxBoxSizer (wxHORIZONTAL); diff --git a/src/wx/film_viewer.cc b/src/wx/film_viewer.cc index d91a9db9c..e2f09f238 100644 --- a/src/wx/film_viewer.cc +++ b/src/wx/film_viewer.cc @@ -118,7 +118,7 @@ FilmViewer::~FilmViewer () } -/** Ask for ::get() to be called next time we are idle */ +/** Ask for ::idle_handler() to be called next time we are idle */ void FilmViewer::request_idle_display_next_frame () { @@ -712,6 +712,13 @@ FilmViewer::dcp_decode_reduction () const } +optional<ContentTime> +FilmViewer::position_in_content (shared_ptr<const Content> 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<float> 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 64ac885e3..af15ded94 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<dcpomatic::ContentTime> position_in_content (std::shared_ptr<const Content> 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<float> crop); + void unset_crop_guess (); void slow_refresh (); @@ -133,6 +136,9 @@ public: } void finished (); void image_changed (std::shared_ptr<PlayerVideo> video); + boost::optional<dcpomatic::Rect<float>> 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<dcpomatic::Rect<float>> _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 ed99e3430..39280c905 100644 --- a/src/wx/gl_video_view.cc +++ b/src/wx/gl_video_view.cc @@ -196,12 +196,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" @@ -263,6 +265,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" @@ -272,7 +277,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" @@ -282,8 +287,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, }; @@ -308,6 +314,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 @@ -318,12 +326,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 @@ -439,6 +452,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<double> matrix = conversion.xyz_to_rgb (); @@ -453,7 +467,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"); @@ -461,7 +475,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 () { auto pad = pad_colour(); @@ -511,6 +536,11 @@ GLVideoView::draw () glDrawElements (GL_LINES, indices_outline_content_number, GL_UNSIGNED_INT, reinterpret_cast<void*>(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<void*>(indices_crop_guess_offset * sizeof(int))); + check_gl_error ("glDrawElements"); + } glFlush(); check_gl_error ("glFlush"); @@ -553,6 +583,7 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> 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); @@ -562,6 +593,7 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> 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 { @@ -631,8 +663,9 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> 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); @@ -645,6 +678,17 @@ GLVideoView::set_image (shared_ptr<const PlayerVideo> 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 098b8ce8e..7a3aa41b0 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<Position<int>> _last_inter_position; Last<dcp::Size> _last_inter_size; Last<dcp::Size> _last_out_size; + Last<boost::optional<dcpomatic::Rect<float>>> _last_crop_guess; boost::atomic<wxSize> _canvas_size; std::unique_ptr<Texture> _video_texture; diff --git a/src/wx/simple_video_view.cc b/src/wx/simple_video_view.cc index 7f816c31d..45fcf4405 100644 --- a/src/wx/simple_video_view.cc +++ b/src/wx/simple_video_view.cc @@ -117,6 +117,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 1c5937ae0..60b17c45c 100644 --- a/src/wx/timeline.cc +++ b/src/wx/timeline.cc @@ -77,7 +77,7 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> 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 1c645f11f..64226e88c 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 5353f213f..496888d43 100644 --- a/src/wx/video_view.h +++ b/src/wx/video_view.h @@ -146,6 +146,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 1976397d1..8569872b7 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 |
