Various bits mostly related to colour conversions.
[dcpomatic.git] / src / wx / video_panel.cc
1 /*
2     Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <wx/spinctrl.h>
21 #include "lib/ratio.h"
22 #include "lib/filter.h"
23 #include "lib/ffmpeg_content.h"
24 #include "lib/colour_conversion.h"
25 #include "lib/config.h"
26 #include "filter_dialog.h"
27 #include "video_panel.h"
28 #include "wx_util.h"
29 #include "film_editor.h"
30 #include "content_colour_conversion_dialog.h"
31
32 using std::vector;
33 using std::string;
34 using std::pair;
35 using std::cout;
36 using std::list;
37 using boost::shared_ptr;
38 using boost::dynamic_pointer_cast;
39 using boost::bind;
40 using boost::optional;
41
42 VideoPanel::VideoPanel (FilmEditor* e)
43         : FilmEditorPanel (e, _("Video"))
44 {
45         wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
46         _sizer->Add (grid, 0, wxALL, 8);
47
48         int r = 0;
49
50         add_label_to_grid_bag_sizer (grid, this, _("Type"), true, wxGBPosition (r, 0));
51         _frame_type = new wxChoice (this, wxID_ANY);
52         grid->Add (_frame_type, wxGBPosition (r, 1));
53         ++r;
54         
55         add_label_to_grid_bag_sizer (grid, this, _("Left crop"), true, wxGBPosition (r, 0));
56         _left_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
57         grid->Add (_left_crop, wxGBPosition (r, 1));
58         ++r;
59
60         add_label_to_grid_bag_sizer (grid, this, _("Right crop"), true, wxGBPosition (r, 0));
61         _right_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
62         grid->Add (_right_crop, wxGBPosition (r, 1));
63         ++r;
64         
65         add_label_to_grid_bag_sizer (grid, this, _("Top crop"), true, wxGBPosition (r, 0));
66         _top_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
67         grid->Add (_top_crop, wxGBPosition (r, 1));
68         ++r;
69         
70         add_label_to_grid_bag_sizer (grid, this, _("Bottom crop"), true, wxGBPosition (r, 0));
71         _bottom_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
72         grid->Add (_bottom_crop, wxGBPosition (r, 1));
73         ++r;
74
75         add_label_to_grid_bag_sizer (grid, this, _("Scale to"), true, wxGBPosition (r, 0));
76         _ratio = new wxChoice (this, wxID_ANY);
77         grid->Add (_ratio, wxGBPosition (r, 1));
78         ++r;
79
80         {
81                 add_label_to_grid_bag_sizer (grid, this, _("Filters"), true, wxGBPosition (r, 0));
82                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
83
84                 wxClientDC dc (this);
85                 wxSize size = dc.GetTextExtent (wxT ("A quite long name"));
86                 size.SetHeight (-1);
87                 
88                 _filters = new wxStaticText (this, wxID_ANY, _("None"), wxDefaultPosition, size);
89                 s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
90                 _filters_button = new wxButton (this, wxID_ANY, _("Edit..."));
91                 s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL);
92                 grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
93         }
94         ++r;
95         
96         {
97                 add_label_to_grid_bag_sizer (grid, this, _("Colour conversion"), true, wxGBPosition (r, 0));
98                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
99
100                 wxClientDC dc (this);
101                 wxSize size = dc.GetTextExtent (wxT ("A quite long name"));
102                 size.SetHeight (-1);
103                 
104                 _colour_conversion = new wxStaticText (this, wxID_ANY, wxT (""), wxDefaultPosition, size);
105
106                 s->Add (_colour_conversion, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
107                 _colour_conversion_button = new wxButton (this, wxID_ANY, _("Edit..."));
108                 s->Add (_colour_conversion_button, 0, wxALIGN_CENTER_VERTICAL);
109                 grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
110         }
111         ++r;
112
113         _description = new wxStaticText (this, wxID_ANY, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize);
114         grid->Add (_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
115         wxFont font = _description->GetFont();
116         font.SetStyle(wxFONTSTYLE_ITALIC);
117         font.SetPointSize(font.GetPointSize() - 1);
118         _description->SetFont(font);
119         ++r;
120
121         _left_crop->SetRange (0, 1024);
122         _top_crop->SetRange (0, 1024);
123         _right_crop->SetRange (0, 1024);
124         _bottom_crop->SetRange (0, 1024);
125
126         vector<Ratio const *> ratios = Ratio::all ();
127         _ratio->Clear ();
128         for (vector<Ratio const *>::iterator i = ratios.begin(); i != ratios.end(); ++i) {
129                 _ratio->Append (std_to_wx ((*i)->nickname ()));
130         }
131
132         _frame_type->Append (_("2D"));
133         _frame_type->Append (_("3D left/right"));
134
135         _frame_type->Bind               (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&VideoPanel::frame_type_changed, this));
136         _left_crop->Bind                (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::left_crop_changed, this));
137         _right_crop->Bind               (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::right_crop_changed, this));
138         _top_crop->Bind                 (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::top_crop_changed, this));
139         _bottom_crop->Bind              (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::bottom_crop_changed, this));
140         _ratio->Bind                    (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&VideoPanel::ratio_changed, this));
141         _filters_button->Bind           (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_filters_clicked, this));
142         _colour_conversion_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_colour_conversion_clicked, this));
143 }
144
145
146 /** Called when the left crop widget has been changed */
147 void
148 VideoPanel::left_crop_changed ()
149 {
150         shared_ptr<VideoContent> c = _editor->selected_video_content ();
151         if (!c) {
152                 return;
153         }
154
155         c->set_left_crop (_left_crop->GetValue ());
156 }
157
158 /** Called when the right crop widget has been changed */
159 void
160 VideoPanel::right_crop_changed ()
161 {
162         shared_ptr<VideoContent> c = _editor->selected_video_content ();
163         if (!c) {
164                 return;
165         }
166
167         c->set_right_crop (_right_crop->GetValue ());
168 }
169
170 /** Called when the top crop widget has been changed */
171 void
172 VideoPanel::top_crop_changed ()
173 {
174         shared_ptr<VideoContent> c = _editor->selected_video_content ();
175         if (!c) {
176                 return;
177         }
178
179         c->set_top_crop (_top_crop->GetValue ());
180 }
181
182 /** Called when the bottom crop value has been changed */
183 void
184 VideoPanel::bottom_crop_changed ()
185 {
186         shared_ptr<VideoContent> c = _editor->selected_video_content ();
187         if (!c) {
188                 return;
189         }
190
191         c->set_bottom_crop (_bottom_crop->GetValue ());
192 }
193
194 void
195 VideoPanel::film_changed (Film::Property property)
196 {
197         switch (property) {
198         case Film::CONTAINER:
199         case Film::VIDEO_FRAME_RATE:
200                 setup_description ();
201                 break;
202         default:
203                 break;
204         }
205 }
206
207 void
208 VideoPanel::film_content_changed (shared_ptr<Content> c, int property)
209 {
210         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
211         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
212
213         if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
214                 checked_set (_frame_type, vc ? vc->video_frame_type () : VIDEO_FRAME_TYPE_2D);
215                 setup_description ();
216         } else if (property == VideoContentProperty::VIDEO_CROP) {
217                 checked_set (_left_crop,   vc ? vc->crop().left : 0);
218                 checked_set (_right_crop,  vc ? vc->crop().right        : 0);
219                 checked_set (_top_crop,    vc ? vc->crop().top  : 0);
220                 checked_set (_bottom_crop, vc ? vc->crop().bottom : 0);
221                 setup_description ();
222         } else if (property == VideoContentProperty::VIDEO_RATIO) {
223                 if (vc) {
224                         int n = 0;
225                         vector<Ratio const *> ratios = Ratio::all ();
226                         vector<Ratio const *>::iterator i = ratios.begin ();
227                         while (i != ratios.end() && *i != vc->ratio()) {
228                                 ++i;
229                                 ++n;
230                         }
231
232                         if (i == ratios.end()) {
233                                 checked_set (_ratio, -1);
234                         } else {
235                                 checked_set (_ratio, n);
236                         }
237                 } else {
238                         checked_set (_ratio, -1);
239                 }
240                 setup_description ();
241         } else if (property == VideoContentProperty::VIDEO_FRAME_RATE) {
242                 setup_description ();
243         } else if (property == VideoContentProperty::COLOUR_CONVERSION) {
244                 optional<size_t> preset = vc ? vc->colour_conversion().preset () : optional<size_t> ();
245                 vector<PresetColourConversion> cc = Config::instance()->colour_conversions ();
246                 _colour_conversion->SetLabel (preset ? std_to_wx (cc[preset.get()].name) : _("Custom"));
247         } else if (property == FFmpegContentProperty::FILTERS) {
248                 if (fc) {
249                         pair<string, string> p = Filter::ffmpeg_strings (fc->filters ());
250                         if (p.first.empty () && p.second.empty ()) {
251                                 _filters->SetLabel (_("None"));
252                         } else {
253                                 string const b = p.first + " " + p.second;
254                                 _filters->SetLabel (std_to_wx (b));
255                         }
256                 }
257         }
258 }
259
260 /** Called when the `Edit filters' button has been clicked */
261 void
262 VideoPanel::edit_filters_clicked ()
263 {
264         shared_ptr<Content> c = _editor->selected_content ();
265         if (!c) {
266                 return;
267         }
268
269         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
270         if (!fc) {
271                 return;
272         }
273         
274         FilterDialog* d = new FilterDialog (this, fc->filters());
275         d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1));
276         d->ShowModal ();
277         d->Destroy ();
278 }
279
280 void
281 VideoPanel::setup_description ()
282 {
283         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
284         if (!vc) {
285                 _description->SetLabel ("");
286                 return;
287         }
288
289         wxString d;
290
291         int lines = 0;
292
293         if (vc->video_size().width && vc->video_size().height) {
294                 d << wxString::Format (
295                         _("Content video is %dx%d (%.2f:1)\n"),
296                         vc->video_size_after_3d_split().width, vc->video_size_after_3d_split().height,
297                         float (vc->video_size_after_3d_split().width) / vc->video_size_after_3d_split().height
298                         );
299                 ++lines;
300         }
301
302         Crop const crop = vc->crop ();
303         if ((crop.left || crop.right || crop.top || crop.bottom) && vc->video_size() != libdcp::Size (0, 0)) {
304                 libdcp::Size cropped = vc->video_size_after_3d_split ();
305                 cropped.width -= crop.left + crop.right;
306                 cropped.height -= crop.top + crop.bottom;
307                 d << wxString::Format (
308                         _("Cropped to %dx%d (%.2f:1)\n"),
309                         cropped.width, cropped.height,
310                         float (cropped.width) / cropped.height
311                         );
312                 ++lines;
313         }
314
315         Ratio const * ratio = vc->ratio ();
316         if (ratio) {
317                 libdcp::Size container_size = _editor->film()->container()->size (_editor->film()->full_frame ());
318                 
319                 libdcp::Size const scaled = ratio->size (container_size);
320                 d << wxString::Format (
321                         _("Scaled to %dx%d (%.2f:1)\n"),
322                         scaled.width, scaled.height,
323                         float (scaled.width) / scaled.height
324                         );
325                 ++lines;
326
327                 if (scaled != container_size) {
328                         d << wxString::Format (
329                                 _("Padded with black to %dx%d (%.2f:1)\n"),
330                                 container_size.width, container_size.height,
331                                 float (container_size.width) / container_size.height
332                                 );
333                         ++lines;
334                 }
335         }
336
337         d << wxString::Format (_("Content frame rate %.4f\n"), vc->video_frame_rate ());
338         ++lines;
339         FrameRateConversion frc (vc->video_frame_rate(), _editor->film()->video_frame_rate ());
340         d << frc.description << "\n";
341         ++lines;
342
343         for (int i = lines; i < 6; ++i) {
344                 d << wxT ("\n ");
345         }
346
347         _description->SetLabel (d);
348         _sizer->Layout ();
349 }
350
351
352 void
353 VideoPanel::ratio_changed ()
354 {
355         if (!_editor->film ()) {
356                 return;
357         }
358
359         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
360         
361         int const n = _ratio->GetSelection ();
362         if (n >= 0) {
363                 vector<Ratio const *> ratios = Ratio::all ();
364                 assert (n < int (ratios.size()));
365                 vc->set_ratio (ratios[n]);
366         }
367 }
368
369 void
370 VideoPanel::frame_type_changed ()
371 {
372         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
373         if (vc) {
374                 vc->set_video_frame_type (static_cast<VideoFrameType> (_frame_type->GetSelection ()));
375         }
376 }
377
378 void
379 VideoPanel::edit_colour_conversion_clicked ()
380 {
381         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
382         if (!vc) {
383                 return;
384         }
385
386         ColourConversion conversion = vc->colour_conversion ();
387         ContentColourConversionDialog* d = new ContentColourConversionDialog (this);
388         d->set (conversion);
389         d->ShowModal ();
390
391         vc->set_colour_conversion (d->get ());
392         d->Destroy ();
393 }