1
// Copyright (c) 2005-2007, Rodrigo Braz Monteiro
2
// All rights reserved.
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
7
// * Redistributions of source code must retain the above copyright notice,
8
// this list of conditions and the following disclaimer.
9
// * Redistributions in binary form must reproduce the above copyright notice,
10
// this list of conditions and the following disclaimer in the documentation
11
// and/or other materials provided with the distribution.
12
// * Neither the name of the Aegisub Group nor the names of its contributors
13
// may be used to endorse or promote products derived from this software
14
// without specific prior written permission.
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
// POSSIBILITY OF SUCH DAMAGE.
28
// -----------------------------------------------------------------------------
32
// Website: http://aegisub.cellosoft.com
33
// Contact: mailto:zeratul@cellosoft.com
41
#include <wx/glcanvas.h>
43
#include <OpenGL/GL.h>
44
#include <OpenGL/glu.h>
51
#include <wx/clipbrd.h>
52
#include <wx/filename.h>
53
#include <wx/config.h>
55
#include "video_display.h"
56
#include "video_provider_manager.h"
60
#include "ass_dialogue.h"
61
#include "ass_style.h"
62
#include "subs_grid.h"
67
#include "video_out_gl.h"
69
#include "video_box.h"
71
#include "video_slider.h"
72
#include "visual_tool.h"
73
#include "visual_tool_cross.h"
74
#include "visual_tool_rotatez.h"
75
#include "visual_tool_rotatexy.h"
76
#include "visual_tool_scale.h"
77
#include "visual_tool_clip.h"
78
#include "visual_tool_vector_clip.h"
79
#include "visual_tool_drag.h"
85
VIDEO_MENU_COPY_COORDS = 1230,
86
VIDEO_MENU_COPY_TO_CLIPBOARD,
87
VIDEO_MENU_COPY_TO_CLIPBOARD_RAW,
88
VIDEO_MENU_SAVE_SNAPSHOT,
89
VIDEO_MENU_SAVE_SNAPSHOT_RAW
95
BEGIN_EVENT_TABLE(VideoDisplay, wxGLCanvas)
96
EVT_MOUSE_EVENTS(VideoDisplay::OnMouseEvent)
97
EVT_KEY_DOWN(VideoDisplay::OnKey)
98
EVT_PAINT(VideoDisplay::OnPaint)
99
EVT_SIZE(VideoDisplay::OnSizeEvent)
100
EVT_ERASE_BACKGROUND(VideoDisplay::OnEraseBackground)
102
EVT_MENU(VIDEO_MENU_COPY_COORDS,VideoDisplay::OnCopyCoords)
103
EVT_MENU(VIDEO_MENU_COPY_TO_CLIPBOARD,VideoDisplay::OnCopyToClipboard)
104
EVT_MENU(VIDEO_MENU_SAVE_SNAPSHOT,VideoDisplay::OnSaveSnapshot)
105
EVT_MENU(VIDEO_MENU_COPY_TO_CLIPBOARD_RAW,VideoDisplay::OnCopyToClipboardRaw)
106
EVT_MENU(VIDEO_MENU_SAVE_SNAPSHOT_RAW,VideoDisplay::OnSaveSnapshotRaw)
115
WX_GL_STENCIL_SIZE, 8, // stencil buffer is needed for the shape overlay drawing stuff
116
WX_GL_MIN_RED, 8, // wx implementation thing:
117
WX_GL_MIN_GREEN, 8, // wxMSW sets pfd.cColorBits to 0 if given an attribList
118
WX_GL_MIN_BLUE, 8, // where none of WX_GL_MIN_(RED|GREEN|BLUE) or WX_GL_BUFFER_SIZE are set.
119
WX_GL_BUFFER_SIZE, 24, // WX_GL_BUFFER_SIZE must be set last or it will get a nonsense value.
125
VideoDisplay::VideoDisplay(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
126
: wxGLCanvas (parent, id, attribList, pos, size, style, name)
127
, videoOut(new VideoOutGL())
132
ControlSlider = NULL;
133
PositionDisplay = NULL;
140
SetCursor(wxNullCursor);
148
VideoDisplay::~VideoDisplay () {
151
VideoContext::Get()->RemoveDisplay(this);
157
void VideoDisplay::ShowCursor(bool show) {
159
if (show) SetCursor(wxNullCursor);
163
// Bleeeh! Hate this 'solution':
165
static char cursor_image[] = {0};
166
wxCursor cursor(cursor_image, 8, 1, -1, -1, cursor_image);
168
wxCursor cursor(wxCURSOR_BLANK);
174
void VideoDisplay::SetFrame(int frameNumber) {
175
VideoContext *context = VideoContext::Get();
176
ControlSlider->SetValue(frameNumber);
178
// Get time for frame
179
int time = VFR_Output.GetTimeAtFrame(frameNumber, true, true);
180
int h = time / 3600000;
181
int m = time % 3600000 / 60000;
182
int s = time % 60000 / 1000;
183
int ms = time % 1000;
185
// Set the text box for frame number and time
186
PositionDisplay->SetValue(wxString::Format(_T("%01i:%02i:%02i.%03i - %i"), h, m, s, ms, frameNumber));
187
if (context->GetKeyFrames().Index(frameNumber) != wxNOT_FOUND) {
188
// Set the background color to indicate this is a keyframe
189
PositionDisplay->SetBackgroundColour(Options.AsColour(_T("Grid selection background")));
190
PositionDisplay->SetForegroundColour(Options.AsColour(_T("Grid selection foreground")));
193
PositionDisplay->SetBackgroundColour(wxNullColour);
194
PositionDisplay->SetForegroundColour(wxNullColour);
202
if (AssDialogue *curLine = context->curLine) {
203
startOff = time - curLine->Start.GetMS();
204
endOff = time - curLine->End.GetMS();
208
if (startOff > 0) startSign = _T("+");
209
if (endOff > 0) endSign = _T("+");
211
// Set the text box for time relative to active subtitle line
212
SubsPosition->SetValue(wxString::Format(_T("%s%ims; %s%ims"), startSign.c_str(), startOff, endSign.c_str(), endOff));
214
if (IsShownOnScreen() && visual) visual->Refresh();
216
// Render the new frame
217
if (context->IsLoaded()) {
218
AegiVideoFrame frame;
220
frame = context->GetFrame(frameNumber);
222
catch (const wxChar *err) {
224
_T("Failed seeking video. The video file may be corrupt or incomplete.\n")
225
_T("Error message reported: %s"),
228
catch (wxString err) {
230
_T("Failed seeking video. The video file may be corrupt or incomplete.\n")
231
_T("Error message reported: %s"),
236
_T("Failed seeking video. The video file may be corrupt or incomplete.\n")
237
_T("No further error message given."));
240
videoOut->UploadFrameData(frame);
242
catch (const VideoOutInitException& err) {
244
L"Failed to initialize video display. Closing other running programs and updating your video card drivers may fix this.\n"
245
L"Error message reported: %s",
246
err.GetMessage().c_str());
249
catch (const VideoOutRenderException& err) {
251
L"Could not upload video frame to graphics card.\n"
252
L"Error message reported: %s",
253
err.GetMessage().c_str());
258
currentFrame = frameNumber;
263
void VideoDisplay::Render() try {
264
if (!IsShownOnScreen()) return;
265
wxASSERT(wxIsMainThread());
267
VideoContext *context = VideoContext::Get();
269
if (!context->IsLoaded()) return;
272
SetCurrent(*context->GetGLContext(this));
275
int w, h, sw, sh, pw, ph;
276
GetClientSize(&w, &h);
279
context->GetScriptSize(sw, sh);
280
pw = context->GetWidth();
281
ph = context->GetHeight();
285
// Freesized transform
292
float thisAr = float(w)/float(h);
293
float vidAr = context->GetAspectRatioType() == 0 ? float(pw)/float(ph) : context->GetAspectRatioValue();
295
// Window is wider than video, blackbox left/right
296
if (thisAr - vidAr > 0.01f) {
297
int delta = int(w-vidAr*h);
302
// Video is wider than window, blackbox top/bottom
303
else if (vidAr - thisAr > 0.01f) {
304
int delta = int(h-w/vidAr);
310
videoOut->SetViewport(dx1, dy1, dx2, dy2);
311
videoOut->Render(sw, sh);
315
if (!context->IsPlaying())
323
catch (const VideoOutException &err) {
325
_T("An error occurred trying to render the video frame on the screen.\n")
326
_T("Error message reported: %s"),
327
err.GetMessage().c_str());
328
VideoContext::Get()->Reset();
332
_T("An error occurred trying to render the video frame to screen.\n")
333
_T("No further error message given."));
334
VideoContext::Get()->Reset();
338
///////////////////////////////////
339
// TV effects (overscan and so on)
340
void VideoDisplay::DrawTVEffects() {
343
VideoContext *context = VideoContext::Get();
344
context->GetScriptSize(sw,sh);
345
bool drawOverscan = Options.AsBool(_T("Show Overscan Mask"));
347
// Draw overscan mask
350
double ar = context->GetAspectRatioValue();
352
// Based on BBC's guidelines: http://www.bbc.co.uk/guidelines/dq/pdf/tv/tv_standards_london.pdf
355
DrawOverscanMask(int(sw * 0.1),int(sh * 0.05),wxColour(30,70,200),0.5);
356
DrawOverscanMask(int(sw * 0.035),int(sh * 0.035),wxColour(30,70,200),0.5);
359
// Less wide than 16:9 (use 4:3 standard)
361
DrawOverscanMask(int(sw * 0.067),int(sh * 0.05),wxColour(30,70,200),0.5);
362
DrawOverscanMask(int(sw * 0.033),int(sh * 0.035),wxColour(30,70,200),0.5);
368
//////////////////////
369
// Draw overscan mask
370
void VideoDisplay::DrawOverscanMask(int sizeH,int sizeV,wxColour colour,double alpha) {
373
VideoContext *context = VideoContext::Get();
374
context->GetScriptSize(sw,sh);
375
int rad1 = int(sh * 0.05);
376
int gapH = sizeH+rad1;
377
int gapV = sizeV+rad1;
378
int rad2 = (int)sqrt(double(gapH*gapH + gapV*gapV))+1;
382
gl.SetFillColour(colour,alpha);
383
gl.SetLineColour(wxColour(0,0,0),0.0,1);
386
gl.DrawRectangle(gapH,0,sw-gapH,sizeV); // Top
387
gl.DrawRectangle(sw-sizeH,gapV,sw,sh-gapV); // Right
388
gl.DrawRectangle(gapH,sh-sizeV,sw-gapH,sh); // Bottom
389
gl.DrawRectangle(0,gapV,sizeH,sh-gapV); // Left
392
gl.DrawRing(gapH,gapV,rad1,rad2,1.0,180.0,270.0); // Top-left
393
gl.DrawRing(sw-gapH,gapV,rad1,rad2,1.0,90.0,180.0); // Top-right
394
gl.DrawRing(sw-gapH,sh-gapV,rad1,rad2,1.0,0.0,90.0); // Bottom-right
395
gl.DrawRing(gapH,sh-gapV,rad1,rad2,1.0,270.0,360.0); // Bottom-left
404
void VideoDisplay::UpdateSize() {
405
// Don't do anything if it's a free sizing display
406
//if (freeSize) return;
408
VideoContext *con = VideoContext::Get();
410
if (!con->IsLoaded()) return;
411
if (!IsShownOnScreen()) return;
414
GetClientSize(&w,&h);
417
if (con->GetAspectRatioType() == 0) w = int(con->GetWidth() * zoomValue);
418
else w = int(con->GetHeight() * zoomValue * con->GetAspectRatioValue());
419
h = int(con->GetHeight() * zoomValue);
421
// Set Min and Max sizes. This sets the outer size, not client size, so it
422
// depends on us not having any borders on the control.
427
// Then tell the sizer to re-fit for us.
429
box->VideoSizer->Fit(box);
430
box->GetParent()->Layout();
432
// And finally properly resize to the wanted size, to avoid a glitch when
433
// switching between small zoom levels on Windows.
444
void VideoDisplay::Reset() {
445
// Only calculate sizes if it's visible
446
if (!IsShownOnScreen()) return;
447
int w = origSize.GetX();
448
int h = origSize.GetY();
456
SetSizeHints(_w,_h,_w,_h);
462
void VideoDisplay::OnPaint(wxPaintEvent& event) {
470
void VideoDisplay::OnSizeEvent(wxSizeEvent &event) {
481
void VideoDisplay::OnMouseEvent(wxMouseEvent& event) {
486
mouse_x = event.GetX();
487
mouse_y = event.GetY();
489
// Disable when playing
490
if (VideoContext::Get()->IsPlaying()) return;
493
if (event.ButtonUp(wxMOUSE_BTN_RIGHT)) {
496
menu.Append(VIDEO_MENU_SAVE_SNAPSHOT,_("Save PNG snapshot"));
497
menu.Append(VIDEO_MENU_COPY_TO_CLIPBOARD,_("Copy image to Clipboard"));
498
menu.AppendSeparator();
499
menu.Append(VIDEO_MENU_SAVE_SNAPSHOT_RAW,_("Save PNG snapshot (no subtitles)"));
500
menu.Append(VIDEO_MENU_COPY_TO_CLIPBOARD_RAW,_("Copy image to Clipboard (no subtitles)"));
501
menu.AppendSeparator();
502
menu.Append(VIDEO_MENU_COPY_COORDS,_("Copy coordinates to Clipboard"));
504
// Show cursor and popup
510
// Enforce correct cursor display
511
ShowCursor(visualMode != 0);
514
if (event.ButtonDown(wxMOUSE_BTN_ANY)) {
519
if (visual) visual->OnMouseEvent(event);
525
void VideoDisplay::OnKey(wxKeyEvent &event) {
526
// FIXME: should these be configurable?
527
// Think of the frenchmen and other people not using qwerty layout
528
if (event.GetKeyCode() == 'A') SetVisualMode(0);
529
if (event.GetKeyCode() == 'S') SetVisualMode(1);
530
if (event.GetKeyCode() == 'D') SetVisualMode(2);
531
if (event.GetKeyCode() == 'F') SetVisualMode(3);
532
if (event.GetKeyCode() == 'G') SetVisualMode(4);
533
if (event.GetKeyCode() == 'H') SetVisualMode(5);
534
if (event.GetKeyCode() == 'J') SetVisualMode(6);
542
void VideoDisplay::SetZoom(double value) {
548
//////////////////////
549
// Sets zoom position
550
void VideoDisplay::SetZoomPos(int value) {
551
if (value < 0) value = 0;
552
if (value > 15) value = 15;
553
SetZoom(double(value+1)/8.0);
554
if (zoomBox->GetSelection() != value) zoomBox->SetSelection(value);
557
/////////////////////
559
void VideoDisplay::OnCopyToClipboard(wxCommandEvent &event) {
560
if (wxTheClipboard->Open()) {
561
wxTheClipboard->SetData(new wxBitmapDataObject(wxBitmap(VideoContext::Get()->GetFrame(-1).GetImage(),24)));
562
wxTheClipboard->Close();
567
//////////////////////////
568
// Copy to clipboard (raw)
569
void VideoDisplay::OnCopyToClipboardRaw(wxCommandEvent &event) {
570
if (wxTheClipboard->Open()) {
571
wxTheClipboard->SetData(new wxBitmapDataObject(wxBitmap(VideoContext::Get()->GetFrame(-1,true).GetImage(),24)));
572
wxTheClipboard->Close();
579
void VideoDisplay::OnSaveSnapshot(wxCommandEvent &event) {
580
VideoContext::Get()->SaveSnapshot(false);
584
//////////////////////
585
// Save snapshot (raw)
586
void VideoDisplay::OnSaveSnapshotRaw(wxCommandEvent &event) {
587
VideoContext::Get()->SaveSnapshot(true);
591
/////////////////////
593
void VideoDisplay::OnCopyCoords(wxCommandEvent &event) {
594
if (wxTheClipboard->Open()) {
596
VideoContext::Get()->GetScriptSize(sw,sh);
597
int vx = (sw * mouse_x + w/2) / w;
598
int vy = (sh * mouse_y + h/2) / h;
599
wxTheClipboard->SetData(new wxTextDataObject(wxString::Format(_T("%i,%i"),vx,vy)));
600
wxTheClipboard->Close();
605
/////////////////////////////
606
// Convert mouse coordinates
607
void VideoDisplay::ConvertMouseCoords(int &x,int &y) {
609
GetClientSize(&w,&h);
621
void VideoDisplay::SetVisualMode(int mode) {
623
if (visualMode != mode) {
625
wxToolBar *toolBar = NULL;
627
toolBar = box->visualSubToolBar;
628
toolBar->ClearTools();
630
toolBar->Show(false);
631
if (!box->visualToolBar->GetToolState(mode + Video_Mode_Standard)) box->visualToolBar->ToggleTool(mode + Video_Mode_Standard,true);
638
case 0: visual = new VisualToolCross(this); break;
639
case 1: visual = new VisualToolDrag(this,toolBar); break;
640
case 2: visual = new VisualToolRotateZ(this); break;
641
case 3: visual = new VisualToolRotateXY(this); break;
642
case 4: visual = new VisualToolScale(this); break;
643
case 5: visual = new VisualToolClip(this); break;
644
case 6: visual = new VisualToolVectorClip(this,toolBar); break;
645
default: visual = NULL;
648
// Update size to reflect toolbar changes