|
MythTV
0.26-pre
|
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 ®) 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
1.7.6.1