MythTV  0.26-pre
mhi.cpp
Go to the documentation of this file.
00001 #include <unistd.h>
00002 
00003 #include <QRegion>
00004 #include <QBitArray>
00005 #include <QVector>
00006 
00007 #include "mhi.h"
00008 #include "interactivescreen.h"
00009 #include "mythpainter.h"
00010 #include "mythimage.h"
00011 #include "mythuiimage.h"
00012 #include "osd.h"
00013 #include "mythdirs.h"
00014 #include "myth_imgconvert.h"
00015 #include "mythlogging.h"
00016 
00017 static bool       ft_loaded = false;
00018 static FT_Library ft_library;
00019 
00020 #define FONT_WIDTHRES   48
00021 #define FONT_HEIGHTRES  72
00022 #define FONT_TO_USE "FreeSans.ttf"
00023 
00024 #define SCALED_X(arg1) (int)(((float)arg1 * m_xScale) + 0.5f)
00025 #define SCALED_Y(arg1) (int)(((float)arg1 * m_yScale) + 0.5f)
00026 
00027 // LifecycleExtension tuneinfo:
00028 const unsigned kTuneQuietly   = 1U<<0; // b0 tune quietly
00029 const unsigned kTuneKeepApp   = 1U<<1; // b1 keep app running
00030 const unsigned kTuneCarId     = 1U<<2; // b2 carousel id in bits 8..16
00031 const unsigned kTuneCarReset  = 1U<<3; // b3 get carousel id from gateway info
00032 const unsigned kTuneBcastDisa = 1U<<4; // b4 broadcaster_interrupt disable
00033 // b5..7 reserverd
00034 // b8..15 carousel id
00035 // b16..31 reserved
00036 const unsigned kTuneKeepChnl  = 1U<<16; // Keep current channel
00037 
00041 class MHIImageData
00042 {
00043   public:
00044     QImage m_image;
00045     int    m_x;
00046     int    m_y;
00047 };
00048 
00049 // Special value for the NetworkBootInfo version.  Real values are a byte.
00050 #define NBI_VERSION_UNSET       257
00051 
00052 MHIContext::MHIContext(InteractiveTV *parent)
00053     : m_parent(parent),     m_dsmcc(NULL),
00054       m_keyProfile(0),
00055       m_engine(NULL),       m_stop(false),
00056       m_updated(false),
00057       m_displayWidth(StdDisplayWidth), m_displayHeight(StdDisplayHeight),
00058       m_face_loaded(false), m_engineThread(NULL), m_currentChannel(-1),
00059       m_currentStream(-1),  m_isLive(false),      m_currentSource(-1),
00060       m_audioTag(-1),       m_videoTag(-1),
00061       m_lastNbiVersion(NBI_VERSION_UNSET),
00062       m_videoRect(0, 0, StdDisplayWidth, StdDisplayHeight),
00063       m_displayRect(0, 0, StdDisplayWidth, StdDisplayHeight)
00064 {
00065     m_xScale = (float)m_displayWidth / (float)MHIContext::StdDisplayWidth;
00066     m_yScale = (float)m_displayHeight / (float)MHIContext::StdDisplayHeight;
00067 
00068     if (!ft_loaded)
00069     {
00070         FT_Error error = FT_Init_FreeType(&ft_library);
00071         if (!error)
00072             ft_loaded = true;
00073     }
00074 
00075     if (ft_loaded)
00076     {
00077         // TODO: We need bold and italic versions.
00078         if (LoadFont(FONT_TO_USE))
00079             m_face_loaded = true;
00080     }
00081 }
00082 
00083 // Load the font.  Copied, generally, from OSD::LoadFont.
00084 bool MHIContext::LoadFont(QString name)
00085 {
00086     QString fullnameA = GetConfDir() + "/" + name;
00087     QByteArray fnameA = fullnameA.toAscii();
00088     FT_Error errorA = FT_New_Face(ft_library, fnameA.constData(), 0, &m_face);
00089     if (!errorA)
00090         return true;
00091 
00092     QString fullnameB = GetFontsDir() + name;
00093     QByteArray fnameB = fullnameB.toAscii();
00094     FT_Error errorB = FT_New_Face(ft_library, fnameB.constData(), 0, &m_face);
00095     if (!errorB)
00096         return true;
00097 
00098     QString fullnameC = GetShareDir() + "themes/" + name;
00099     QByteArray fnameC = fullnameC.toAscii();
00100     FT_Error errorC = FT_New_Face(ft_library, fnameC.constData(), 0, &m_face);
00101     if (!errorC)
00102         return true;
00103 
00104     QString fullnameD = name;
00105     QByteArray fnameD = fullnameD.toAscii();
00106     FT_Error errorD = FT_New_Face(ft_library, fnameD.constData(), 0, &m_face);
00107     if (!errorD)
00108         return true;
00109 
00110     LOG(VB_GENERAL, LOG_ERR, QString("Unable to find font: %1").arg(name));
00111     return false;
00112 }
00113 
00114 MHIContext::~MHIContext()
00115 {
00116     StopEngine();
00117     delete(m_engine);
00118     delete(m_dsmcc);
00119     if (m_face_loaded) FT_Done_Face(m_face);
00120 
00121     ClearDisplay();
00122     ClearQueue();
00123 }
00124 
00125 void MHIContext::ClearDisplay(void)
00126 {
00127     list<MHIImageData*>::iterator it = m_display.begin();
00128     for (; it != m_display.end(); ++it)
00129         delete *it;
00130     m_display.clear();
00131 }
00132 
00133 void MHIContext::ClearQueue(void)
00134 {
00135     MythDeque<DSMCCPacket*>::iterator it = m_dsmccQueue.begin();
00136     for (; it != m_dsmccQueue.end(); ++it)
00137         delete *it;
00138     m_dsmccQueue.clear();
00139 }
00140 
00141 // Ask the engine to stop and block until it has.
00142 void MHIContext::StopEngine(void)
00143 {
00144     if (NULL == m_engineThread)
00145         return;
00146 
00147     m_runLock.lock();
00148     m_stop = true;
00149     m_engine_wait.wakeAll();
00150     m_runLock.unlock();
00151 
00152     m_engineThread->wait();
00153     delete m_engineThread;
00154     m_engineThread = NULL;
00155 }
00156 
00157 
00158 // Start or restart the MHEG engine.
00159 void MHIContext::Restart(int chanid, int sourceid, bool isLive)
00160 {
00161     int tuneinfo = m_tuneinfo.isEmpty() ? 0 : m_tuneinfo.takeFirst();
00162 
00163     LOG(VB_MHEG, LOG_INFO,
00164         QString("[mhi] Restart ch=%1 source=%2 live=%3 tuneinfo=0x%4")
00165         .arg(chanid).arg(sourceid).arg(isLive).arg(tuneinfo,0,16));
00166 
00167     m_currentSource = sourceid;
00168     m_currentStream = chanid ? chanid : -1;
00169     if (!(tuneinfo & kTuneKeepChnl))
00170         m_currentChannel = m_currentStream;
00171 
00172     if (tuneinfo & kTuneKeepApp)
00173     {
00174         // We have tuned to the channel in order to find the streams.
00175         // Leave the MHEG engine running but restart the DSMCC carousel.
00176         // This is a bit of a mess but it's the only way to be able to
00177         // select streams from a different channel.
00178         if (!m_dsmcc)
00179             m_dsmcc = new Dsmcc();
00180         {
00181             QMutexLocker locker(&m_dsmccLock);
00182             if (tuneinfo & kTuneCarReset)
00183                 m_dsmcc->Reset();
00184             ClearQueue();
00185         }
00186 
00187         if (tuneinfo & (kTuneCarReset|kTuneCarId))
00188             m_engine->EngineEvent(10); // NonDestructiveTuneOK
00189     }
00190     else
00191     {
00192         StopEngine();
00193 
00194         m_audioTag = -1;
00195         m_videoTag = -1;
00196 
00197         if (!m_dsmcc)
00198             m_dsmcc = new Dsmcc();
00199 
00200         {
00201             QMutexLocker locker(&m_dsmccLock);
00202             m_dsmcc->Reset();
00203             ClearQueue();
00204         }
00205 
00206         {
00207             QMutexLocker locker(&m_keyLock);
00208             m_keyQueue.clear();
00209         }
00210 
00211         if (!m_engine)
00212             m_engine = MHCreateEngine(this);
00213 
00214         m_engine->SetBooting();
00215         ClearDisplay();
00216         m_updated = true;
00217         m_stop = false;
00218         m_isLive = isLive;
00219         // Don't set the NBI version here.  Restart is called
00220         // after the PMT is processed.
00221         m_engineThread = new MThread("MHEG", this);
00222         m_engineThread->start();
00223     }
00224 }
00225 
00226 void MHIContext::run(void)
00227 {
00228     QMutexLocker locker(&m_runLock);
00229 
00230     QTime t; t.start();
00231     while (!m_stop)
00232     {
00233         locker.unlock();
00234 
00235         int toWait;
00236         // Dequeue and process any key presses.
00237         int key = 0;
00238         do
00239         {
00240             (void)NetworkBootRequested();
00241             ProcessDSMCCQueue();
00242             {
00243                 QMutexLocker locker(&m_keyLock);
00244                 key = m_keyQueue.dequeue();
00245             }
00246 
00247             if (key != 0)
00248                 m_engine->GenerateUserAction(key);
00249 
00250             // Run the engine and find out how long to pause.
00251             toWait = m_engine->RunAll();
00252             if (toWait < 0)
00253                 return;
00254         } while (key != 0);
00255 
00256         toWait = (toWait > 1000 || toWait <= 0) ? 1000 : toWait;
00257 
00258         locker.relock();
00259         if (!m_stop && (toWait > 0))
00260             m_engine_wait.wait(locker.mutex(), toWait);
00261     }
00262 }
00263 
00264 // Dequeue and process any DSMCC packets.
00265 void MHIContext::ProcessDSMCCQueue(void)
00266 {
00267     DSMCCPacket *packet = NULL;
00268     do
00269     {
00270         {
00271             QMutexLocker locker(&m_dsmccLock);
00272             packet = m_dsmccQueue.dequeue();
00273         }
00274 
00275         if (packet)
00276         {
00277             m_dsmcc->ProcessSection(
00278                 packet->m_data,           packet->m_length,
00279                 packet->m_componentTag,   packet->m_carouselId,
00280                 packet->m_dataBroadcastId);
00281 
00282             delete packet;
00283         }
00284     } while (packet);
00285 }
00286 
00287 void MHIContext::QueueDSMCCPacket(
00288     unsigned char *data, int length, int componentTag,
00289     unsigned carouselId, int dataBroadcastId)
00290 {
00291     unsigned char *dataCopy =
00292         (unsigned char*) malloc(length * sizeof(unsigned char));
00293 
00294     if (dataCopy == NULL)
00295         return;
00296 
00297     memcpy(dataCopy, data, length*sizeof(unsigned char));
00298     {
00299         QMutexLocker locker(&m_dsmccLock);
00300         m_dsmccQueue.enqueue(new DSMCCPacket(dataCopy,     length,
00301                                              componentTag, carouselId,
00302                                              dataBroadcastId));
00303     }
00304     QMutexLocker locker(&m_runLock);
00305     m_engine_wait.wakeAll();
00306 }
00307 
00308 // A NetworkBootInfo sub-descriptor is present in the PMT.
00309 void MHIContext::SetNetBootInfo(const unsigned char *data, uint length)
00310 {
00311     if (length < 2) // A valid descriptor should always have at least 2 bytes.
00312         return;
00313 
00314     LOG(VB_MHEG, LOG_INFO, QString("[mhi] SetNetBootInfo version %1 mode %2 len %3")
00315         .arg(data[0]).arg(data[1]).arg(length));
00316 
00317     QMutexLocker locker(&m_dsmccLock);
00318     // Save the data from the descriptor.
00319     m_nbiData.resize(0);
00320     m_nbiData.reserve(length);
00321     m_nbiData.insert(m_nbiData.begin(), data, data+length);
00322     // If there is no Network Boot Info or we're setting it
00323     // for the first time just update the "last version".
00324     if (m_lastNbiVersion == NBI_VERSION_UNSET)
00325         m_lastNbiVersion = data[0];
00326     else
00327     {
00328         locker.unlock();
00329         QMutexLocker locker2(&m_runLock);
00330         m_engine_wait.wakeAll();
00331     }
00332 }
00333 
00334 void MHIContext::NetworkBootRequested(void)
00335 {
00336     QMutexLocker locker(&m_dsmccLock);
00337     if (m_nbiData.size() >= 2 && m_nbiData[0] != m_lastNbiVersion)
00338     {
00339         m_lastNbiVersion = m_nbiData[0]; // Update the saved version
00340         switch (m_nbiData[1])
00341         {
00342         case 1:
00343             m_dsmcc->Reset();
00344             m_engine->SetBooting();
00345             ClearDisplay();
00346             m_updated = true;
00347             break;
00348         case 2:
00349             m_engine->EngineEvent(9); // NetworkBootInfo EngineEvent
00350             break;
00351         default:
00352             LOG(VB_MHEG, LOG_INFO, QString("[mhi] Unknown NetworkBoot type %1")
00353                 .arg(m_nbiData[1]));
00354             break;
00355         }
00356     }
00357 }
00358 
00359 // Called by the engine to check for the presence of an object in the carousel.
00360 bool MHIContext::CheckCarouselObject(QString objectPath)
00361 {
00362     QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
00363     QByteArray result; // Unused
00364     int res = m_dsmcc->GetDSMCCObject(path, result);
00365     return res == 0; // It's available now.
00366 }
00367 
00368 // Called by the engine to request data from the carousel.
00369 bool MHIContext::GetCarouselData(QString objectPath, QByteArray &result)
00370 {
00371     // Get the path components.  The string will normally begin with "//"
00372     // since this is an absolute path but that will be removed by split.
00373     QStringList path = objectPath.split(QChar('/'), QString::SkipEmptyParts);
00374     // Since the DSMCC carousel and the MHEG engine are currently on the
00375     // same thread this is safe.  Otherwise we need to make a deep copy of
00376     // the result.
00377 
00378     QMutexLocker locker(&m_runLock);
00379     QTime t; t.start();
00380     while (!m_stop)
00381     {
00382         locker.unlock();
00383 
00384         int res = m_dsmcc->GetDSMCCObject(path, result);
00385         if (res == 0)
00386             return true; // Found it
00387         else if (res < 0)
00388             return false; // Not there.
00389         else if (t.elapsed() > 60000) // TODO get this from carousel info
00390             return false; // Not there.
00391         // Otherwise we block.
00392         // Process DSMCC packets then block for a second or until we receive
00393         // some more packets.  We should eventually find out if this item is
00394         // present.
00395         ProcessDSMCCQueue();
00396 
00397         locker.relock();
00398         if (!m_stop)
00399             m_engine_wait.wait(locker.mutex(), 1000);
00400     }
00401     return false; // Stop has been set.  Say the object isn't present.
00402 }
00403 
00404 // Called from tv_play when a key is pressed.
00405 // If it is one in the current profile we queue it for the engine
00406 // and return true otherwise we return false.
00407 bool MHIContext::OfferKey(QString key)
00408 {
00409     int action = 0;
00410     QMutexLocker locker(&m_keyLock);
00411 
00412     // This supports the UK and NZ key profile registers.
00413     // The UK uses 3, 4 and 5 and NZ 13, 14 and 15.  These are
00414     // similar but the NZ profile also provides an EPG key.
00415 
00416     if (key == ACTION_UP)
00417     {
00418         if (m_keyProfile == 4 || m_keyProfile == 5 ||
00419             m_keyProfile == 14 || m_keyProfile == 15)
00420             action = 1;
00421     }
00422     else if (key == ACTION_DOWN)
00423     {
00424         if (m_keyProfile == 4 || m_keyProfile == 5 ||
00425             m_keyProfile == 14 || m_keyProfile == 15)
00426             action = 2;
00427     }
00428     else if (key == ACTION_LEFT)
00429     {
00430         if (m_keyProfile == 4 || m_keyProfile == 5 ||
00431             m_keyProfile == 14 || m_keyProfile == 15)
00432             action = 3;
00433     }
00434     else if (key == ACTION_RIGHT)
00435     {
00436         if (m_keyProfile == 4 || m_keyProfile == 5 ||
00437             m_keyProfile == 14 || m_keyProfile == 15)
00438             action = 4;
00439     }
00440     else if (key == ACTION_0 || key == ACTION_1 || key == ACTION_2 ||
00441              key == ACTION_3 || key == ACTION_4 || key == ACTION_5 ||
00442              key == ACTION_6 || key == ACTION_7 || key == ACTION_8 ||
00443              key == ACTION_9)
00444     {
00445         if (m_keyProfile == 4 || m_keyProfile == 14)
00446             action = key.toInt() + 5;
00447     }
00448     else if (key == ACTION_SELECT)
00449     {
00450         if (m_keyProfile == 4 || m_keyProfile == 5 ||
00451             m_keyProfile == 14 || m_keyProfile == 15)
00452             action = 15;
00453     }
00454     else if (key == ACTION_TEXTEXIT)
00455         action = 16;
00456     else if (key == ACTION_MENURED)
00457         action = 100;
00458     else if (key == ACTION_MENUGREEN)
00459         action = 101;
00460     else if (key == ACTION_MENUYELLOW)
00461         action = 102;
00462     else if (key == ACTION_MENUBLUE)
00463         action = 103;
00464     else if (key == ACTION_MENUTEXT)
00465         action = m_keyProfile > 12 ? 105 : 104;
00466     else if (key == ACTION_MENUEPG)
00467         action = m_keyProfile > 12 ? 300 : 0;
00468 
00469     if (action != 0)
00470     {
00471         m_keyQueue.enqueue(action);
00472         LOG(VB_GENERAL, LOG_INFO, QString("Adding MHEG key %1:%2:%3").arg(key)
00473                 .arg(action).arg(m_keyQueue.size()));
00474         locker.unlock();
00475         QMutexLocker locker2(&m_runLock);
00476         m_engine_wait.wakeAll();
00477         return true;
00478     }
00479 
00480     return false;
00481 }
00482 
00483 void MHIContext::Reinit(const QRect &display)
00484 {
00485     m_displayWidth = display.width();
00486     m_displayHeight = display.height();
00487     m_xScale = (float)m_displayWidth / (float)MHIContext::StdDisplayWidth;
00488     m_yScale = (float)m_displayHeight / (float)MHIContext::StdDisplayHeight;
00489     m_videoRect   = QRect(QPoint(0,0), display.size());
00490     m_displayRect = display;
00491 }
00492 
00493 void MHIContext::SetInputRegister(int num)
00494 {
00495     QMutexLocker locker(&m_keyLock);
00496     m_keyQueue.clear();
00497     m_keyProfile = num;
00498 }
00499 
00500 
00501 // Called by the video player to redraw the image.
00502 void MHIContext::UpdateOSD(InteractiveScreen *osdWindow,
00503                            MythPainter *osdPainter)
00504 {
00505     if (!osdWindow || !osdPainter)
00506         return;
00507 
00508     QMutexLocker locker(&m_display_lock);
00509     m_updated = false;
00510     osdWindow->DeleteAllChildren();
00511     // Copy all the display items into the display.
00512     list<MHIImageData*>::iterator it = m_display.begin();
00513     for (int count = 0; it != m_display.end(); ++it, count++)
00514     {
00515         MHIImageData *data = *it;
00516         MythImage* image = osdPainter->GetFormatImage();
00517         if (!image)
00518             continue;
00519 
00520         image->Assign(data->m_image);
00521         MythUIImage *uiimage = new MythUIImage(osdWindow, QString("itv%1")
00522                                                .arg(count));
00523         if (uiimage)
00524         {
00525             uiimage->SetImage(image);
00526             uiimage->SetArea(MythRect(data->m_x, data->m_y,
00527                              data->m_image.width(), data->m_image.height()));
00528         }
00529     }
00530     osdWindow->OptimiseDisplayedArea();
00531     // N.B. bypasses OSD class hence no expiry set
00532     osdWindow->SetVisible(true);
00533 }
00534 
00535 void MHIContext::GetInitialStreams(int &audioTag, int &videoTag)
00536 {
00537     audioTag = m_audioTag;
00538     videoTag = m_videoTag;
00539 }
00540 
00541 
00542 // An area of the screen/image needs to be redrawn.
00543 // Called from the MHEG engine.
00544 // We always redraw the whole scene.
00545 void MHIContext::RequireRedraw(const QRegion &)
00546 {
00547     m_display_lock.lock();
00548     ClearDisplay();
00549     m_display_lock.unlock();
00550     // Always redraw the whole screen
00551     m_engine->DrawDisplay(QRegion(0, 0, StdDisplayWidth, StdDisplayHeight));
00552     m_updated = true;
00553 }
00554 
00555 void MHIContext::AddToDisplay(const QImage &image, int x, int y)
00556 {
00557     MHIImageData *data = new MHIImageData;
00558     int dispx = x + m_displayRect.left();
00559     int dispy = y + m_displayRect.top();
00560 
00561     data->m_image = image;
00562     data->m_x = dispx;
00563     data->m_y = dispy;
00564     QMutexLocker locker(&m_display_lock);
00565     m_display.push_back(data);
00566 }
00567 
00568 // In MHEG the video is just another item in the display stack
00569 // but when we create the OSD we overlay everything over the video.
00570 // We need to cut out anything belowthe video on the display stack
00571 // to leave the video area clear.
00572 // The videoRect gives the size and position to which the video must be scaled.
00573 // The displayRect gives the rectangle reserved for the video.
00574 // e.g. part of the video may be clipped within the displayRect.
00575 void MHIContext::DrawVideo(const QRect &videoRect, const QRect &dispRect)
00576 {
00577     // tell the video player to resize the video stream
00578     if (m_parent->GetNVP())
00579     {
00580         QRect vidRect(SCALED_X(videoRect.x()),
00581                       SCALED_Y(videoRect.y()),
00582                       SCALED_X(videoRect.width()),
00583                       SCALED_Y(videoRect.height()));
00584         if (m_videoRect != vidRect)
00585         {
00586             m_parent->GetNVP()->SetVideoResize(vidRect.translated(m_displayRect.topLeft()));
00587             m_videoRect = vidRect;
00588         }
00589     }
00590 
00591     QMutexLocker locker(&m_display_lock);
00592     QRect displayRect(SCALED_X(dispRect.x()),
00593                       SCALED_Y(dispRect.y()),
00594                       SCALED_X(dispRect.width()),
00595                       SCALED_Y(dispRect.height()));
00596 
00597     list<MHIImageData*>::iterator it = m_display.begin();
00598     for (; it != m_display.end(); ++it)
00599     {
00600         MHIImageData *data = *it;
00601         QRect imageRect(data->m_x, data->m_y,
00602                         data->m_image.width(), data->m_image.height());
00603         if (displayRect.intersects(imageRect))
00604         {
00605             // Replace this item with a set of cut-outs.
00606             it = m_display.erase(it);
00607 
00608             QVector<QRect> rects =
00609                 (QRegion(imageRect) - QRegion(displayRect)).rects();
00610             for (uint j = 0; j < (uint)rects.size(); j++)
00611             {
00612                 QRect &rect = rects[j];
00613                 QImage image =
00614                     data->m_image.copy(rect.x()-data->m_x, rect.y()-data->m_y,
00615                                        rect.width(), rect.height());
00616                 MHIImageData *newData = new MHIImageData;
00617                 newData->m_image = image;
00618                 newData->m_x = rect.x();
00619                 newData->m_y = rect.y();
00620                 m_display.insert(it, newData);
00621                 ++it;
00622             }
00623             delete data;
00624         }
00625     }
00626 }
00627 
00628 
00629 // Tuning.  Get the index corresponding to a given channel.
00630 // The format of the service is dvb://netID.[transPortID].serviceID
00631 // where the IDs are in hex.
00632 // or rec://svc/lcn/N where N is the "logical channel number"
00633 // i.e. the Freeview channel.
00634 // Returns -1 if it cannot find it.
00635 int MHIContext::GetChannelIndex(const QString &str)
00636 {
00637     int nResult = -1;
00638 
00639     do if (str.startsWith("dvb://"))
00640     {
00641         QStringList list = str.mid(6).split('.');
00642         MSqlQuery query(MSqlQuery::InitCon());
00643         if (list.size() != 3)
00644             break; // Malformed.
00645         // The various fields are expressed in hexadecimal.
00646         // Convert them to decimal for the DB.
00647         bool ok;
00648         int netID = list[0].toInt(&ok, 16);
00649         if (!ok)
00650             break;
00651         int serviceID = list[2].toInt(&ok, 16);
00652         if (!ok)
00653             break;
00654         // We only return channels that match the current capture source.
00655         if (list[1].isEmpty()) // TransportID is not specified
00656         {
00657             query.prepare(
00658                 "SELECT chanid "
00659                 "FROM channel, dtv_multiplex "
00660                 "WHERE networkid        = :NETID AND"
00661                 "      channel.mplexid  = dtv_multiplex.mplexid AND "
00662                 "      serviceid        = :SERVICEID AND "
00663                 "      channel.sourceid = :SOURCEID");
00664         }
00665         else
00666         {
00667             int transportID = list[1].toInt(&ok, 16);
00668             if (!ok)
00669                 break;
00670             query.prepare(
00671                 "SELECT chanid "
00672                 "FROM channel, dtv_multiplex "
00673                 "WHERE networkid        = :NETID AND"
00674                 "      channel.mplexid  = dtv_multiplex.mplexid AND "
00675                 "      serviceid        = :SERVICEID AND "
00676                 "      transportid      = :TRANSID AND "
00677                 "      channel.sourceid = :SOURCEID");
00678             query.bindValue(":TRANSID", transportID);
00679         }
00680         query.bindValue(":NETID", netID);
00681         query.bindValue(":SERVICEID", serviceID);
00682         query.bindValue(":SOURCEID", m_currentSource);
00683         if (query.exec() && query.isActive() && query.next())
00684             nResult = query.value(0).toInt();
00685     }
00686     else if (str.startsWith("rec://svc/lcn/"))
00687     {
00688         // I haven't seen this yet so this is untested.
00689         bool ok;
00690         int channelNo = str.mid(14).toInt(&ok); // Decimal integer
00691         if (!ok)
00692             break;
00693         MSqlQuery query(MSqlQuery::InitCon());
00694         query.prepare("SELECT chanid "
00695                       "FROM channel "
00696                       "WHERE channum = :CHAN AND "
00697                       "      channel.sourceid = :SOURCEID");
00698         query.bindValue(":CHAN", channelNo);
00699         query.bindValue(":SOURCEID", m_currentSource);
00700         if (query.exec() && query.isActive() && query.next())
00701             nResult = query.value(0).toInt();
00702     }
00703     else if (str == "rec://svc/cur")
00704         nResult = m_currentStream;
00705     else if (str == "rec://svc/def")
00706         nResult = m_currentChannel;
00707     else if (str.startsWith("rec://"))
00708         ;
00709     while(0);
00710 
00711     LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetChannelIndex %1 => %2")
00712         .arg(str).arg(nResult));
00713     return nResult;
00714 }
00715 
00716 // Get netId etc from the channel index.  This is the inverse of GetChannelIndex.
00717 bool MHIContext::GetServiceInfo(int channelId, int &netId, int &origNetId,
00718                                 int &transportId, int &serviceId)
00719 {
00720     LOG(VB_MHEG, LOG_INFO, QString("[mhi] GetServiceInfo %1").arg(channelId));
00721     MSqlQuery query(MSqlQuery::InitCon());
00722     query.prepare("SELECT networkid, transportid, serviceid "
00723                   "FROM channel, dtv_multiplex "
00724                   "WHERE chanid           = :CHANID AND "
00725                   "      channel.mplexid  = dtv_multiplex.mplexid");
00726     query.bindValue(":CHANID", channelId);
00727     if (query.exec() && query.isActive() && query.next())
00728     {
00729         netId = query.value(0).toInt();
00730         origNetId = netId; // We don't have this in the database.
00731         transportId = query.value(1).toInt();
00732         serviceId = query.value(2).toInt();
00733         return true;
00734     }
00735     else return false;
00736 }
00737 
00738 bool MHIContext::TuneTo(int channel, int tuneinfo)
00739 {
00740     LOG(VB_MHEG, LOG_INFO, QString("[mhi] TuneTo %1 0x%2")
00741         .arg(channel).arg(tuneinfo,0,16));
00742 
00743     if (!m_isLive)
00744         return false; // Can't tune if this is a recording.
00745 
00746     m_tuneinfo.append(tuneinfo);
00747 
00748     // Post an event requesting a channel change.
00749     MythEvent me(QString("NETWORK_CONTROL CHANID %1").arg(channel));
00750     gCoreContext->dispatch(me);
00751     // Reset the NBI version here to prevent a reboot.
00752     QMutexLocker locker(&m_dsmccLock);
00753     m_lastNbiVersion = NBI_VERSION_UNSET;
00754     m_nbiData.resize(0);
00755     return true;
00756 }
00757 
00758 // Begin playing audio from the specified stream
00759 bool MHIContext::BeginAudio(const QString &stream, int tag)
00760 {
00761     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginAudio %1 %2").arg(stream).arg(tag));
00762 
00763     int chan = GetChannelIndex(stream);
00764     if (chan < 0)
00765         return false;
00766 
00767     if (chan != m_currentStream)
00768     {
00769         // We have to tune to the channel where the audio is to be found.
00770         // Because the audio and video are both components of an MHEG stream
00771         // they will both be on the same channel.
00772         m_currentStream = chan;
00773         m_audioTag = tag;
00774         return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp);
00775     }
00776 
00777     if (tag < 0)
00778         return true; // Leave it at the default.
00779     else if (m_parent->GetNVP())
00780         return m_parent->GetNVP()->SetAudioByComponentTag(tag);
00781     else
00782         return false;
00783 }
00784 
00785 // Stop playing audio
00786 void MHIContext::StopAudio(void)
00787 {
00788     // Do nothing at the moment.
00789 }
00790 
00791 // Begin displaying video from the specified stream
00792 bool MHIContext::BeginVideo(const QString &stream, int tag)
00793 {
00794     LOG(VB_MHEG, LOG_INFO, QString("[mhi] BeginVideo %1 %2").arg(stream).arg(tag));
00795 
00796     int chan = GetChannelIndex(stream);
00797     if (chan < 0)
00798         return false;
00799     if (chan != m_currentStream)
00800     {
00801         // We have to tune to the channel where the video is to be found.
00802         m_currentStream = chan;
00803         m_videoTag = tag;
00804         return TuneTo(chan, kTuneKeepChnl|kTuneQuietly|kTuneKeepApp);
00805     }
00806     if (tag < 0)
00807         return true; // Leave it at the default.
00808     else if (m_parent->GetNVP())
00809         return m_parent->GetNVP()->SetVideoByComponentTag(tag);
00810 
00811     return false;
00812 }
00813 
00814 // Stop displaying video
00815 void MHIContext::StopVideo(void)
00816 {
00817     // Do nothing at the moment.
00818 }
00819 
00820 // Create a new object to draw dynamic line art.
00821 MHDLADisplay *MHIContext::CreateDynamicLineArt(
00822     bool isBoxed, MHRgba lineColour, MHRgba fillColour)
00823 {
00824     return new MHIDLA(this, isBoxed, lineColour, fillColour);
00825 }
00826 
00827 // Create a new object to draw text.
00828 MHTextDisplay *MHIContext::CreateText()
00829 {
00830     return new MHIText(this);
00831 }
00832 
00833 // Create a new object to draw bitmaps.
00834 MHBitmapDisplay *MHIContext::CreateBitmap(bool tiled)
00835 {
00836     return new MHIBitmap(this, tiled);
00837 }
00838 
00839 // Draw a rectangle.  This is complicated if we want to get transparency right.
00840 void MHIContext::DrawRect(int xPos, int yPos, int width, int height,
00841                           MHRgba colour)
00842 {
00843     if (colour.alpha() == 0 || height == 0 || width == 0)
00844         return; // Fully transparent
00845 
00846     QRgb qColour = qRgba(colour.red(), colour.green(),
00847                          colour.blue(), colour.alpha());
00848 
00849     int scaledWidth  = SCALED_X(width);
00850     int scaledHeight = SCALED_Y(height);
00851     QImage qImage(scaledWidth, scaledHeight, QImage::Format_ARGB32);
00852 
00853     for (int i = 0; i < scaledHeight; i++)
00854     {
00855         for (int j = 0; j < scaledWidth; j++)
00856         {
00857             qImage.setPixel(j, i, qColour);
00858         }
00859     }
00860 
00861     AddToDisplay(qImage, SCALED_X(xPos), SCALED_Y(yPos));
00862 }
00863 
00864 // Draw an image at the specified position.
00865 // Generally the whole of the image is drawn but sometimes the
00866 // image may be clipped. x and y define the origin of the bitmap
00867 // and usually that will be the same as the origin of the bounding
00868 // box (clipRect).
00869 void MHIContext::DrawImage(int x, int y, const QRect &clipRect,
00870                            const QImage &qImage)
00871 {
00872     if (qImage.isNull())
00873         return;
00874 
00875     QRect imageRect(x, y, qImage.width(), qImage.height());
00876     QRect displayRect = QRect(clipRect.x(), clipRect.y(),
00877                               clipRect.width(), clipRect.height()) & imageRect;
00878 
00879     if (displayRect == imageRect) // No clipping required
00880     {
00881         QImage q_scaled =
00882             qImage.scaled(
00883                 SCALED_X(displayRect.width()),
00884                 SCALED_Y(displayRect.height()),
00885                 Qt::IgnoreAspectRatio,
00886                 Qt::SmoothTransformation);
00887         AddToDisplay(q_scaled.convertToFormat(QImage::Format_ARGB32),
00888                      SCALED_X(x), SCALED_Y(y));
00889     }
00890     else if (!displayRect.isEmpty())
00891     { // We must clip the image.
00892         QImage clipped = qImage.convertToFormat(QImage::Format_ARGB32)
00893             .copy(displayRect.x() - x, displayRect.y() - y,
00894                   displayRect.width(), displayRect.height());
00895         QImage q_scaled =
00896             clipped.scaled(
00897                 SCALED_X(displayRect.width()),
00898                 SCALED_Y(displayRect.height()),
00899                 Qt::IgnoreAspectRatio,
00900                 Qt::SmoothTransformation);
00901         AddToDisplay(q_scaled,
00902                      SCALED_X(displayRect.x()),
00903                      SCALED_Y(displayRect.y()));
00904     }
00905     // Otherwise draw nothing.
00906 }
00907 
00908 // Fill in the background.  This is only called if there is some area of
00909 // the screen that is not covered with other visibles.
00910 void MHIContext::DrawBackground(const QRegion &reg)
00911 {
00912     if (reg.isEmpty())
00913         return;
00914 
00915     QRect bounds = reg.boundingRect();
00916     DrawRect(bounds.x(), bounds.y(), bounds.width(), bounds.height(),
00917              MHRgba(0, 0, 0, 255)/* black. */);
00918 }
00919 
00920 MHIText::MHIText(MHIContext *parent): m_parent(parent)
00921 {
00922     m_fontsize = 12;
00923     m_fontItalic = false;
00924     m_fontBold = false;
00925     m_width = 0;
00926     m_height = 0;
00927 }
00928 
00929 void MHIText::Draw(int x, int y)
00930 {
00931     m_parent->DrawImage(x, y, QRect(x, y, m_width, m_height), m_image);
00932 }
00933 
00934 void MHIText::SetSize(int width, int height)
00935 {
00936     m_width = width;
00937     m_height = height;
00938 }
00939 
00940 void MHIText::SetFont(int size, bool isBold, bool isItalic)
00941 {
00942     m_fontsize = size;
00943     m_fontItalic = isItalic;
00944     m_fontBold = isBold;
00945     // TODO: Only the size is currently used.
00946     // Bold and Italic are currently ignored.
00947 }
00948 
00949 // Return the bounding rectangle for a piece of text drawn in the
00950 // current font. If maxSize is non-negative it sets strLen to the
00951 // number of characters that will fit in the space and returns the
00952 // bounds for those characters.
00953 // N.B.  The box is relative to the origin so the y co-ordinate will
00954 // be negative. It's also possible that the x co-ordinate could be
00955 // negative for slanted fonts but that doesn't currently happen.
00956 QRect MHIText::GetBounds(const QString &str, int &strLen, int maxSize)
00957 {
00958     if (!m_parent->IsFaceLoaded())
00959         return QRect(0,0,0,0);
00960 
00961     FT_Face face = m_parent->GetFontFace();
00962     FT_Error error = FT_Set_Char_Size(face, 0, m_fontsize*64,
00963                                       FONT_WIDTHRES, FONT_HEIGHTRES);
00964     if (error)
00965         return QRect(0,0,0,0);
00966 
00967     FT_GlyphSlot slot = face->glyph; /* a small shortcut */
00968 
00969     int maxAscent = 0, maxDescent = 0, width = 0;
00970     FT_Bool useKerning = FT_HAS_KERNING(face);
00971     FT_UInt previous = 0;
00972 
00973     for (int n = 0; n < strLen; n++)
00974     {
00975         QChar ch = str[n];
00976         FT_UInt glyphIndex = FT_Get_Char_Index(face, ch.unicode());
00977         int kerning = 0;
00978 
00979         if (useKerning && previous != 0 && glyphIndex != 0)
00980         {
00981             FT_Vector delta;
00982             FT_Get_Kerning(face, previous, glyphIndex,
00983                            FT_KERNING_DEFAULT, &delta);
00984             kerning = delta.x;
00985         }
00986 
00987         error = FT_Load_Glyph(face, glyphIndex, 0); // Don't need to render.
00988 
00989         if (error)
00990             continue; // ignore errors.
00991 
00992         if (maxSize >= 0)
00993         {
00994             if ((width + slot->advance.x + kerning + (1<<6)-1) >> 6 > maxSize)
00995             {
00996                 // There isn't enough space for this character.
00997                 strLen = n;
00998                 break;
00999             }
01000         }
01001         // Calculate the ascent and descent of this glyph.
01002         int descent = slot->metrics.height - slot->metrics.horiBearingY;
01003 
01004         if (slot->metrics.horiBearingY > maxAscent)
01005             maxAscent = slot->metrics.horiBearingY;
01006 
01007         if (descent > maxDescent)
01008             maxDescent = descent;
01009 
01010         width += slot->advance.x + kerning;
01011         previous = glyphIndex;
01012     }
01013 
01014     maxAscent = (maxAscent + (1<<6)-1) >> 6;
01015     maxDescent = (maxDescent + (1<<6)-1) >> 6;
01016 
01017     return QRect(0, -maxAscent,
01018                  (width+(1<<6)-1) >> 6, maxAscent + maxDescent);
01019 }
01020 
01021 // Reset the image and fill it with transparent ink.
01022 // The UK MHEG profile says that we should consider the background
01023 // as paper and the text as ink.  We have to consider these as two
01024 // different layers.  The background is drawn separately as a rectangle.
01025 void MHIText::Clear(void)
01026 {
01027     m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
01028     // QImage::fill doesn't set the alpha buffer.
01029     for (int i = 0; i < m_height; i++)
01030     {
01031         for (int j = 0; j < m_width; j++)
01032         {
01033             m_image.setPixel(j, i, qRgba(0, 0, 0, 0));
01034         }
01035     }
01036 }
01037 
01038 // Draw a line of text in the given position within the image.
01039 // It would be nice to be able to use TTFFont for this but it doesn't provide
01040 // what we want.
01041 void MHIText::AddText(int x, int y, const QString &str, MHRgba colour)
01042 {
01043     if (!m_parent->IsFaceLoaded()) return;
01044     FT_Face face = m_parent->GetFontFace();
01045     FT_GlyphSlot slot = face->glyph;
01046     FT_Error error = FT_Set_Char_Size(face, 0, m_fontsize*64,
01047                                       FONT_WIDTHRES, FONT_HEIGHTRES);
01048 
01049     // X positions are computed to 64ths and rounded.
01050     // Y positions are in pixels
01051     int posX = x << 6;
01052     int pixelY = y;
01053     FT_Bool useKerning = FT_HAS_KERNING(face);
01054     FT_UInt previous = 0;
01055 
01056     int len = str.length();
01057     for (int n = 0; n < len; n++)
01058     {
01059         // Load the glyph.
01060         QChar ch = str[n];
01061         FT_UInt glyphIndex = FT_Get_Char_Index(face, ch.unicode());
01062         if (useKerning && previous != 0 && glyphIndex != 0)
01063         {
01064             FT_Vector delta;
01065             FT_Get_Kerning(face, previous, glyphIndex,
01066                            FT_KERNING_DEFAULT, &delta);
01067             posX += delta.x;
01068         }
01069         error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER);
01070 
01071         if (error)
01072             continue; // ignore errors
01073 
01074         if (slot->format != FT_GLYPH_FORMAT_BITMAP)
01075             continue; // Problem
01076 
01077         if ((enum FT_Pixel_Mode_)slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
01078             continue;
01079 
01080         unsigned char *source = slot->bitmap.buffer;
01081         // Get the origin for the bitmap
01082         int baseX = ((posX + (1 << 5)) >> 6) + slot->bitmap_left;
01083         int baseY = pixelY - slot->bitmap_top;
01084         // Copy the bitmap into the image.
01085         for (int i = 0; i < slot->bitmap.rows; i++)
01086         {
01087             for (int j = 0; j < slot->bitmap.width; j++)
01088             {
01089                 int greyLevel = source[j];
01090                 // Set the pixel to the specified colour but scale its
01091                 // brightness according to the grey scale of the pixel.
01092                 int red = colour.red();
01093                 int green = colour.green();
01094                 int blue = colour.blue();
01095                 int alpha = colour.alpha() *
01096                     greyLevel / slot->bitmap.num_grays;
01097                 int xBit =  j + baseX;
01098                 int yBit =  i + baseY;
01099 
01100                 // The bits ought to be inside the bitmap but
01101                 // I guess there's the possibility
01102                 // that rounding might put it outside.
01103                 if (xBit >= 0 && xBit < m_width &&
01104                     yBit >= 0 && yBit < m_height)
01105                 {
01106                     m_image.setPixel(xBit, yBit,
01107                                      qRgba(red, green, blue, alpha));
01108                 }
01109             }
01110             source += slot->bitmap.pitch;
01111         }
01112         posX += slot->advance.x;
01113         previous = glyphIndex;
01114     }
01115 }
01116 
01117 // Internal function to fill a rectangle with a colour
01118 void MHIDLA::DrawRect(int x, int y, int width, int height, MHRgba colour)
01119 {
01120     QRgb qColour = qRgba(colour.red(), colour.green(),
01121                          colour.blue(), colour.alpha());
01122 
01123     // Constrain the drawing within the image.
01124     if (x < 0)
01125     {
01126         width += x;
01127         x = 0;
01128     }
01129 
01130     if (y < 0)
01131     {
01132         height += y;
01133         y = 0;
01134     }
01135 
01136     if (width <= 0 || height <= 0)
01137         return;
01138 
01139     int imageWidth = m_image.width(), imageHeight = m_image.height();
01140     if (x+width > imageWidth)
01141         width = imageWidth - x;
01142 
01143     if (y+height > imageHeight)
01144         height = imageHeight - y;
01145 
01146     if (width <= 0 || height <= 0)
01147         return;
01148 
01149     for (int i = 0; i < height; i++)
01150     {
01151         for (int j = 0; j < width; j++)
01152         {
01153             m_image.setPixel(x+j, y+i, qColour);
01154         }
01155     }
01156 }
01157 
01158 // Reset the drawing.
01159 void MHIDLA::Clear()
01160 {
01161     if (m_width == 0 || m_height == 0)
01162     {
01163         m_image = QImage();
01164         return;
01165     }
01166     m_image = QImage(m_width, m_height, QImage::Format_ARGB32);
01167     // Fill the image with "transparent colour".
01168     DrawRect(0, 0, m_width, m_height, MHRgba(0, 0, 0, 0));
01169 }
01170 
01171 void MHIDLA::Draw(int x, int y)
01172 {
01173     QRect bounds(x, y, m_width, m_height);
01174     if (m_boxed && m_lineWidth != 0)
01175     {
01176         // Draw the lines round the outside.
01177         // These don't form part of the drawing.
01178         m_parent->DrawRect(x, y, m_width,
01179                            m_lineWidth, m_boxLineColour);
01180 
01181         m_parent->DrawRect(x, y + m_height - m_lineWidth,
01182                            m_width, m_lineWidth, m_boxLineColour);
01183 
01184         m_parent->DrawRect(x, y + m_lineWidth,
01185                            m_lineWidth, m_height - m_lineWidth * 2,
01186                            m_boxLineColour);
01187 
01188         m_parent->DrawRect(x + m_width - m_lineWidth, y + m_lineWidth,
01189                            m_lineWidth, m_height - m_lineWidth * 2,
01190                            m_boxLineColour);
01191 
01192         // Deflate the box to within the border.
01193         bounds = QRect(bounds.x() + m_lineWidth,
01194                        bounds.y() + m_lineWidth,
01195                        bounds.width() - 2*m_lineWidth,
01196                        bounds.height() - 2*m_lineWidth);
01197     }
01198 
01199     // Draw the background.
01200     m_parent->DrawRect(x + m_lineWidth,
01201                        y + m_lineWidth,
01202                        m_width  - m_lineWidth * 2,
01203                        m_height - m_lineWidth * 2,
01204                        m_boxFillColour);
01205 
01206     // Now the drawing.
01207     m_parent->DrawImage(x, y, bounds, m_image);
01208 }
01209 
01210 // The UK MHEG profile defines exactly how transparency is supposed to work.
01211 // The drawings are made using possibly transparent ink with any crossings
01212 // just set to that ink and then the whole drawing is alpha-merged with the
01213 // underlying graphics.
01214 // DynamicLineArt no longer seems to be used in transmissions in the UK
01215 // although it appears that DrawPoly is used in New Zealand.  These are
01216 // very basic implementations of the functions.
01217 
01218 // Lines
01219 void MHIDLA::DrawLine(int x1, int y1, int x2, int y2)
01220 {
01221     // Get the arguments so that the lower x comes first and the
01222     // absolute gradient is less than one.
01223     if (abs(y2-y1) > abs(x2-x1))
01224     {
01225         if (y2 > y1)
01226             DrawLineSub(y1, x1, y2, x2, true);
01227         else
01228             DrawLineSub(y2, x2, y1, x1, true);
01229     }
01230     else
01231     {
01232         if (x2 > x1)
01233             DrawLineSub(x1, y1, x2, y2, false);
01234         else
01235             DrawLineSub(x2, y2, x1, y1, false);
01236     }
01237 }
01238 
01239 // Based on the Bresenham line drawing algorithm but extended to draw
01240 // thick lines.
01241 void MHIDLA::DrawLineSub(int x1, int y1, int x2, int y2, bool swapped)
01242 {
01243     QRgb colour = qRgba(m_lineColour.red(), m_lineColour.green(),
01244                          m_lineColour.blue(), m_lineColour.alpha());
01245     int dx = x2-x1, dy = abs(y2-y1);
01246     int yStep = y2 >= y1 ? 1 : -1;
01247     // Adjust the starting positions to take account of the
01248     // line width.
01249     int error2 = dx/2;
01250     for (int k = 0; k < m_lineWidth/2; k++)
01251     {
01252         y1--;
01253         y2--;
01254         error2 += dy;
01255         if (error2*2 > dx)
01256         {
01257             error2 -= dx;
01258             x1 += yStep;
01259             x2 += yStep;
01260         }
01261     }
01262     // Main loop
01263     int y = y1;
01264     int error = dx/2;
01265     for (int x = x1; x <= x2; x++) // Include both endpoints
01266     {
01267         error2 = dx/2;
01268         int j = 0;
01269         // Inner loop also uses the Bresenham algorithm to draw lines
01270         // perpendicular to the principal direction.
01271         for (int i = 0; i < m_lineWidth; i++)
01272         {
01273             if (swapped)
01274             {
01275                 if (x+j >= 0 && y+i >= 0 && y+i < m_width && x+j < m_height)
01276                     m_image.setPixel(y+i, x+j, colour);
01277             }
01278             else
01279             {
01280                 if (x+j >= 0 && y+i >= 0 && x+j < m_width && y+i < m_height)
01281                     m_image.setPixel(x+j, y+i, colour);
01282             }
01283             error2 += dy;
01284             if (error2*2 > dx)
01285             {
01286                 error2 -= dx;
01287                 j -= yStep;
01288                 if (i < m_lineWidth-1)
01289                 {
01290                     // Add another pixel in this case.
01291                     if (swapped)
01292                     {
01293                         if (x+j >= 0 && y+i >= 0 && y+i < m_width && x+j < m_height)
01294                             m_image.setPixel(y+i, x+j, colour);
01295                     }
01296                     else
01297                     {
01298                         if (x+j >= 0 && y+i >= 0 && x+j < m_width && y+i < m_height)
01299                             m_image.setPixel(x+j, y+i, colour);
01300                     }
01301                 }
01302             }
01303         }
01304         error += dy;
01305         if (error*2 > dx)
01306         {
01307             error -= dx;
01308             y += yStep;
01309         }
01310     }
01311 }
01312 
01313 // Rectangles
01314 void MHIDLA::DrawBorderedRectangle(int x, int y, int width, int height)
01315 {
01316     if (m_lineWidth != 0)
01317     {
01318         // Draw the lines round the rectangle.
01319         DrawRect(x, y, width, m_lineWidth,
01320                  m_lineColour);
01321 
01322         DrawRect(x, y + height - m_lineWidth,
01323                  width, m_lineWidth,
01324                  m_lineColour);
01325 
01326         DrawRect(x, y + m_lineWidth,
01327                  m_lineWidth, height - m_lineWidth * 2,
01328                  m_lineColour);
01329 
01330         DrawRect(x + width - m_lineWidth, y + m_lineWidth,
01331                  m_lineWidth, height - m_lineWidth * 2,
01332                  m_lineColour);
01333 
01334         // Fill the rectangle.
01335         DrawRect(x + m_lineWidth, y + m_lineWidth,
01336                  width - m_lineWidth * 2, height - m_lineWidth * 2,
01337                  m_fillColour);
01338     }
01339     else
01340     {
01341         DrawRect(x, y, width, height, m_fillColour);
01342     }
01343 }
01344 
01345 // Ovals (ellipses)
01346 void MHIDLA::DrawOval(int x, int y, int width, int height)
01347 {
01348     // Not implemented.  Not actually used in practice.
01349 }
01350 
01351 // Arcs and sectors
01352 void MHIDLA::DrawArcSector(int x, int y, int width, int height,
01353                            int start, int arc, bool isSector)
01354 {
01355     // Not implemented.  Not actually used in practice.
01356 }
01357 
01358 // Polygons.  This is used directly and also to draw other figures.
01359 // The UK profile says that MHEG should not contain concave or
01360 // self-crossing polygons but we can get the former at least as
01361 // a result of rounding when drawing ellipses.
01362 typedef struct { int yBottom, yTop, xBottom; float slope; } lineSeg;
01363 
01364 void MHIDLA::DrawPoly(bool isFilled, int nPoints, const int *xArray, const int *yArray)
01365 {
01366     if (nPoints < 2)
01367         return;
01368 
01369     if (isFilled)
01370     {
01371         QVector <lineSeg> lineArray(nPoints);
01372         int nLines = 0;
01373         // Initialise the line segment array.  Include all lines
01374         // apart from horizontal.  Close the polygon by starting
01375         // with the last point in the array.
01376         int lastX = xArray[nPoints-1]; // Last point
01377         int lastY = yArray[nPoints-1];
01378         int yMin = lastY, yMax = lastY;
01379         for (int k = 0; k < nPoints; k++)
01380         {
01381             int thisX = xArray[k];
01382             int thisY = yArray[k];
01383             if (lastY != thisY)
01384             {
01385                 if (lastY > thisY)
01386                 {
01387                     lineArray[nLines].yBottom = thisY;
01388                     lineArray[nLines].yTop = lastY;
01389                     lineArray[nLines].xBottom = thisX;
01390                 }
01391                 else
01392                 {
01393                     lineArray[nLines].yBottom = lastY;
01394                     lineArray[nLines].yTop = thisY;
01395                     lineArray[nLines].xBottom = lastX;
01396                 }
01397                 lineArray[nLines++].slope =
01398                     (float)(thisX-lastX) / (float)(thisY-lastY);
01399             }
01400             if (thisY < yMin)
01401                 yMin = thisY;
01402             if (thisY > yMax)
01403                 yMax = thisY;
01404             lastX = thisX;
01405             lastY = thisY;
01406         }
01407 
01408         // Find the intersections of each line in the line segment array
01409         // with the scan line.  Because UK MHEG says that figures should be
01410         // convex we only need to consider two intersections.
01411         QRgb fillColour = qRgba(m_fillColour.red(), m_fillColour.green(),
01412                                 m_fillColour.blue(), m_fillColour.alpha());
01413         for (int y = yMin; y < yMax; y++)
01414         {
01415             int crossings = 0, xMin = 0, xMax = 0;
01416             for (int l = 0; l < nLines; l++)
01417             {
01418                 if (y >= lineArray[l].yBottom && y < lineArray[l].yTop)
01419                 {
01420                     int x = (int)round((float)(y - lineArray[l].yBottom) *
01421                         lineArray[l].slope) + lineArray[l].xBottom;
01422                     if (crossings == 0 || x < xMin)
01423                         xMin = x;
01424                     if (crossings == 0 || x > xMax)
01425                         xMax = x;
01426                     crossings++;
01427                 }
01428             }
01429             if (crossings == 2)
01430             {
01431                 for (int x = xMin; x <= xMax; x++)
01432                     m_image.setPixel(x, y, fillColour);
01433             }
01434         }
01435 
01436         // Draw the boundary
01437         int lastXpoint = xArray[nPoints-1]; // Last point
01438         int lastYpoint = yArray[nPoints-1];
01439         for (int i = 0; i < nPoints; i++)
01440         {
01441             DrawLine(xArray[i], yArray[i], lastXpoint, lastYpoint);
01442             lastXpoint = xArray[i];
01443             lastYpoint = yArray[i];
01444         }
01445     }
01446     else // PolyLine - draw lines between the points but don't close it.
01447     {
01448         for (int i = 1; i < nPoints; i++)
01449         {
01450             DrawLine(xArray[i], yArray[i], xArray[i-1], yArray[i-1]);
01451         }
01452     }
01453 }
01454 
01455 
01456 void MHIBitmap::Draw(int x, int y, QRect rect, bool tiled)
01457 {
01458     if (tiled)
01459     {
01460         if (m_image.width() == 0 || m_image.height() == 0)
01461             return;
01462         // Construct an image the size of the bounding box and tile the
01463         // bitmap over this.
01464         QImage tiledImage = QImage(rect.width(), rect.height(),
01465                                    QImage::Format_ARGB32);
01466 
01467         for (int i = 0; i < rect.width(); i++)
01468         {
01469             for (int j = 0; j < rect.height(); j++)
01470             {
01471                 tiledImage.setPixel(i, j, m_image.pixel(i % m_image.width(), j % m_image.height()));
01472             }
01473         }
01474         m_parent->DrawImage(rect.x(), rect.y(), rect, tiledImage);
01475     }
01476     else
01477     {
01478         m_parent->DrawImage(x, y, rect, m_image);
01479     }
01480 }
01481 
01482 // Create a bitmap from PNG.
01483 void MHIBitmap::CreateFromPNG(const unsigned char *data, int length)
01484 {
01485     m_image = QImage();
01486 
01487     if (!m_image.loadFromData(data, length, "PNG"))
01488     {
01489         m_image = QImage();
01490         return;
01491     }
01492 
01493     // Assume that if it has an alpha buffer then it's partly transparent.
01494     m_opaque = ! m_image.hasAlphaChannel();
01495 }
01496 
01497 // Convert an MPEG I-frame into a bitmap.  This is used as the way of
01498 // sending still pictures.  We convert the image to a QImage even
01499 // though that actually means converting it from YUV and eventually
01500 // converting it back again but we do this very infrequently so the
01501 // cost is outweighed by the simplification.
01502 void MHIBitmap::CreateFromMPEG(const unsigned char *data, int length)
01503 {
01504     AVCodecContext *c = NULL;
01505     AVFrame *picture = NULL;
01506     AVPacket pkt;
01507     uint8_t *buff = NULL;
01508     int gotPicture = 0, len;
01509     m_image = QImage();
01510 
01511     // Find the mpeg2 video decoder.
01512     AVCodec *codec = avcodec_find_decoder(CODEC_ID_MPEG2VIDEO);
01513     if (!codec)
01514         return;
01515 
01516     c = avcodec_alloc_context3(NULL);
01517     picture = avcodec_alloc_frame();
01518 
01519     if (avcodec_open2(c, codec, NULL) < 0)
01520         goto Close;
01521 
01522     // Copy the data into AVPacket
01523     if (av_new_packet(&pkt, length) < 0)
01524         goto Close;
01525 
01526     memcpy(pkt.data, data, length);
01527     buff = pkt.data;
01528 
01529     while (pkt.size > 0 && ! gotPicture)
01530     {
01531         len = avcodec_decode_video2(c, picture, &gotPicture, &pkt);
01532         if (len < 0) // Error
01533             goto Close;
01534         pkt.data += len;
01535         pkt.size -= len;
01536     }
01537 
01538     if (!gotPicture)
01539     {
01540         pkt.data = NULL;
01541         pkt.size = 0;
01542         // Process any buffered data
01543         if (avcodec_decode_video2(c, picture, &gotPicture, &pkt) < 0)
01544             goto Close;
01545     }
01546 
01547     if (gotPicture)
01548     {
01549         int nContentWidth = c->width;
01550         int nContentHeight = c->height;
01551         m_image = QImage(nContentWidth, nContentHeight, QImage::Format_ARGB32);
01552         m_opaque = true; // MPEG images are always opaque.
01553 
01554         AVPicture retbuf;
01555         memset(&retbuf, 0, sizeof(AVPicture));
01556 
01557         int bufflen = nContentWidth * nContentHeight * 3;
01558         unsigned char *outputbuf = new unsigned char[bufflen];
01559 
01560         avpicture_fill(&retbuf, outputbuf, PIX_FMT_RGB24,
01561                        nContentWidth, nContentHeight);
01562 
01563         myth_sws_img_convert(
01564             &retbuf, PIX_FMT_RGB24, (AVPicture*)picture, c->pix_fmt,
01565                     nContentWidth, nContentHeight);
01566 
01567         uint8_t * buf = outputbuf;
01568 
01569         // Copy the data a pixel at a time.
01570         // This should handle endianness correctly.
01571         for (int i = 0; i < nContentHeight; i++)
01572         {
01573             for (int j = 0; j < nContentWidth; j++)
01574             {
01575                 int red = *buf++;
01576                 int green = *buf++;
01577                 int blue = *buf++;
01578                 m_image.setPixel(j, i, qRgb(red, green, blue));
01579             }
01580         }
01581         delete [] outputbuf;
01582     }
01583 
01584 Close:
01585     pkt.data = buff;
01586     av_free_packet(&pkt);
01587     avcodec_close(c);
01588     av_free(c);
01589     av_free(picture);
01590 }
01591 
01592 // Scale the bitmap.  Only used for image derived from MPEG I-frames.
01593 void MHIBitmap::ScaleImage(int newWidth, int newHeight)
01594 {
01595     if (m_image.isNull())
01596         return;
01597 
01598     if (newWidth == m_image.width() && newHeight == m_image.height())
01599         return;
01600 
01601     if (newWidth <= 0 || newHeight <= 0)
01602     { // This would be a bit silly but handle it anyway.
01603         m_image = QImage();
01604         return;
01605     }
01606 
01607     m_image = m_image.scaled(newWidth, newHeight,
01608             Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
01609 }
01610 
01611 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends