1 /* qv4l2: a control panel controlling v4l2 devices.
2 *
3 * Copyright (C) 2006 Hans Verkuil <hverkuil@xs4all.nl>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include "capture-win.h"
21
22 #undef min
23 #undef max
24
25 #include <QCloseEvent>
26 #include <QLabel>
27 #include <QImage>
28 #include <QVBoxLayout>
29 #include <QApplication>
30 #if QT_VERSION < 0x050a00
31 #include <QDesktopWidget>
32 #endif
33 #include <QScreen>
34 #include <QWindow>
35
36 #include <math.h>
37
38 #define MIN_WIN_SIZE_WIDTH 16
39 #define MIN_WIN_SIZE_HEIGHT 12
40
41 bool CaptureWin::m_enableScaling = true;
42 double CaptureWin::m_pixelAspectRatio = 1.0;
43 CropMethod CaptureWin::m_cropMethod = QV4L2_CROP_NONE;
44
CaptureWin(ApplicationWindow * aw)45 CaptureWin::CaptureWin(ApplicationWindow *aw) :
46 m_appWin(aw)
47 {
48 setWindowTitle("V4L2 Capture");
49 m_hotkeyScaleReset = new QShortcut(Qt::CTRL|Qt::Key_F, this);
50 connect(m_hotkeyScaleReset, SIGNAL(activated()), this, SLOT(resetSize()));
51 connect(aw->m_resetScalingAct, SIGNAL(triggered()), this, SLOT(resetSize()));
52 m_hotkeyExitFullscreen = new QShortcut(Qt::Key_Escape, this);
53 connect(m_hotkeyExitFullscreen, SIGNAL(activated()), this, SLOT(escape()));
54 m_hotkeyToggleFullscreen = new QShortcut(Qt::Key_F, this);
55 connect(m_hotkeyToggleFullscreen, SIGNAL(activated()), aw->m_makeFullScreenAct, SLOT(toggle()));
56 m_exitFullScreen = new QAction(QIcon(":/fullscreenexit.png"), "Exit Fullscreen", this);
57 m_exitFullScreen->setShortcut(m_hotkeyToggleFullscreen->key());
58 connect(m_exitFullScreen, SIGNAL(triggered()), this, SLOT(escape()));
59 m_enterFullScreen = new QAction(QIcon(":/fullscreen.png"), "Show Fullscreen", this);
60 m_enterFullScreen ->setShortcut(m_hotkeyToggleFullscreen->key());
61 connect(m_enterFullScreen, SIGNAL(triggered()), this, SLOT(fullScreen()));
62 // Add the action to allow the hotkey to start/stop the stream
63 addAction(m_appWin->m_capStartAct);
64
65 m_closeWindowAct = new QAction(QIcon(":/fileclose.png"), "&Close Window", this);
66 m_closeWindowAct->setStatusTip("Close");
67 QList<QKeySequence> shortcuts;
68 // More standard close window shortcut
69 shortcuts << (Qt::CTRL|Qt::Key_W);
70 // Historic qv4l2 shortcut
71 shortcuts << Qt::Key_Q;
72 m_closeWindowAct->setShortcuts(shortcuts);
73 addAction(m_closeWindowAct);
74 connect(m_closeWindowAct, SIGNAL(triggered()), this, SLOT(close()));
75
76 m_frame.format = 0;
77 m_frame.size.setWidth(0);
78 m_frame.size.setHeight(0);
79 m_frame.planeData[0] = NULL;
80 m_frame.planeData[1] = NULL;
81 m_frame.planeData[2] = NULL;
82 m_crop.delta.setWidth(0);
83 m_crop.delta.setHeight(0);
84 m_crop.size.setWidth(0);
85 m_crop.size.setHeight(0);
86 m_crop.updated = 0;
87 m_scaledSize.setWidth(0);
88 m_scaledSize.setHeight(0);
89 m_origFrameSize.setWidth(0);
90 m_origFrameSize.setHeight(0);
91 m_windowSize.setWidth(0);
92 m_windowSize.setHeight(0);
93 }
94
~CaptureWin()95 CaptureWin::~CaptureWin()
96 {
97 if (layout() == NULL)
98 return;
99
100 layout()->removeWidget(this);
101 delete layout();
102 delete m_hotkeyScaleReset;
103 }
104
setFrame(int width,int height,__u32 format,unsigned char * data,unsigned char * data2,unsigned char * data3)105 void CaptureWin::setFrame(int width, int height, __u32 format,
106 unsigned char *data, unsigned char *data2, unsigned char *data3)
107 {
108 m_frame.planeData[0] = data;
109 m_frame.planeData[1] = data2;
110 m_frame.planeData[2] = data3;
111
112 m_frame.updated = false;
113 if (width != m_frame.size.width() || height != m_frame.size.height()
114 || format != m_frame.format) {
115 m_frame.size.setHeight(height);
116 m_frame.size.setWidth(width);
117 m_frame.format = format;
118 m_frame.updated = true;
119 updateSize();
120 }
121
122 setRenderFrame();
123 }
124
buildWindow(QWidget * videoSurface)125 void CaptureWin::buildWindow(QWidget *videoSurface)
126 {
127 int l, t, r, b;
128 m_vboxLayout = new QVBoxLayout(this);
129 m_vboxLayout->getContentsMargins(&l, &t, &r, &b);
130 #if QT_VERSION < 0x060000
131 m_vboxLayout->setMargin(0);
132 #else
133 m_vboxLayout->setContentsMargins(0, 0, 0, 0);
134 #endif
135 m_vboxLayout->addWidget(videoSurface, 1000, Qt::AlignCenter);
136
137 setContextMenuPolicy(Qt::CustomContextMenu);
138 connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customMenuRequested(QPoint)));
139 }
140
resetSize()141 void CaptureWin::resetSize()
142 {
143 // Force resize even if no size change
144 QSize resetFrameSize = m_origFrameSize;
145 m_origFrameSize.setWidth(0);
146 m_origFrameSize.setHeight(0);
147
148 setWindowSize(resetFrameSize);
149 }
150
cropSize(QSize size)151 QSize CaptureWin::cropSize(QSize size)
152 {
153 QSize croppedSize = size;
154 double realWidth = size.width() * m_pixelAspectRatio;
155 double realHeight = size.height() / m_pixelAspectRatio;
156 double aspectRatio = 1;
157
158 switch (m_cropMethod) {
159 case QV4L2_CROP_P43:
160 aspectRatio = 4.0 / 3.0;
161 break;
162 case QV4L2_CROP_W149:
163 aspectRatio = 14.0 / 9.0;
164 break;
165 case QV4L2_CROP_W169:
166 aspectRatio = 16.0 / 9.0;
167 break;
168 case QV4L2_CROP_C185:
169 aspectRatio = 1.85;
170 break;
171 case QV4L2_CROP_C239:
172 aspectRatio = 2.39;
173 break;
174 case QV4L2_CROP_TB:
175 croppedSize.setHeight(size.height() - 2);
176 break;
177 default:
178 break; // No cropping
179 }
180
181 if ((m_cropMethod != QV4L2_CROP_TB) && (m_cropMethod != QV4L2_CROP_NONE)) {
182 if (realWidth / size.height() < aspectRatio)
183 croppedSize.setHeight(realWidth / aspectRatio);
184 else
185 croppedSize.setWidth(realHeight * aspectRatio);
186 }
187
188 if (croppedSize.width() >= size.width())
189 croppedSize.setWidth(size.width());
190 if (croppedSize.width() < MIN_WIN_SIZE_WIDTH)
191 croppedSize.setWidth(MIN_WIN_SIZE_WIDTH);
192 if (croppedSize.height() >= size.height())
193 croppedSize.setHeight(size.height());
194 if (croppedSize.height() < MIN_WIN_SIZE_HEIGHT)
195 croppedSize.setHeight(MIN_WIN_SIZE_HEIGHT);
196
197 return croppedSize;
198 }
199
updateSize()200 void CaptureWin::updateSize()
201 {
202 m_crop.updated = false;
203 if (m_frame.updated) {
204 m_scaledSize = scaleFrameSize(m_windowSize, m_frame.size);
205 m_crop.size = cropSize(m_frame.size);
206 m_crop.delta = (m_frame.size - m_crop.size) / 2;
207 m_crop.updated = true;
208 }
209 }
210
setCropMethod(CropMethod crop)211 void CaptureWin::setCropMethod(CropMethod crop)
212 {
213 m_cropMethod = crop;
214 resetSize();
215 }
216
pixelAspectFrameSize(QSize size)217 QSize CaptureWin::pixelAspectFrameSize(QSize size)
218 {
219 if (!m_enableScaling)
220 return size;
221
222 if (m_pixelAspectRatio > 1)
223 size.rwidth() *= m_pixelAspectRatio;
224
225 if (m_pixelAspectRatio < 1)
226 size.rheight() /= m_pixelAspectRatio;
227
228 return size;
229 }
230
getMargins()231 QSize CaptureWin::getMargins()
232 {
233 int l, t, r, b;
234 layout()->getContentsMargins(&l, &t, &r, &b);
235 return QSize(l + r, t + b);
236 }
237
enableScaling(bool enable)238 void CaptureWin::enableScaling(bool enable)
239 {
240 if (!enable) {
241 QSize margins = getMargins();
242 QWidget::setMinimumSize(m_origFrameSize.width() + margins.width(),
243 m_origFrameSize.height() + margins.height());
244 } else {
245 QWidget::setMinimumSize(MIN_WIN_SIZE_WIDTH, MIN_WIN_SIZE_HEIGHT);
246 }
247 m_enableScaling = enable;
248 resetSize();
249 }
250
setWindowSize(QSize frameSize)251 void CaptureWin::setWindowSize(QSize frameSize)
252 {
253 // Dont resize window if the frame size is the same in
254 // the event the window has been paused when beeing resized.
255 if (frameSize == m_origFrameSize)
256 return;
257
258 m_origFrameSize = frameSize;
259
260 QSize margins = getMargins();
261 #if QT_VERSION < 0x050a00
262 QDesktopWidget *screen = QApplication::desktop();
263 QRect resolution = screen->screenGeometry();
264 #else
265 QSize resolution(1920, 1080);
266
267 // window()->windowHandle() can be NULL, for example when
268 // using X11 forwarding.
269 if (window()->windowHandle()) {
270 QScreen *screen = window()->windowHandle()->screen();
271 resolution = screen->availableSize();
272 }
273 #endif
274
275 QSize windowSize = pixelAspectFrameSize(cropSize(frameSize)) + margins;
276
277 if (windowSize.width() > resolution.width())
278 windowSize.setWidth(resolution.width());
279 if (windowSize.width() < MIN_WIN_SIZE_WIDTH)
280 windowSize.setWidth(MIN_WIN_SIZE_WIDTH);
281
282 if (windowSize.height() > resolution.height())
283 windowSize.setHeight(resolution.height());
284 if (windowSize.height() < MIN_WIN_SIZE_HEIGHT)
285 windowSize.setHeight(MIN_WIN_SIZE_HEIGHT);
286
287 QWidget::setMinimumSize(MIN_WIN_SIZE_WIDTH, MIN_WIN_SIZE_HEIGHT);
288
289 m_frame.size = frameSize;
290
291 QWidget::resize(windowSize);
292 }
293
scaleFrameSize(QSize window,QSize frame)294 QSize CaptureWin::scaleFrameSize(QSize window, QSize frame)
295 {
296 QSize actualSize = pixelAspectFrameSize(cropSize(frame));
297
298 if (!m_enableScaling) {
299 window.setWidth(actualSize.width());
300 window.setHeight(actualSize.height());
301 }
302
303 qreal newW, newH;
304 if (window.width() >= window.height()) {
305 newW = (qreal)window.width() / actualSize.width();
306 newH = (qreal)window.height() / actualSize.height();
307 } else {
308 newH = (qreal)window.width() / actualSize.width();
309 newW = (qreal)window.height() / actualSize.height();
310 }
311 qreal resized = (qreal)std::min(newW, newH);
312
313 return (actualSize * resized);
314 }
315
setPixelAspectRatio(double ratio)316 void CaptureWin::setPixelAspectRatio(double ratio)
317 {
318 m_pixelAspectRatio = ratio;
319 resetSize();
320 }
321
getHorScaleFactor()322 float CaptureWin::getHorScaleFactor()
323 {
324 float ow, sw, wscale;
325
326 sw = m_scaledSize.width();
327 ow = m_origFrameSize.width();
328 wscale = floor(100 * (sw / ow)) / 100.0;
329
330 return wscale;
331 }
332
getVertScaleFactor()333 float CaptureWin::getVertScaleFactor()
334 {
335 float oh, sh, hscale;
336
337 sh = m_scaledSize.height();
338 oh = m_origFrameSize.height();
339 hscale = floor(100 * (sh / oh)) / 100.0;
340
341 return hscale;
342 }
343
mouseDoubleClickEvent(QMouseEvent * e)344 void CaptureWin::mouseDoubleClickEvent(QMouseEvent *e)
345 {
346 m_appWin->m_makeFullScreenAct->toggle();
347 }
348
escape()349 void CaptureWin::escape()
350 {
351 m_appWin->m_makeFullScreenAct->setChecked(false);
352 }
353
fullScreen()354 void CaptureWin::fullScreen()
355 {
356 m_appWin->m_makeFullScreenAct->setChecked(true);
357 }
358
makeFullScreen(bool enable)359 void CaptureWin::makeFullScreen(bool enable)
360 {
361 if (enable) {
362 showFullScreen();
363 setStyleSheet("background-color:#000000;");
364 } else {
365 showNormal();
366 setStyleSheet("background-color:none;");
367 }
368 QSize resetFrameSize = m_origFrameSize;
369 m_origFrameSize.setWidth(0);
370 m_origFrameSize.setHeight(0);
371
372 setWindowSize(resetFrameSize);
373 }
374
customMenuRequested(QPoint pos)375 void CaptureWin::customMenuRequested(QPoint pos)
376 {
377 QMenu *menu = new QMenu(this);
378
379 if (isFullScreen()) {
380 menu->addAction(m_exitFullScreen);
381 menu->setStyleSheet("background-color:none;");
382 } else {
383 menu->addAction(m_enterFullScreen);
384 }
385
386 menu->addAction(m_appWin->m_capStartAct);
387 menu->addAction(m_appWin->m_capStepAct);
388 menu->addAction(m_appWin->m_resetScalingAct);
389 if (m_appWin->m_useBlendingAct)
390 menu->addAction(m_appWin->m_useBlendingAct);
391 if (m_appWin->m_useLinearAct)
392 menu->addAction(m_appWin->m_useLinearAct);
393 menu->addAction(m_appWin->m_snapshotAct);
394 menu->addAction(m_appWin->m_showFramesAct);
395 menu->addMenu(m_appWin->m_overrideColorspaceMenu);
396 menu->addMenu(m_appWin->m_overrideXferFuncMenu);
397 menu->addMenu(m_appWin->m_overrideYCbCrEncMenu);
398 menu->addMenu(m_appWin->m_overrideQuantizationMenu);
399 menu->addAction(m_closeWindowAct);
400
401 menu->popup(mapToGlobal(pos));
402 }
403
closeEvent(QCloseEvent * event)404 void CaptureWin::closeEvent(QCloseEvent *event)
405 {
406 QWidget::closeEvent(event);
407 emit close();
408 }
409