From 8618a0f10820562793417c0a546f91ea02194f75 Mon Sep 17 00:00:00 2001
From: Lawrence Rust <lvr@softsystem.co.uk>
Date: Sun, 2 Oct 2011 14:36:50 +0200
Subject: [PATCH] freemheg: Support bitmap backgrounds used by BBC/Freesat
Add support for bitmaps with a content hook type of 5 which are
transmitted by the BBC on FreesSat & FreeView.
Type 5 bitmaps are identical to type 2, an MPEG I-frame, but are intended
to be used as a background onto which the MHEG content and video is rendered.
Signed-off-by: Lawrence Rust <lvr@softsystem.co.uk>
---
mythtv/libs/libmythfreemheg/Bitmap.cpp | 26 ++++----
mythtv/libs/libmythfreemheg/freemheg.h | 2 +-
mythtv/libs/libmythtv/mhi.cpp | 113 ++++++++++++++++++++-----------
mythtv/libs/libmythtv/mhi.h | 9 ++-
4 files changed, 91 insertions(+), 59 deletions(-)
diff --git a/mythtv/libs/libmythfreemheg/Bitmap.cpp b/mythtv/libs/libmythfreemheg/Bitmap.cpp
index 42c9df1..1c4ffae 100644
a
|
b
|
void MHBitmap::ContentArrived(const unsigned char *data, int length, MHEngine *e
|
150 | 150 | nCHook = engine->GetDefaultBitmapCHook(); |
151 | 151 | } |
152 | 152 | |
153 | | // TODO: What if we can't convert it? |
154 | | if (nCHook == 4) // PNG. |
| 153 | switch (nCHook) |
155 | 154 | { |
| 155 | case 4: // PNG. |
156 | 156 | m_pContent->CreateFromPNG(data, length); |
157 | | } |
158 | | // CHook 5 seems to be used by the BBC on Freesat for an MPEG I-frame for the |
159 | | // background but enabling it here results in it overlaying the video. |
160 | | // Presumably it is not simply the same as CHook 2. |
161 | | else if (nCHook == 2 /* ||nCHook == 5 */) // MPEG I-frame. |
162 | | { |
| 157 | break; |
| 158 | case 2: // MPEG I-frame. |
| 159 | case 5: // BBC/Freesat MPEG I-frame background |
163 | 160 | m_pContent->CreateFromMPEG(data, length); |
164 | | } |
165 | | |
166 | | else |
167 | | { |
| 161 | break; |
| 162 | case 6: // JPEG ISO/IEC 10918-1, JFIF file |
| 163 | default: // 1,3,5,8 are reserved. 7= H.264 Intra Frame |
168 | 164 | MHERROR(QString("Unknown bitmap content hook %1").arg(nCHook)); |
169 | 165 | } |
170 | 166 | |
… |
… |
void MHBitmap::Display(MHEngine *)
|
229 | 225 | } |
230 | 226 | |
231 | 227 | m_pContent->Draw(m_nPosX + m_nXDecodeOffset, m_nPosY + m_nYDecodeOffset, |
232 | | QRect(m_nPosX, m_nPosY, m_nBoxWidth, m_nBoxHeight), m_fTiling); |
| 228 | QRect(m_nPosX, m_nPosY, m_nBoxWidth, m_nBoxHeight), m_fTiling, |
| 229 | m_nContentHook == 5); // 'under video' if BBC MPEG I-frame background |
233 | 230 | } |
234 | 231 | |
235 | 232 | // Return the region drawn by the bitmap. |
… |
… |
QRegion MHBitmap::GetVisibleArea()
|
253 | 250 | QRegion MHBitmap::GetOpaqueArea() |
254 | 251 | { |
255 | 252 | // The area is empty unless the bitmap is opaque. |
256 | | if (! m_fRunning || m_pContent == NULL || ! m_pContent->IsOpaque()) |
| 253 | // and it's not a BBC MPEG I-frame background |
| 254 | if (! m_fRunning || m_nContentHook == 5 || m_pContent == NULL || ! m_pContent->IsOpaque()) |
257 | 255 | { |
258 | 256 | return QRegion(); |
259 | 257 | } |
diff --git a/mythtv/libs/libmythfreemheg/freemheg.h b/mythtv/libs/libmythfreemheg/freemheg.h
index 0b61f65..42ad883 100644
a
|
b
|
class MHBitmapDisplay
|
187 | 187 | // Draw the completed drawing onto the display. x and y give the position of the image |
188 | 188 | // relative to the screen. rect gives the bounding box for the image, again relative to |
189 | 189 | // the screen. |
190 | | virtual void Draw(int x, int y, QRect rect, bool tiled) = 0; |
| 190 | virtual void Draw(int x, int y, QRect rect, bool tiled, bool bUnder) = 0; |
191 | 191 | // Creation functions |
192 | 192 | virtual void CreateFromPNG(const unsigned char *data, int length) = 0; |
193 | 193 | virtual void CreateFromMPEG(const unsigned char *data, int length) = 0; |
diff --git a/mythtv/libs/libmythtv/mhi.cpp b/mythtv/libs/libmythtv/mhi.cpp
index 1631495..6694efc 100644
a
|
b
|
class MHIImageData
|
44 | 44 | QImage m_image; |
45 | 45 | int m_x; |
46 | 46 | int m_y; |
| 47 | bool m_bUnder; |
47 | 48 | }; |
48 | 49 | |
49 | 50 | // Special value for the NetworkBootInfo version. Real values are a byte. |
… |
… |
MHIContext::MHIContext(InteractiveTV *parent)
|
60 | 61 | m_audioTag(-1), m_videoTag(-1), |
61 | 62 | m_lastNbiVersion(NBI_VERSION_UNSET), |
62 | 63 | m_videoRect(0, 0, StdDisplayWidth, StdDisplayHeight), |
| 64 | m_videoDisplayRect(0, 0, StdDisplayWidth, StdDisplayHeight), |
63 | 65 | m_displayRect(0, 0, StdDisplayWidth, StdDisplayHeight) |
64 | 66 | { |
65 | 67 | m_xScale = (float)m_displayWidth / (float)MHIContext::StdDisplayWidth; |
… |
… |
void MHIContext::UpdateOSD(InteractiveScreen *osdWindow,
|
505 | 507 | return; |
506 | 508 | |
507 | 509 | QMutexLocker locker(&m_display_lock); |
| 510 | |
| 511 | // In MHEG the video is just another item in the display stack |
| 512 | // but when we create the OSD we overlay everything over the video. |
| 513 | // We need to cut out anything belowthe video on the display stack |
| 514 | // to leave the video area clear. |
| 515 | list<MHIImageData*>::iterator it = m_display.begin(); |
| 516 | for (; it != m_display.end(); ++it) |
| 517 | { |
| 518 | MHIImageData *data = *it; |
| 519 | if (!data->m_bUnder) |
| 520 | continue; |
| 521 | |
| 522 | QRect imageRect(data->m_x, data->m_y, |
| 523 | data->m_image.width(), data->m_image.height()); |
| 524 | if (!m_videoDisplayRect.intersects(imageRect)) |
| 525 | continue; |
| 526 | |
| 527 | // Replace this item with a set of cut-outs. |
| 528 | it = m_display.erase(it); |
| 529 | |
| 530 | QVector<QRect> rects = |
| 531 | (QRegion(imageRect) - QRegion(m_videoDisplayRect)).rects(); |
| 532 | for (uint j = 0; j < (uint)rects.size(); j++) |
| 533 | { |
| 534 | QRect &rect = rects[j]; |
| 535 | QImage image = |
| 536 | data->m_image.copy(rect.x()-data->m_x, rect.y()-data->m_y, |
| 537 | rect.width(), rect.height()); |
| 538 | MHIImageData *newData = new MHIImageData; |
| 539 | newData->m_image = image; |
| 540 | newData->m_x = rect.x(); |
| 541 | newData->m_y = rect.y(); |
| 542 | newData->m_bUnder = true; |
| 543 | m_display.insert(it, newData); |
| 544 | ++it; |
| 545 | } |
| 546 | delete data; |
| 547 | } |
| 548 | |
508 | 549 | m_updated = false; |
509 | 550 | osdWindow->DeleteAllChildren(); |
510 | 551 | // Copy all the display items into the display. |
511 | | list<MHIImageData*>::iterator it = m_display.begin(); |
| 552 | it = m_display.begin(); |
512 | 553 | for (int count = 0; it != m_display.end(); ++it, count++) |
513 | 554 | { |
514 | 555 | MHIImageData *data = *it; |
… |
… |
void MHIContext::RequireRedraw(const QRegion &)
|
551 | 592 | m_updated = true; |
552 | 593 | } |
553 | 594 | |
554 | | void MHIContext::AddToDisplay(const QImage &image, int x, int y) |
| 595 | void MHIContext::AddToDisplay(const QImage &image, int x, int y, bool bUnder /*=false*/) |
555 | 596 | { |
556 | 597 | MHIImageData *data = new MHIImageData; |
557 | 598 | int dispx = x + m_displayRect.left(); |
… |
… |
void MHIContext::AddToDisplay(const QImage &image, int x, int y)
|
560 | 601 | data->m_image = image; |
561 | 602 | data->m_x = dispx; |
562 | 603 | data->m_y = dispy; |
| 604 | data->m_bUnder = bUnder; |
563 | 605 | QMutexLocker locker(&m_display_lock); |
564 | | m_display.push_back(data); |
| 606 | if (!bUnder) |
| 607 | m_display.push_back(data); |
| 608 | else |
| 609 | { |
| 610 | // Replace any existing items under the video with this |
| 611 | list<MHIImageData*>::iterator it = m_display.begin(); |
| 612 | while (it != m_display.end()) |
| 613 | { |
| 614 | MHIImageData *old = *it; |
| 615 | if (!old->m_bUnder) |
| 616 | ++it; |
| 617 | else |
| 618 | { |
| 619 | it = m_display.erase(it); |
| 620 | delete old; |
| 621 | } |
| 622 | } |
| 623 | m_display.push_front(data); |
| 624 | } |
565 | 625 | } |
566 | 626 | |
567 | | // In MHEG the video is just another item in the display stack |
568 | | // but when we create the OSD we overlay everything over the video. |
569 | | // We need to cut out anything belowthe video on the display stack |
570 | | // to leave the video area clear. |
571 | 627 | // The videoRect gives the size and position to which the video must be scaled. |
572 | 628 | // The displayRect gives the rectangle reserved for the video. |
573 | 629 | // e.g. part of the video may be clipped within the displayRect. |
… |
… |
void MHIContext::DrawVideo(const QRect &videoRect, const QRect &dispRect)
|
587 | 643 | } |
588 | 644 | } |
589 | 645 | |
590 | | QMutexLocker locker(&m_display_lock); |
591 | | QRect displayRect(SCALED_X(dispRect.x()), |
| 646 | m_videoDisplayRect = QRect(SCALED_X(dispRect.x()), |
592 | 647 | SCALED_Y(dispRect.y()), |
593 | 648 | SCALED_X(dispRect.width()), |
594 | 649 | SCALED_Y(dispRect.height())); |
595 | 650 | |
| 651 | // Mark all existing items in the display stack as under the video |
| 652 | QMutexLocker locker(&m_display_lock); |
596 | 653 | list<MHIImageData*>::iterator it = m_display.begin(); |
597 | 654 | for (; it != m_display.end(); ++it) |
598 | 655 | { |
599 | | MHIImageData *data = *it; |
600 | | QRect imageRect(data->m_x, data->m_y, |
601 | | data->m_image.width(), data->m_image.height()); |
602 | | if (displayRect.intersects(imageRect)) |
603 | | { |
604 | | // Replace this item with a set of cut-outs. |
605 | | it = m_display.erase(it); |
606 | | |
607 | | QVector<QRect> rects = |
608 | | (QRegion(imageRect) - QRegion(displayRect)).rects(); |
609 | | for (uint j = 0; j < (uint)rects.size(); j++) |
610 | | { |
611 | | QRect &rect = rects[j]; |
612 | | QImage image = |
613 | | data->m_image.copy(rect.x()-data->m_x, rect.y()-data->m_y, |
614 | | rect.width(), rect.height()); |
615 | | MHIImageData *newData = new MHIImageData; |
616 | | newData->m_image = image; |
617 | | newData->m_x = rect.x(); |
618 | | newData->m_y = rect.y(); |
619 | | m_display.insert(it, newData); |
620 | | ++it; |
621 | | } |
622 | | delete data; |
623 | | } |
| 656 | (*it)->m_bUnder = true; |
624 | 657 | } |
625 | 658 | } |
626 | 659 | |
… |
… |
void MHIContext::DrawRect(int xPos, int yPos, int width, int height,
|
866 | 899 | // and usually that will be the same as the origin of the bounding |
867 | 900 | // box (clipRect). |
868 | 901 | void MHIContext::DrawImage(int x, int y, const QRect &clipRect, |
869 | | const QImage &qImage) |
| 902 | const QImage &qImage, bool bUnder) |
870 | 903 | { |
871 | 904 | if (qImage.isNull()) |
872 | 905 | return; |
… |
… |
void MHIContext::DrawImage(int x, int y, const QRect &clipRect,
|
884 | 917 | Qt::IgnoreAspectRatio, |
885 | 918 | Qt::SmoothTransformation); |
886 | 919 | AddToDisplay(q_scaled.convertToFormat(QImage::Format_ARGB32), |
887 | | SCALED_X(x), SCALED_Y(y)); |
| 920 | SCALED_X(x), SCALED_Y(y), bUnder); |
888 | 921 | } |
889 | 922 | else if (!displayRect.isEmpty()) |
890 | 923 | { // We must clip the image. |
… |
… |
void MHIContext::DrawImage(int x, int y, const QRect &clipRect,
|
899 | 932 | Qt::SmoothTransformation); |
900 | 933 | AddToDisplay(q_scaled, |
901 | 934 | SCALED_X(displayRect.x()), |
902 | | SCALED_Y(displayRect.y())); |
| 935 | SCALED_Y(displayRect.y()), bUnder); |
903 | 936 | } |
904 | 937 | // Otherwise draw nothing. |
905 | 938 | } |
… |
… |
void MHIDLA::DrawPoly(bool isFilled, int nPoints, const int *xArray, const int *
|
1452 | 1485 | } |
1453 | 1486 | |
1454 | 1487 | |
1455 | | void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled) |
| 1488 | void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled, bool bUnder) |
1456 | 1489 | { |
1457 | 1490 | if (tiled) |
1458 | 1491 | { |
… |
… |
void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled)
|
1470 | 1503 | tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height())); |
1471 | 1504 | } |
1472 | 1505 | } |
1473 | | m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage); |
| 1506 | m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage, bUnder); |
1474 | 1507 | } |
1475 | 1508 | else |
1476 | 1509 | { |
1477 | | m_parent->DrawImage(x, y, rect, m_image); |
| 1510 | m_parent->DrawImage(x, y, rect, m_image, bUnder); |
1478 | 1511 | } |
1479 | 1512 | } |
1480 | 1513 | |
diff --git a/mythtv/libs/libmythtv/mhi.h b/mythtv/libs/libmythtv/mhi.h
index 2b10c8b..c7339fe 100644
a
|
b
|
class MHIContext : public MHContext, public QRunnable
|
99 | 99 | virtual void DrawBackground(const QRegion ®); |
100 | 100 | virtual void DrawVideo(const QRect &videoRect, const QRect &displayRect); |
101 | 101 | |
102 | | void DrawImage(int x, int y, const QRect &rect, const QImage &image); |
| 102 | void DrawImage(int x, int y, const QRect &rect, const QImage &image, |
| 103 | bool bUnder = false); |
103 | 104 | |
104 | 105 | virtual int GetChannelIndex(const QString &str); |
105 | 106 | /// Get netId etc from the channel index. |
… |
… |
class MHIContext : public MHContext, public QRunnable
|
125 | 126 | |
126 | 127 | // Operations used by the display classes |
127 | 128 | // Add an item to the display vector |
128 | | void AddToDisplay(const QImage &image, int x, int y); |
| 129 | void AddToDisplay(const QImage &image, int x, int y, bool bUnder = false); |
129 | 130 | |
130 | 131 | FT_Face GetFontFace(void) { return m_face; } |
131 | 132 | bool IsFaceLoaded(void) { return m_face_loaded; } |
… |
… |
class MHIContext : public MHContext, public QRunnable
|
185 | 186 | uint m_lastNbiVersion; |
186 | 187 | vector<unsigned char> m_nbiData; |
187 | 188 | |
188 | | QRect m_videoRect; |
| 189 | QRect m_videoRect, m_videoDisplayRect; |
189 | 190 | QRect m_displayRect; |
190 | 191 | }; |
191 | 192 | |
… |
… |
class MHIBitmap : public MHBitmapDisplay
|
238 | 239 | * \param y Vertical position of the image relative to the screen. |
239 | 240 | * \param rect Bounding box for the image relative to the screen. |
240 | 241 | */ |
241 | | virtual void Draw(int x, int y, QRect rect, bool tiled); |
| 242 | virtual void Draw(int x, int y, QRect rect, bool tiled, bool bUnder); |
242 | 243 | |
243 | 244 | /// Scale the bitmap. Only used for image derived from MPEG I-frames. |
244 | 245 | virtual void ScaleImage(int newWidth, int newHeight); |