MythTV  master
videooutwindow.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) Daniel Kristjansson, Jens Rehaag 2008
3  *
4  * This class encapsulates some of the video framing information,
5  * so that a VideoOutput class can have multiple concurrent video
6  * windows displayed at any one time.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 // Qt
24 #include <QApplication>
25 
26 // MythtTV
27 #include "mythconfig.h"
28 #include "mythmiscutil.h"
29 #include "osd.h"
30 #include "mythplayer.h"
31 #include "videodisplayprofile.h"
32 #include "decoderbase.h"
33 #include "mythcorecontext.h"
34 #include "videooutwindow.h"
35 
36 // Std
37 #include <cmath>
38 
39 #define LOC QString("VideoWin: ")
40 
41 static float fix_aspect(float raw);
42 static float snap(float value, float snapto, float diff);
43 
49 
51  : m_display(nullptr)
52 {
53  m_dbPipSize = gCoreContext->GetNumSetting("PIPSize", 26);
54 
55  m_dbMove = QPoint(gCoreContext->GetNumSetting("xScanDisplacement", 0),
56  gCoreContext->GetNumSetting("yScanDisplacement", 0));
57  m_dbUseGUISize = gCoreContext->GetBoolSetting("GuiSizeForTV", false);
58 }
59 
60 void VideoOutWindow::ScreenChanged(QScreen */*screen*/)
61 {
63  MoveResize();
64 }
65 
67 {
68  if (!m_display)
69  return;
70 
71  qApp->processEvents();
72  if (not qobject_cast<QApplication*>(qApp))
73  return;
74 
75  QScreen *screen = m_display->GetCurrentScreen();
76  if (!screen)
77  return;
78 
80  {
81  m_screenGeometry = screen->virtualGeometry();
82  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Window using all screens %1x%2")
83  .arg(m_screenGeometry.width()).arg(m_screenGeometry.height()));
84  return;
85  }
86 
87  m_screenGeometry = screen->geometry();
88  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Window using screen %1 %2x%3")
89  .arg(screen->name()).arg(m_screenGeometry.width()).arg(m_screenGeometry.height()));
90 }
91 
103 {
104  // for 'portrait' mode, rotate the 'screen' dimensions
105  float tempdisplayaspect = m_displayAspect;
106  bool rotate = (m_rotation == 90) || (m_rotation == -90);
107  if (rotate)
108  Rotate();
109 
110  // Preset all image placement and sizing variables.
111  m_videoRect = QRect(QPoint(0, 0), m_videoDispDim);
113 
114  // Apply various modifications
118 
119  // Interactive TV (MHEG) embedding
120  if (m_itvResizing)
122 
123  // and switch back
124  if (rotate)
125  Rotate();
126  m_displayAspect = tempdisplayaspect;
127 
129 
130  // TODO fine tune when these are emitted - it is not enough to just check whether the values
131  // have changed
136 }
137 
148 {
149  m_dbMove = QPoint(m_dbMove.y(), m_dbMove.x());
150  float temp = m_dbHorizScale;
152  m_dbVertScale = temp;
153  temp = m_manualHorizScale;
155  m_manualVertScale = temp;
156  m_manualMove = QPoint(m_manualMove.y(), m_manualMove.x());
158 
159  m_displayVisibleRect = QRect(QPoint(m_displayVisibleRect.top(), m_displayVisibleRect.left()),
160  QSize(m_displayVisibleRect.height(), m_displayVisibleRect.width()));
161  m_displayVideoRect = QRect(QPoint(m_displayVideoRect.top(), m_displayVideoRect.left()),
162  QSize(m_displayVideoRect.height(), m_displayVideoRect.width()));
171 }
172 
183 {
184  if (m_dbVertScale > 0)
185  {
186  // Veritcal overscan. Move the Y start point in original image.
187  float tmp = 1.0F - 2.0F * m_dbVertScale;
188  m_videoRect.moveTop(qRound(m_videoRect.height() * m_dbVertScale));
189  m_videoRect.setHeight(qRound(m_videoRect.height() * tmp));
190 
191  // If there is an offset, apply it now that we have a room.
192  int yoff = m_dbMove.y();
193  if (yoff > 0)
194  {
195  // To move the image down, move the start point up.
196  // Don't offset the image more than we have overscanned.
197  yoff = min(m_videoRect.top(), yoff);
198  m_videoRect.moveTop(m_videoRect.top() - yoff);
199  }
200  else if (yoff < 0)
201  {
202  // To move the image up, move the start point down.
203  // Don't offset the image more than we have overscanned.
204  if (abs(yoff) > m_videoRect.top())
205  yoff = 0 - m_videoRect.top();
206  m_videoRect.moveTop(m_videoRect.top() - yoff);
207  }
208  }
209  else if (m_dbVertScale < 0)
210  {
211  // Vertical underscan. Move the starting Y point in the display window.
212  // Use the abolute value of scan factor.
213  float vscanf = fabs(m_dbVertScale);
214  float tmp = 1.0F - 2.0F * vscanf;
215 
216  m_displayVideoRect.moveTop(qRound(m_displayVisibleRect.height() * vscanf) + m_displayVisibleRect.top());
217  m_displayVideoRect.setHeight(qRound(m_displayVisibleRect.height() * tmp));
218 
219  // Now offset the image within the extra blank space created by
220  // underscanning. To move the image down, increase the Y offset
221  // inside the display window.
222  int yoff = m_dbMove.y();
223  if (yoff > 0)
224  {
225  // Don't offset more than we have underscanned.
226  yoff = min(m_displayVideoRect.top(), yoff);
227  m_displayVideoRect.moveTop(m_displayVideoRect.top() + yoff);
228  }
229  else if (yoff < 0)
230  {
231  // Don't offset more than we have underscanned.
232  if (abs(yoff) > m_displayVideoRect.top())
233  yoff = 0 - m_displayVideoRect.top();
234  m_displayVideoRect.moveTop(m_displayVideoRect.top() + yoff);
235  }
236  }
237 
238  // Horizontal.. comments, same as vertical...
239  if (m_dbHorizScale > 0)
240  {
241  float tmp = 1.0F - 2.0F * m_dbHorizScale;
242  m_videoRect.moveLeft(qRound(m_videoDispDim.width() * m_dbHorizScale));
243  m_videoRect.setWidth(qRound(m_videoDispDim.width() * tmp));
244 
245  int xoff = m_dbMove.x();
246  if (xoff > 0)
247  {
248  xoff = min(m_videoRect.left(), xoff);
249  m_videoRect.moveLeft(m_videoRect.left() - xoff);
250  }
251  else if (xoff < 0)
252  {
253  if (abs(xoff) > m_videoRect.left())
254  xoff = 0 - m_videoRect.left();
255  m_videoRect.moveLeft(m_videoRect.left() - xoff);
256  }
257  }
258  else if (m_dbHorizScale < 0)
259  {
260  float hscanf = fabs(m_dbHorizScale);
261  float tmp = 1.0F - 2.0F * hscanf;
262 
263  m_displayVideoRect.moveLeft(qRound(m_displayVisibleRect.width() * hscanf) + m_displayVisibleRect.left());
264  m_displayVideoRect.setWidth(qRound(m_displayVisibleRect.width() * tmp));
265 
266  int xoff = m_dbMove.x();
267  if (xoff > 0)
268  {
269  xoff = min(m_displayVideoRect.left(), xoff);
270  m_displayVideoRect.moveLeft(m_displayVideoRect.left() + xoff);
271  }
272  else if (xoff < 0)
273  {
274  if (abs(xoff) > m_displayVideoRect.left())
275  xoff = 0 - m_displayVideoRect.left();
276  m_displayVideoRect.moveLeft(m_displayVideoRect.left() + xoff);
277  }
278  }
279 
280 }
281 
286 {
287  if ((m_manualVertScale != 1.0F) || (m_manualHorizScale != 1.0F))
288  {
289  QSize newsz = QSize(qRound(m_displayVideoRect.width() * m_manualHorizScale),
290  qRound(m_displayVideoRect.height() * m_manualVertScale));
291  QSize tmp = (m_displayVideoRect.size() - newsz) / 2;
292  QPoint chgloc = QPoint(tmp.width(), tmp.height());
293  QPoint newloc = m_displayVideoRect.topLeft() + chgloc;
294 
295  m_displayVideoRect = QRect(newloc, newsz);
296  }
297 
298  if (m_manualMove.y())
299  {
300  int move_vert = m_manualMove.y() * m_displayVideoRect.height() / 100;
301  m_displayVideoRect.moveTop(m_displayVideoRect.top() + move_vert);
302  }
303 
304  if (m_manualMove.x())
305  {
306  int move_horiz = m_manualMove.x() * m_displayVideoRect.width() / 100;
307  m_displayVideoRect.moveLeft(m_displayVideoRect.left() + move_horiz);
308  }
309 }
310 
311 // Code should take into account the aspect ratios of both the video as
312 // well as the actual screen to allow proper letterboxing to take place.
314 {
315  float disp_aspect = fix_aspect(GetDisplayAspect());
316  float aspect_diff = disp_aspect - m_videoAspectOverride;
317  bool aspects_match = abs(aspect_diff / disp_aspect) <= 0.02F;
318  bool nomatch_with_fill = !aspects_match && ((kAdjustFill_HorizontalStretch == m_adjustFill) ||
320  bool nomatch_without_fill = (!aspects_match) && !nomatch_with_fill;
321 
322  // Adjust for video/display aspect ratio mismatch
323  if (nomatch_with_fill && (disp_aspect > m_videoAspectOverride))
324  {
325  int pixNeeded = qRound(((disp_aspect / m_videoAspectOverride) * static_cast<float>(m_displayVideoRect.height())));
326  m_displayVideoRect.moveTop(m_displayVideoRect.top() + (m_displayVideoRect.height() - pixNeeded) / 2);
327  m_displayVideoRect.setHeight(pixNeeded);
328  }
329  else if (nomatch_with_fill)
330  {
331  int pixNeeded = qRound(((m_videoAspectOverride / disp_aspect) * static_cast<float>(m_displayVideoRect.width())));
332  m_displayVideoRect.moveLeft(m_displayVideoRect.left() + (m_displayVideoRect.width() - pixNeeded) / 2);
333  m_displayVideoRect.setWidth(pixNeeded);
334  }
335  else if (nomatch_without_fill && (disp_aspect > m_videoAspectOverride))
336  {
337  int pixNeeded = qRound(((m_videoAspectOverride / disp_aspect) * static_cast<float>(m_displayVideoRect.width())));
338  m_displayVideoRect.moveLeft(m_displayVideoRect.left() + (m_displayVideoRect.width() - pixNeeded) / 2);
339  m_displayVideoRect.setWidth(pixNeeded);
340  }
341  else if (nomatch_without_fill)
342  {
343  int pixNeeded = qRound(((disp_aspect / m_videoAspectOverride) * static_cast<float>(m_displayVideoRect.height())));
344  m_displayVideoRect.moveTop(m_displayVideoRect.top() + (m_displayVideoRect.height() - pixNeeded) / 2);
345  m_displayVideoRect.setHeight(pixNeeded);
346  }
347 
348  // Process letterbox zoom modes
350  {
351  // Zoom mode -- Expand by 4/3 and overscan.
352  // 1/6 of original is 1/8 of new
353  m_displayVideoRect = QRect(
354  m_displayVideoRect.left() - (m_displayVideoRect.width() / 6),
355  m_displayVideoRect.top() - (m_displayVideoRect.height() / 6),
356  m_displayVideoRect.width() * 4 / 3,
357  m_displayVideoRect.height() * 4 / 3);
358  }
359  else if (m_adjustFill == kAdjustFill_Half)
360  {
361  // Zoom mode -- Expand by 7/6 and overscan.
362  // Intended for eliminating the top bars on 14:9 material.
363  // Also good compromise for 4:3 material on 16:9 screen.
364  // Expanding by 7/6, so remove 1/6 of original from overscan;
365  // take half from each side, so remove 1/12.
366  m_displayVideoRect = QRect(
367  m_displayVideoRect.left() - (m_displayVideoRect.width() / 12),
368  m_displayVideoRect.top() - (m_displayVideoRect.height() / 12),
369  m_displayVideoRect.width() * 7 / 6,
370  m_displayVideoRect.height() * 7 / 6);
371  }
373  {
374  // Horizontal Stretch mode -- 1/6 of original is 1/8 of new
375  // Intended to be used to eliminate side bars on 4:3 material
376  // encoded to 16:9.
377  m_displayVideoRect.moveLeft(
378  m_displayVideoRect.left() - (m_displayVideoRect.width() / 6));
379 
380  m_displayVideoRect.setWidth(m_displayVideoRect.width() * 4 / 3);
381  }
383  {
384  // Vertical Stretch mode -- 1/6 of original is 1/8 of new
385  // Intended to be used to eliminate top/bottom bars on 16:9
386  // material encoded to 4:3.
387  m_displayVideoRect.moveTop(
388  m_displayVideoRect.top() - (m_displayVideoRect.height() / 6));
389 
390  m_displayVideoRect.setHeight(m_displayVideoRect.height() * 4 / 3);
391  }
392  else if (m_adjustFill == kAdjustFill_VerticalFill && m_displayVideoRect.height() > 0)
393  {
394  // Video fills screen vertically. May be cropped left and right
395  float factor = static_cast<float>(m_displayVisibleRect.height()) / static_cast<float>(m_displayVideoRect.height());
396  QSize newsize = QSize(qRound(m_displayVideoRect.width() * factor),
397  qRound(m_displayVideoRect.height() * factor));
398  QSize temp = (m_displayVideoRect.size() - newsize) / 2;
399  QPoint newloc = m_displayVideoRect.topLeft() + QPoint(temp.width(), temp.height());
400  m_displayVideoRect = QRect(newloc, newsize);
401  }
403  {
404  // Video fills screen horizontally. May be cropped top and bottom
405  float factor = static_cast<float>(m_displayVisibleRect.width()) /
406  static_cast<float>(m_displayVideoRect.width());
407  QSize newsize = QSize(qRound(m_displayVideoRect.width() * factor),
408  qRound(m_displayVideoRect.height() * factor));
409  QSize temp = (m_displayVideoRect.size() - newsize) / 2;
410  QPoint newloc = m_displayVideoRect.topLeft() + QPoint(temp.width(), temp.height());
411  m_displayVideoRect = QRect(newloc, newsize);
412  }
413 }
414 
415 bool VideoOutWindow::Init(const QSize &VideoDim, const QSize &VideoDispDim,
416  float Aspect, const QRect &WindowRect,
418 {
419  if (!m_display && Display)
420  {
421  m_display = Display;
423  }
424 
425  if (m_display)
426  {
427  QString dummy;
428  m_displayAspect = static_cast<float>(m_display->GetAspectRatio(dummy));
429  }
430 
431  // Refresh the geometry in case the video mode has changed
433 
434  // N.B. we are always confined to the window size so use that for the initial
435  // displayVisibleRect
436  m_windowRect = m_displayVisibleRect = WindowRect;
437 
438  int pbp_width = m_displayVisibleRect.width() / 2;
440  m_displayVisibleRect.setWidth(pbp_width);
441 
442  if (m_pipState == kPBPRight)
443  m_displayVisibleRect.moveLeft(pbp_width);
444 
445  m_videoDispDim = Fix1088(VideoDispDim);
446  m_videoDim = VideoDim;
447  m_videoRect = QRect(m_displayVisibleRect.topLeft(), m_videoDispDim);
448 
449  if (m_pipState > kPIPOff)
450  {
453  }
454  else
455  {
458  }
459  m_embedding = false;
460  SetVideoAspectRatio(Aspect);
461  MoveResize();
462  return true;
463 }
464 
466 {
467  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Window Rect: %1x%2+%3+%4")
468  .arg(m_windowRect.width()).arg(m_windowRect.height())
469  .arg(m_windowRect.left()).arg(m_windowRect.top()));
470  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Display Rect: %1x%2+%3+%4 Aspect: %5")
471  .arg(m_displayVideoRect.width()).arg(m_displayVideoRect.height())
472  .arg(m_displayVideoRect.left()).arg(m_displayVideoRect.top())
473  .arg(static_cast<qreal>(fix_aspect(GetDisplayAspect()))));
474  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Video Rect: %1x%2+%3+%4 Aspect: %5")
475  .arg(m_videoRect.width()).arg(m_videoRect.height())
476  .arg(m_videoRect.left()).arg(m_videoRect.top())
477  .arg(static_cast<qreal>(m_videoAspectOverride)));
478 }
479 
488 {
489  m_videoAspect = aspect;
491 }
492 
499 {
500  if (!qFuzzyCompare(Aspect, m_videoAspect))
501  {
502  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New video aspect ratio: '%1'")
503  .arg(static_cast<double>(Aspect)));
504  SetVideoAspectRatio(Aspect);
505  MoveResize();
506  }
507 }
508 
514 void VideoOutWindow::InputChanged(const QSize &VideoDim, const QSize &VideoDispDim, float Aspect)
515 {
516  if (Aspect < 0.0F)
517  Aspect = m_videoAspect;
518 
519  QSize newvideodispdim = Fix1088(VideoDispDim);
520 
521  if (!((VideoDim == m_videoDim) && (newvideodispdim == m_videoDispDim) &&
522  qFuzzyCompare(Aspect + 100.0F, m_videoAspect + 100.0F)))
523  {
524  m_videoDispDim = newvideodispdim;
525  m_videoDim = VideoDim;
526  SetVideoAspectRatio(Aspect);
527  LOG(VB_PLAYBACK, LOG_INFO, LOC +
528  QString("New video parameters: Size %1x%2 DisplaySize: %3x%4 Aspect: %5")
529  .arg(m_videoDim.width()).arg(m_videoDim.height())
530  .arg(m_videoDispDim.width()).arg(m_videoDispDim.height())
531  .arg(static_cast<double>(m_videoAspect)));
532  MoveResize();
533  }
534 }
535 
536 QSize VideoOutWindow::Fix1088(QSize Dimensions)
537 {
538  QSize result = Dimensions;
539  // 544 represents a 1088 field
540  if (result.width() == 1920 || result.width() == 1440)
541  {
542  if (result.height() == 1088)
543  result.setHeight(1080);
544  else if (result.height() == 544)
545  result.setHeight(540);
546  }
547  return result;
548 }
549 
555 {
556  return { QPoint(0, 0), m_videoDispDim };
557 }
558 
566 {
568  AdjustFill = static_cast<AdjustFillMode>((m_adjustFill + 1) % kAdjustFill_END);
569  if (m_adjustFill != AdjustFill)
570  {
572  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New fill mode: '%1'").arg(toString(m_adjustFill)));
573  MoveResize();
574  }
575 }
576 
581 {
582  float oldvert = m_dbVertScale;
583  float oldhoriz = m_dbHorizScale;
584 
585  if (Change)
586  {
587  m_dbVertScale = gCoreContext->GetNumSetting("VertScanPercentage", 0) * 0.01F;
588  m_dbHorizScale = gCoreContext->GetNumSetting("HorizScanPercentage", 0) * 0.01F;
589  m_dbScalingAllowed = true;
590  }
591  else
592  {
593  m_dbVertScale = 0.0F;
594  m_dbHorizScale = 0.0F;
595  m_dbScalingAllowed = false;
596  }
597 
598  if (!(qFuzzyCompare(oldvert + 100.0F, m_dbVertScale + 100.0F) &&
599  qFuzzyCompare(oldhoriz + 100.0F, m_dbHorizScale + 100.0F)))
600  {
601  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Over/underscan. V: %1, H: %2")
602  .arg(static_cast<double>(m_dbVertScale)).arg(static_cast<double>(m_dbHorizScale)));
603  MoveResize();
604  }
605 }
606 
607 void VideoOutWindow::SetDisplayAspect(float DisplayAspect)
608 {
609  if (!qFuzzyCompare(DisplayAspect + 10.0F, m_displayAspect + 10.0F))
610  {
611  LOG(VB_GENERAL, LOG_INFO, LOC + QString("New display aspect: %1")
612  .arg(static_cast<double>(DisplayAspect)));
613  m_displayAspect = DisplayAspect;
614  MoveResize();
615  }
616 }
617 
619 {
620  if (Size != m_windowRect.size())
621  {
622  QRect rect(m_windowRect.topLeft(), Size);
623  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New window rect: %1x%2+%3+%4")
624  .arg(rect.width()).arg(rect.height()).arg(rect.left()).arg(rect.top()));
626  MoveResize();
627  }
628 }
629 
631 {
632  QRect oldrect = m_itvDisplayVideoRect;
633  if (Rect.isEmpty())
634  {
635  m_itvResizing = false;
636  m_itvDisplayVideoRect = QRect();
637  }
638  else
639  {
640  m_itvResizing = true;
641  m_itvDisplayVideoRect = Rect;
642  }
643  if (m_itvDisplayVideoRect != oldrect)
644  {
645  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New ITV display rect: %1x%2+%3+%4")
646  .arg(m_itvDisplayVideoRect.width()).arg(m_itvDisplayVideoRect.height())
647  .arg(m_itvDisplayVideoRect.left()).arg(m_itvDisplayVideoRect.right()));
648  MoveResize();
649  }
650 }
651 
656 void VideoOutWindow::SetRotation(int Rotation)
657 {
658  if (Rotation == m_rotation)
659  return;
660  if ((Rotation < -180) || (Rotation > 180))
661  return;
662 
663  m_rotation = Rotation;
664  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New rotation: %1").arg(m_rotation));
665  MoveResize();
666 }
667 
671 void VideoOutWindow::ResizeDisplayWindow(const QRect &Rect, bool SaveVisibleRect)
672 {
673  if (SaveVisibleRect)
675  m_displayVisibleRect = Rect;
676  MoveResize();
677 }
678 
684 void VideoOutWindow::EmbedInWidget(const QRect &Rect)
685 {
686  if (m_embedding && (Rect == m_embeddingRect))
687  return;
688  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New embedding rect: %1x%2+%3+%4")
689  .arg(Rect.width()).arg(Rect.height()).arg(Rect.left()).arg(Rect.top()));
690  m_embeddingRect = Rect;
691  bool savevisiblerect = !m_embedding;
692  m_embedding = true;
693  m_displayVideoRect = Rect;
694  ResizeDisplayWindow(m_displayVideoRect, savevisiblerect);
695 }
696 
702 {
703  if (!m_embedding)
704  return;
705  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Stopped embedding");
706  m_embeddingRect = QRect();
708  m_embedding = false;
709  MoveResize();
710 }
711 
718 QRect VideoOutWindow::GetVisibleOSDBounds(float &VisibleAspect,
719  float &FontScaling,
720  float ThemeAspect) const
721 {
722  float dv_w = ((static_cast<float>(m_videoDispDim.width())) / m_displayVideoRect.width());
723  float dv_h = ((static_cast<float>(m_videoDispDim.height())) / m_displayVideoRect.height());
724 
725  int right_overflow = max((m_displayVideoRect.width() + m_displayVideoRect.left()) - m_displayVisibleRect.width(), 0);
726  int lower_overflow = max((m_displayVideoRect.height() + m_displayVideoRect.top()) - m_displayVisibleRect.height(), 0);
727 
728  bool isPBP = (kPBPLeft == m_pipState || kPBPRight == m_pipState);
729  if (isPBP)
730  {
731  right_overflow = 0;
732  lower_overflow = 0;
733  }
734 
735  // top left and bottom right corners respecting letterboxing
736  QPoint tl = QPoint((static_cast<int>(max(-m_displayVideoRect.left(), 0) * dv_w)) & ~1,
737  (static_cast<int>(max(-m_displayVideoRect.top(), 0) * dv_h)) & ~1);
738  QPoint br = QPoint(static_cast<int>(floor(m_videoDispDim.width() - (right_overflow * dv_w))),
739  static_cast<int>(floor(m_videoDispDim.height() - (lower_overflow * dv_h))));
740  // adjust for overscan
741  if ((m_dbVertScale > 0.0F) || (m_dbHorizScale > 0.0F))
742  {
743  QRect v(tl, br);
744  float xs = (m_dbHorizScale > 0.0F) ? m_dbHorizScale : 0.0F;
745  float ys = (m_dbVertScale > 0.0F) ? m_dbVertScale : 0.0F;
746  QPoint s(qRound((v.width() * xs)), qRound((v.height() * ys)));
747  tl += s;
748  br -= s;
749  }
750  // Work around Qt bug, QRect(QPoint(0,0), QPoint(0,0)) has area 1.
751  QRect vb(tl.x(), tl.y(), br.x() - tl.x(), br.y() - tl.y());
752 
753  // The calculation is completely bogus if the video is not centered
754  // which happens in the EPG, where we don't actually care about the OSD.
755  // So we just make sure the width and height are positive numbers
756  vb = QRect(vb.x(), vb.y(), abs(vb.width()), abs(vb.height()));
757 
758  // set the physical aspect ratio of the displayable area
759  const float dispPixelAdj = m_displayVisibleRect.width() ?
761  / m_displayVisibleRect.width() : 1.F;
762 
763  float vs = m_videoRect.height() ? static_cast<float>(m_videoRect.width()) / m_videoRect.height() : 1.0F;
764  VisibleAspect = ThemeAspect / dispPixelAdj * (m_videoAspectOverride > 0.0F ? vs / m_videoAspectOverride : 1.F);
765 
766  if (ThemeAspect > 0.0F)
767  {
768  // now adjust for scaling of the video on the size
769  float tmp = sqrtf(2.0F/(sq(VisibleAspect / ThemeAspect) + 1.0F));
770  if (tmp > 0.0F)
771  FontScaling = 1.0F / tmp;
772  // now adjust for aspect ratio effect on font size
773  // (should be in osd.cpp?)
774  FontScaling *= sqrtf(m_videoAspectOverride / ThemeAspect);
775  }
776 
777  if (isPBP)
778  FontScaling *= 0.65F;
779  return vb;
780 }
781 
789 {
790  if (m_pipState > kPIPOff)
791  return;
792 
793  if (AspectMode == kAspect_Toggle)
794  AspectMode = static_cast<AspectOverrideMode>(((m_videoAspectOverrideMode + 1) % kAspect_END));
795 
796  if (m_videoAspectOverrideMode != AspectMode)
797  {
798  m_videoAspectOverrideMode = AspectMode;
799  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New video aspect override: '%1'")
802  MoveResize();
803  }
804 }
805 
811 {
812  if (IsEmbedding())
813  return false;
814 
815  return m_displayVideoRect.contains(m_windowRect);
816 }
817 
822 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
824 {
825  QRegion visible(m_windowRect);
826  QRegion video(m_displayVideoRect);
827  return visible.subtracted(video);
828 }
829 #endif
830 
831 /*
832  * \brief Determines PIP Window size and Position.
833  */
835  PIPLocation Location, MythPlayer *PiPPlayer, bool DoPixelAdjustment) const
836 {
837  QRect position;
838 
839  float pipVideoAspect = PiPPlayer ? PiPPlayer->GetVideoAspect() : (4.0F / 3.0F);
840  int tmph = (m_displayVisibleRect.height() * m_dbPipSize) / 100;
841  float pixel_adj = 1.0F;
842  if (DoPixelAdjustment)
843  {
844  pixel_adj = (static_cast<float>(m_displayVisibleRect.width()) /
845  static_cast<float>(m_displayVisibleRect.height())) / m_displayAspect;
846  }
847  position.setHeight(tmph);
848  position.setWidth(qRound((tmph * pipVideoAspect * pixel_adj)));
849 
850  int xoff = qRound(m_displayVisibleRect.width() * 0.06);
851  int yoff = qRound(m_displayVisibleRect.height() * 0.06);
852  switch (Location)
853  {
854  case kPIP_END:
855  case kPIPTopLeft:
856  break;
857  case kPIPBottomLeft:
858  yoff = m_displayVisibleRect.height() - position.height() - yoff;
859  break;
860  case kPIPTopRight:
861  xoff = m_displayVisibleRect.width() - position.width() - xoff;
862  break;
863  case kPIPBottomRight:
864  xoff = m_displayVisibleRect.width() - position.width() - xoff;
865  yoff = m_displayVisibleRect.height() - position.height() - yoff;
866  break;
867  }
868  position.translate(xoff, yoff);
869  return position;
870 }
871 
877 {
878  float oldvertscale = m_manualVertScale;
879  float oldhorizscale = m_manualHorizScale;
880  QPoint oldmove = m_manualMove;
881 
882  const float zf = 0.02F;
883  if (kZoomHome == Direction)
884  {
885  m_manualVertScale = 1.0F;
886  m_manualHorizScale = 1.0F;
887  m_manualMove = QPoint(0, 0);
888  }
889  else if (kZoomIn == Direction)
890  {
893  {
894  m_manualHorizScale += zf;
895  m_manualVertScale += zf;
896  }
897  }
898  else if (kZoomOut == Direction)
899  {
902  {
903  m_manualHorizScale -= zf;
904  m_manualVertScale -= zf;
905  }
906  }
907  else if (kZoomAspectUp == Direction)
908  {
911  {
912  m_manualHorizScale += zf;
913  m_manualVertScale -= zf;
914  }
915  }
916  else if (kZoomAspectDown == Direction)
917  {
920  {
921  m_manualHorizScale -= zf;
922  m_manualVertScale += zf;
923  }
924  }
925  else if (kZoomVerticalIn == Direction)
926  {
928  m_manualVertScale += zf;
929  }
930  else if (kZoomVerticalOut == Direction)
931  {
933  m_manualVertScale -= zf;
934  }
935  else if (kZoomHorizontalIn == Direction)
936  {
938  m_manualHorizScale += zf;
939  }
940  else if (kZoomHorizontalOut == Direction)
941  {
943  m_manualHorizScale -= zf;
944  }
945  else if (kZoomUp == Direction && (m_manualMove.y() < +kManualZoomMaxMove))
946  m_manualMove.setY(m_manualMove.y() + 1);
947  else if (kZoomDown == Direction && (m_manualMove.y() > -kManualZoomMaxMove))
948  m_manualMove.setY(m_manualMove.y() - 1);
949  else if (kZoomLeft == Direction && (m_manualMove.x() < +kManualZoomMaxMove))
950  m_manualMove.setX(m_manualMove.x() + 2);
951  else if (kZoomRight == Direction && (m_manualMove.x() > -kManualZoomMaxMove))
952  m_manualMove.setX(m_manualMove.x() - 2);
953 
954  m_manualVertScale = snap(m_manualVertScale, 1.0F, zf / 2);
955  m_manualHorizScale = snap(m_manualHorizScale, 1.0F, zf / 2);
956 
957  if (!((oldmove == m_manualMove) && qFuzzyCompare(m_manualVertScale + 100.0F, oldvertscale + 100.0F) &&
958  qFuzzyCompare(m_manualHorizScale + 100.0F, oldhorizscale + 100.0F)))
959  {
960  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New zoom: Offset %1x%2 HScale %3 VScale %4")
961  .arg(m_manualMove.x()).arg(m_manualMove.y())
962  .arg(static_cast<double>(m_manualHorizScale))
963  .arg(static_cast<double>(m_manualVertScale)));
964  MoveResize();
965  }
966 }
967 
969 {
970  float oldvertscale = m_manualVertScale;
971  float oldhorizscale = m_manualHorizScale;
972  QPoint oldmove = m_manualMove;
973 
974  if (m_bottomLine)
975  {
976  m_manualMove.setX(0);
977  m_manualMove.setY(0);
978  m_manualHorizScale = 1.0;
979  m_manualVertScale = 1.0;
980  m_bottomLine = false;
981  }
982  else
983  {
984  const float zf = 0.02F;
985  m_manualMove.setX(gCoreContext->GetNumSetting("OSDMoveXBottomLine", 0));
986  m_manualMove.setY(gCoreContext->GetNumSetting("OSDMoveYBottomLine", 5));
987  float h = static_cast<float>(gCoreContext->GetNumSetting("OSDScaleHBottomLine", 100)) / 100.0F;
988  m_manualHorizScale = snap(h, 1.0F, zf / 2.0F);
989  float v = static_cast<float>(gCoreContext->GetNumSetting("OSDScaleVBottomLine", 112)) / 100.0F;
990  m_manualVertScale = snap(v, 1.0F, zf / 2.0F);
991  m_bottomLine = true;
992  }
993 
994  if (!((oldmove == m_manualMove) && qFuzzyCompare(m_manualVertScale + 100.0F, oldvertscale + 100.0F) &&
995  qFuzzyCompare(m_manualHorizScale + 100.0F, oldhorizscale + 100.0F)))
996  {
997  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("New custom zoom: Offset %1x%2 HScale %3 VScale %4")
998  .arg(m_manualMove.x()).arg(m_manualMove.y())
999  .arg(static_cast<double>(m_manualHorizScale))
1000  .arg(static_cast<double>(m_manualVertScale)));
1001  MoveResize();
1002  }
1003 }
1004 
1006 {
1007  gCoreContext->SaveSetting("OSDMoveXBottomLine", m_manualMove.x());
1008  gCoreContext->SaveSetting("OSDMoveYBottomLine", m_manualMove.y());
1009  gCoreContext->SaveSetting("OSDScaleHBottomLine", static_cast<int>(m_manualHorizScale * 100.0F));
1010  gCoreContext->SaveSetting("OSDScaleVBottomLine", static_cast<int>(m_manualVertScale * 100.0F));
1011 }
1012 
1014 {
1015  return tr("Zoom %1x%2 @ (%3,%4)")
1016  .arg(static_cast<double>(m_manualHorizScale), 0, 'f', 2)
1017  .arg(static_cast<double>(m_manualVertScale), 0, 'f', 2)
1018  .arg(m_manualMove.x()).arg(m_manualMove.y());
1019 }
1020 
1022 static float fix_aspect(float raw)
1023 {
1024  // Check if close to 4:3
1025  if (fabs(raw - 1.333333F) < 0.05F)
1026  raw = 1.333333F;
1027 
1028  // Check if close to 16:9
1029  if (fabs(raw - 1.777777F) < 0.05F)
1030  raw = 1.777777F;
1031 
1032  return raw;
1033 }
1034 
1036 {
1037  if (m_pipState != Setting)
1038  {
1039  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("SetPIPState: %1").arg(toString(Setting)));
1040  m_pipState = Setting;
1041  }
1042 }
1043 
1044 static float snap(float value, float snapto, float diff)
1045 {
1046  if ((value + diff > snapto) && (value - diff < snapto))
1047  return snapto;
1048  return value;
1049 }
void ToggleAspectOverride(AspectOverrideMode AspectMode=kAspect_Toggle)
Enforce different aspect ration than detected, then calls VideoAspectRatioChanged(float) to apply the...
QRegion GetBoundingRegion(void) const
Return the region of DisplayVisibleRect that lies outside of DisplayVideoRect.
float GetVideoAspect(void) const
Definition: mythplayer.h:210
QRect m_displayVideoRect
Pixel rectangle in display window into which video_rect maps to.
static float fix_aspect(float raw)
Correct for rounding errors.
void PopulateGeometry(void)
QRect GetTotalOSDBounds(void) const
Returns total OSD bounds.
bool Init(const QSize &VideoDim, const QSize &VideoDispDim, float Aspect, const QRect &WindowRect, AspectOverrideMode AspectOverride, AdjustFillMode AdjustFill, MythDisplay *Display)
QString toString(MarkTypes type)
void VideoRectsChanged(const QRect &DisplayVideoRect, const QRect &VideoRect)
void Zoom(ZoomDirection Direction)
Sets up zooming into to different parts of the video.
void SaveSetting(const QString &key, int newValue)
bool m_dbScalingAllowed
disable this to prevent overscan/underscan
float sq(float a)
Definition: mythmiscutil.h:57
QRect m_tmpDisplayVisibleRect
Used to save the display_visible_rect for restoration after video embedding ends.
void ResizeDisplayWindow(const QRect &Rect, bool SaveVisibleRect)
Resize Display Window.
void Rotate(void)
Adjust various settings to facilitate portrait mode calculations.
bool m_dbUseGUISize
Use the gui size for video window.
float m_videoAspect
Physical aspect ratio of video.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
float get_aspect_override(AspectOverrideMode Aspectmode, float Original)
PIPState m_pipState
float m_videoAspectOverride
Normally this is the same as videoAspect, but may not be if the user has toggled the aspect override ...
QRect GetPIPRect(PIPLocation Location, MythPlayer *PiPPlayer=nullptr, bool DoPixelAdjustment=true) const
void SetVideoAspectRatio(float Aspect)
Sets VideoOutWindow::video_aspect to aspect, and sets VideoOutWindow::overriden_video_aspect if aspec...
static const float kManualZoomMinVerticalZoom
QScreen * GetCurrentScreen(void)
Return a pointer to the screen to use.
static guint32 * tmp
Definition: goom_core.c:35
float GetDisplayAspect(void) const
QRect m_windowRect
Rectangle describing QWidget bounds.
void WindowRectChanged(const QRect &WindowRect)
static QSize Fix1088(QSize Dimensions)
QRect m_embeddingRect
Embedded video rectangle.
void EmbedInWidget(const QRect &Rect)
Tells video output to embed video in an existing window.
double GetAspectRatio(QString &Source, bool IgnoreModeOverride=false)
Returns current screen aspect ratio.
AspectOverrideMode
Definition: videoouttypes.h:44
QRect m_displayVisibleRect
Visible portion of display window in pixels.
QSize m_videoDim
Pixel dimensions of video buffer.
QRect GetVisibleOSDBounds(float &VisibleAspect, float &FontScaling, float ThemeAspect) const
Returns visible portions of total OSD bounds.
float m_dbVertScale
Vertical Overscan/Underscan percentage.
void SaveBottomLine(void)
QRect m_itvDisplayVideoRect
void CurrentScreenChanged(QScreen *qScreen)
float m_manualVertScale
Manually applied vertical scaling.
void VideoAspectRatioChanged(float Aspect)
Calls SetVideoAspectRatio(float aspect), then calls MoveResize() to apply changes.
void SetWindowSize(QSize Size)
void VisibleRectChanged(const QRect &DisplayVisibleRect)
float m_dbHorizScale
Horizontal Overscan/Underscan percentage.
MythDisplay * m_display
bool IsEmbedding(void) const
static const int kManualZoomMaxMove
static HostComboBoxSetting * AspectOverride()
void SetPIPState(PIPState Setting)
static float snap(float value, float snapto, float diff)
void ScreenChanged(QScreen *screen)
void SetDisplayAspect(float DisplayAspect)
bool VideoIsFullScreen(void) const
Check whether the video display rect covers the entire window/framebuffer.
void PrintMoveResizeDebug(void)
PIPLocation
Definition: videoouttypes.h:17
AdjustFillMode
Definition: videoouttypes.h:55
QRect m_videoRect
Pixel rectangle in video frame to display.
int GetNumSetting(const QString &key, int defaultval=0)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QPoint m_dbMove
Percentage move from database.
AspectOverrideMode m_videoAspectOverrideMode
AspectOverrideMode to use to modify overriden_video_aspect.
bool GetBoolSetting(const QString &key, bool defaultval=false)
QSize m_videoDispDim
Pixel dimensions of video display area.
PIPState
Definition: videoouttypes.h:8
static int GetScreenCount(void)
bool m_embedding
State variables.
void ToggleMoveBottomLine(void)
static const float kManualZoomMinHorizontalZoom
static const float kManualZoomMaxVerticalZoom
void MoveResize(void)
performs all the calculations for video framing and any resizing.
void VideoSizeChanged(const QSize &VideoDim, const QSize &VideoDispDim)
void ToggleAdjustFill(AdjustFillMode AdjustFillMode=kAdjustFill_Toggle)
Sets up letterboxing for various standard video frame and monitor dimensions, then calls MoveResize()...
void ApplyLetterboxing(void)
static const float kManualZoomMaxHorizontalZoom
QString GetZoomString(void) const
int m_dbPipSize
percentage of full window to use for PiP
void InputChanged(const QSize &VideoDim, const QSize &VideoDispDim, float Aspect)
Tells video output to discard decoded frames and wait for new ones.
QRect m_screenGeometry
Full screen geometry.
#define LOC
AdjustFillMode m_adjustFill
Zoom mode.
float m_displayAspect
Physical aspect ratio of playback window.
ZoomDirection
Definition: videoouttypes.h:26
void SetRotation(int Rotation)
Set the rotation in degrees.
void StopEmbedding(void)
Tells video output to stop embedding video in an existing window.
static bool SpanAllScreens(void)
Return true if the MythTV windows should span all screens.
void ApplyManualScaleAndMove(void)
Apply scales and moves from "Zoom Mode" settings.
static HostComboBoxSetting * AdjustFill()
void ApplyDBScaleAndMove(void)
Apply scales and moves for "Overscan" and "Underscan" DB settings.
QPoint m_manualMove
Manually applied percentage move.
void SetITVResize(QRect Rect)
float m_manualHorizScale
Manually applied horizontal scaling.
void SetVideoScalingAllowed(bool Change)
Disable or enable underscan/overscan.
QMap< QString, bool > Setting