|
MythTV
0.26-pre
|
00001 00002 #include "mythuiimage.h" 00003 00004 // C 00005 #include <cstdlib> 00006 #include <time.h> 00007 00008 // POSIX 00009 #include <stdint.h> 00010 00011 // QT 00012 #include <QFile> 00013 #include <QDir> 00014 #include <QDomDocument> 00015 #include <QImageReader> 00016 #include <QReadWriteLock> 00017 #include <QRunnable> 00018 #include <QEvent> 00019 #include <QCoreApplication> 00020 00021 // libmythbase 00022 #include "mythlogging.h" 00023 00024 // Mythui 00025 #include "mythpainter.h" 00026 #include "mythmainwindow.h" 00027 #include "mythuihelper.h" 00028 #include "mythscreentype.h" 00029 00030 class ImageLoadThread; 00031 00032 #define LOC QString("MythUIImage(0x%1): ").arg((uint64_t)this,0,16) 00033 00035 00036 ImageProperties::ImageProperties() 00037 { 00038 Init(); 00039 } 00040 00041 ImageProperties::ImageProperties(const ImageProperties& other) 00042 { 00043 Init(); 00044 Copy(other); 00045 } 00046 00047 ImageProperties &ImageProperties::operator=(const ImageProperties &other) 00048 { 00049 Copy(other); 00050 00051 return *this; 00052 } 00053 00054 ImageProperties::~ImageProperties() 00055 { 00056 if (maskImage) 00057 maskImage->DownRef(); 00058 } 00059 00060 void ImageProperties::Init() 00061 { 00062 filename = QString(); 00063 cropRect = MythRect(0, 0, 0, 0); 00064 forceSize = QSize(0, 0); 00065 preserveAspect = false; 00066 isGreyscale = false; 00067 isReflected = false; 00068 isMasked = false; 00069 reflectAxis = ReflectVertical; 00070 reflectScale = 100; 00071 reflectLength = 100; 00072 reflectShear = 0; 00073 reflectSpacing = 0, 00074 maskImage = NULL; 00075 } 00076 00077 void ImageProperties::Copy(const ImageProperties &other) 00078 { 00079 filename = other.filename; 00080 filename.detach(); 00081 00082 cropRect = other.cropRect; 00083 forceSize = other.forceSize; 00084 00085 preserveAspect = other.preserveAspect; 00086 isGreyscale = other.isGreyscale; 00087 isReflected = other.isReflected; 00088 isMasked = other.isMasked; 00089 00090 reflectAxis = other.reflectAxis; 00091 reflectScale = other.reflectScale; 00092 reflectLength = other.reflectLength; 00093 reflectShear = other.reflectShear; 00094 reflectSpacing = other.reflectSpacing; 00095 00096 SetMaskImage(other.maskImage); 00097 } 00098 00099 void ImageProperties::SetMaskImage(MythImage* image) 00100 { 00101 if (maskImage) 00102 maskImage->DownRef(); 00103 00104 isMasked = false; 00105 maskImage = image; 00106 00107 if (maskImage) 00108 { 00109 maskImage->UpRef(); 00110 isMasked = true; 00111 } 00112 } 00113 00117 class ImageLoader 00118 { 00119 public: 00120 ImageLoader() { }; 00121 ~ImageLoader() { }; 00122 00123 static QHash<QString, const MythUIImage *> m_loadingImages; 00124 static QMutex m_loadingImagesLock; 00125 static QWaitCondition m_loadingImagesCond; 00126 00127 static bool PreLoad(const QString &cacheKey, const MythUIImage *uitype) 00128 { 00129 m_loadingImagesLock.lock(); 00130 00131 // Check to see if the image is being loaded by us in another thread 00132 if ((m_loadingImages.contains(cacheKey)) && 00133 (m_loadingImages[cacheKey] == uitype)) 00134 { 00135 LOG(VB_GUI | VB_FILE, LOG_DEBUG, 00136 QString("ImageLoader::PreLoad(%1), this " 00137 "file is already being loaded by this same MythUIImage " 00138 "in another thread.").arg(cacheKey)); 00139 m_loadingImagesLock.unlock(); 00140 return false; 00141 } 00142 00143 // Check to see if the exact same image is being loaded anywhere else 00144 while (m_loadingImages.contains(cacheKey)) 00145 m_loadingImagesCond.wait(&m_loadingImagesLock); 00146 00147 m_loadingImages[cacheKey] = uitype; 00148 m_loadingImagesLock.unlock(); 00149 00150 return true; 00151 } 00152 00153 static void PostLoad(const QString &cacheKey) 00154 { 00155 m_loadingImagesLock.lock(); 00156 m_loadingImages.remove(cacheKey); 00157 m_loadingImagesCond.wakeAll(); 00158 m_loadingImagesLock.unlock(); 00159 } 00160 00161 static bool SupportsAnimation(const QString &filename) 00162 { 00163 QString extension = filename.section('.', -1); 00164 if (!filename.startsWith("myth://") && 00165 (extension == "gif" || 00166 extension == "apng" || 00167 extension == "mng")) 00168 return true; 00169 00170 return false; 00171 } 00172 00177 static QString GenImageLabel(const ImageProperties &imProps) 00178 { 00179 QString imagelabel; 00180 QString s_Attrib; 00181 00182 if (imProps.isMasked) 00183 s_Attrib = "masked"; 00184 00185 if (imProps.isReflected) 00186 s_Attrib += "reflected"; 00187 00188 if (imProps.isGreyscale) 00189 s_Attrib += "greyscale"; 00190 00191 int w = -1; 00192 int h = -1; 00193 if (!imProps.forceSize.isNull()) 00194 { 00195 if (imProps.forceSize.width() != -1) 00196 w = imProps.forceSize.width(); 00197 00198 if (imProps.forceSize.height() != -1) 00199 h = imProps.forceSize.height(); 00200 } 00201 00202 00203 imagelabel = QString("%1-%2-%3x%4.png") 00204 .arg(imProps.filename) 00205 .arg(s_Attrib) 00206 .arg(w) 00207 .arg(h); 00208 imagelabel.replace('/', '-'); 00209 00210 return imagelabel; 00211 } 00212 00213 static MythImage *LoadImage(MythPainter *painter, 00214 // Must be a copy for thread safety 00215 ImageProperties imProps, 00216 ImageCacheMode cacheMode, 00217 // Included only to check address, could be 00218 // replaced by generating a unique value for 00219 // each MythUIImage object? 00220 const MythUIImage *parent, 00221 bool &aborted, 00222 MythImageReader *imageReader = NULL) 00223 { 00224 QString cacheKey = GenImageLabel(imProps); 00225 if (!PreLoad(cacheKey, parent)) 00226 { 00227 aborted = true; 00228 return NULL; 00229 } 00230 00231 QString filename = imProps.filename; 00232 MythImage *image = NULL; 00233 00234 bool bForceResize = false; 00235 bool bFoundInCache = false; 00236 00237 int w = -1; 00238 int h = -1; 00239 00240 if (!imProps.forceSize.isNull()) 00241 { 00242 if (imProps.forceSize.width() != -1) 00243 w = imProps.forceSize.width(); 00244 00245 if (imProps.forceSize.height() != -1) 00246 h = imProps.forceSize.height(); 00247 00248 bForceResize = true; 00249 } 00250 00251 if (!imageReader) 00252 { 00253 image = GetMythUI()->LoadCacheImage(filename, cacheKey, 00254 painter, cacheMode); 00255 } 00256 00257 if (image) 00258 { 00259 image->UpRef(); 00260 00261 LOG(VB_GUI | VB_FILE, LOG_INFO, 00262 QString("ImageLoader::LoadImage(%1) Found in cache, " 00263 "RefCount = %2").arg(cacheKey) 00264 .arg(image->RefCount())); 00265 00266 if (imProps.isReflected) 00267 image->setIsReflected(true); 00268 00269 bFoundInCache = true; 00270 } 00271 else 00272 { 00273 LOG(VB_GUI | VB_FILE, LOG_INFO, 00274 QString("ImageLoader::LoadImage(%1) NOT Found in cache. " 00275 "Loading Directly").arg(cacheKey)); 00276 00277 image = painter->GetFormatImage(); 00278 image->UpRef(); 00279 bool ok = false; 00280 00281 if (imageReader) 00282 ok = image->Load(imageReader); 00283 else 00284 ok = image->Load(filename); 00285 00286 if (!ok) 00287 { 00288 image->DownRef(); 00289 image = NULL; 00290 } 00291 } 00292 00293 if (image && !bFoundInCache) 00294 { 00295 if (bForceResize) 00296 image->Resize(QSize(w, h), imProps.preserveAspect); 00297 00298 if (imProps.isMasked) 00299 { 00300 QRect imageArea = image->rect(); 00301 QRect maskArea = imProps.GetMaskImage()->rect(); 00302 00303 // Crop the mask to the image 00304 int x = 0; 00305 int y = 0; 00306 00307 if (maskArea.width() > imageArea.width()) 00308 x = (maskArea.width() - imageArea.width()) / 2; 00309 00310 if (maskArea.height() > imageArea.height()) 00311 y = (maskArea.height() - imageArea.height()) / 2; 00312 00313 if (x > 0 || y > 0) 00314 imageArea.translate(x, y); 00315 00316 QImage mask = imProps.GetMaskImage()->copy(imageArea); 00317 image->setAlphaChannel(mask.alphaChannel()); 00318 } 00319 00320 if (imProps.isReflected) 00321 image->Reflect(imProps.reflectAxis, imProps.reflectShear, 00322 imProps.reflectScale, imProps.reflectLength, 00323 imProps.reflectSpacing); 00324 00325 if (imProps.isGreyscale) 00326 image->ToGreyscale(); 00327 00328 if (!imageReader) 00329 GetMythUI()->CacheImage(cacheKey, image); 00330 } 00331 00332 if (image && image->isNull()) 00333 { 00334 LOG(VB_GUI | VB_FILE, LOG_INFO, 00335 QString("ImageLoader::LoadImage(%1) Image is NULL") 00336 .arg(filename)); 00337 00338 image->DownRef(); 00339 image = NULL; 00340 } 00341 00342 if (image) 00343 image->SetChanged(); 00344 00345 PostLoad(cacheKey); 00346 00347 return image; 00348 } 00349 00350 static AnimationFrames *LoadAnimatedImage(MythPainter *painter, 00351 // Must be a copy for thread safety 00352 ImageProperties imProps, 00353 ImageCacheMode cacheMode, 00354 // Included only to check address, could be 00355 // replaced by generating a unique value for 00356 // each MythUIImage object? 00357 const MythUIImage *parent, 00358 bool &aborted) 00359 { 00360 QString filename = QString("frame-%1-") + imProps.filename; 00361 QString frameFilename; 00362 int imageCount = 1; 00363 00364 MythImageReader *imageReader = new MythImageReader(filename); 00365 00366 AnimationFrames *images = new AnimationFrames(); 00367 00368 while (imageReader->canRead() && !aborted) 00369 { 00370 frameFilename = filename.arg(imageCount); 00371 00372 ImageProperties frameProps = imProps; 00373 frameProps.filename = frameFilename; 00374 00375 MythImage *im = LoadImage(painter, frameProps, cacheMode, parent, 00376 aborted, imageReader); 00377 00378 if (!im) 00379 aborted = true; 00380 00381 images->append(AnimationFrame(im, imageReader->nextImageDelay())); 00382 imageCount++; 00383 } 00384 00385 delete imageReader; 00386 00387 return images; 00388 } 00389 00390 }; 00391 00392 QHash<QString, const MythUIImage *> ImageLoader::m_loadingImages; 00393 QMutex ImageLoader::m_loadingImagesLock; 00394 QWaitCondition ImageLoader::m_loadingImagesCond; 00395 00399 class ImageLoadEvent : public QEvent 00400 { 00401 public: 00402 ImageLoadEvent(const MythUIImage *parent, MythImage *image, 00403 const QString &basefile, const QString &filename, 00404 int number, bool aborted) 00405 : QEvent(kEventType), 00406 m_parent(parent), m_image(image), m_basefile(basefile), 00407 m_filename(filename), m_number(number), 00408 m_images(NULL), m_aborted(aborted) { } 00409 00410 ImageLoadEvent(const MythUIImage *parent, AnimationFrames *frames, 00411 const QString &basefile, 00412 const QString &filename, bool aborted) 00413 : QEvent(kEventType), 00414 m_parent(parent), m_image(NULL), m_basefile(basefile), 00415 m_filename(filename), m_number(0), 00416 m_images(frames), m_aborted(aborted) { } 00417 00418 const MythUIImage *GetParent() const { return m_parent; } 00419 MythImage *GetImage() const { return m_image; } 00420 const QString GetBasefile() const { return m_basefile; } 00421 const QString GetFilename() const { return m_filename; } 00422 const int GetNumber() const { return m_number; } 00423 AnimationFrames *GetAnimationFrames() const { return m_images; } 00424 const bool GetAbortState() const { return m_aborted; } 00425 00426 static Type kEventType; 00427 00428 private: 00429 const MythUIImage *m_parent; 00430 MythImage *m_image; 00431 QString m_basefile; 00432 QString m_filename; 00433 int m_number; 00434 00435 // Animated Images 00436 AnimationFrames *m_images; 00437 00438 // Image Load 00439 bool m_aborted; 00440 }; 00441 00442 QEvent::Type ImageLoadEvent::kEventType = 00443 (QEvent::Type) QEvent::registerEventType(); 00444 00448 class ImageLoadThread : public QRunnable 00449 { 00450 public: 00451 ImageLoadThread(const MythUIImage *parent, MythPainter *painter, 00452 const ImageProperties &imProps, const QString &basefile, 00453 int number, ImageCacheMode mode) : 00454 m_parent(parent), m_painter(painter), m_imageProperties(imProps), 00455 m_basefile(basefile), m_number(number), m_cacheMode(mode) 00456 { 00457 } 00458 00459 void run() 00460 { 00461 bool aborted = false; 00462 QString filename = m_imageProperties.filename; 00463 00464 // NOTE Do NOT use MythImageReader::supportsAnimation here, it defeats 00465 // the point of caching remote images 00466 if (ImageLoader::SupportsAnimation(filename)) 00467 { 00468 AnimationFrames *frames; 00469 00470 frames = ImageLoader::LoadAnimatedImage(m_painter, 00471 m_imageProperties, 00472 m_cacheMode, m_parent, 00473 aborted); 00474 00475 ImageLoadEvent *le = new ImageLoadEvent(m_parent, frames, 00476 m_basefile, 00477 m_imageProperties.filename, 00478 aborted); 00479 QCoreApplication::postEvent(const_cast<MythUIImage*>(m_parent), le); 00480 } 00481 else 00482 { 00483 MythImage *image = ImageLoader::LoadImage(m_painter, 00484 m_imageProperties, 00485 m_cacheMode, m_parent, 00486 aborted); 00487 00488 ImageLoadEvent *le = new ImageLoadEvent(m_parent, image, m_basefile, 00489 m_imageProperties.filename, 00490 m_number, aborted); 00491 QCoreApplication::postEvent(const_cast<MythUIImage*>(m_parent), le); 00492 } 00493 } 00494 00495 private: 00496 const MythUIImage *m_parent; 00497 MythPainter *m_painter; 00498 ImageProperties m_imageProperties; 00499 QString m_basefile; 00500 int m_number; 00501 ImageCacheMode m_cacheMode; 00502 }; 00503 00505 class MythUIImagePrivate 00506 { 00507 public: 00508 MythUIImagePrivate(MythUIImage *p) 00509 : m_parent(p), m_UpdateLock(QReadWriteLock::Recursive) 00510 { }; 00511 ~MythUIImagePrivate() {}; 00512 00513 MythUIImage *m_parent; 00514 00515 QReadWriteLock m_UpdateLock; 00516 }; 00517 00519 00520 MythUIImage::MythUIImage(const QString &filepattern, 00521 int low, int high, int delayms, 00522 MythUIType *parent, const QString &name) 00523 : MythUIType(parent, name) 00524 { 00525 m_imageProperties.filename = filepattern; 00526 m_LowNum = low; 00527 m_HighNum = high; 00528 00529 m_Delay = delayms; 00530 m_EnableInitiator = true; 00531 00532 d = new MythUIImagePrivate(this); 00533 emit DependChanged(false); 00534 Init(); 00535 } 00536 00537 MythUIImage::MythUIImage(const QString &filename, MythUIType *parent, 00538 const QString &name) 00539 : MythUIType(parent, name) 00540 { 00541 m_imageProperties.filename = filename; 00542 m_OrigFilename = filename; 00543 00544 m_LowNum = 0; 00545 m_HighNum = 0; 00546 m_Delay = -1; 00547 m_EnableInitiator = true; 00548 00549 d = new MythUIImagePrivate(this); 00550 emit DependChanged(false); 00551 Init(); 00552 } 00553 00554 MythUIImage::MythUIImage(MythUIType *parent, const QString &name) 00555 : MythUIType(parent, name) 00556 { 00557 m_LowNum = 0; 00558 m_HighNum = 0; 00559 m_Delay = -1; 00560 m_EnableInitiator = true; 00561 00562 d = new MythUIImagePrivate(this); 00563 00564 Init(); 00565 } 00566 00567 MythUIImage::~MythUIImage() 00568 { 00569 // Wait until all image loading threads are complete or bad things 00570 // may happen if this MythUIImage disappears when a queued thread 00571 // needs it. 00572 if (m_runningThreads > 0) 00573 { 00574 GetMythUI()->GetImageThreadPool()->waitForDone(); 00575 } 00576 00577 Clear(); 00578 00579 delete d; 00580 } 00581 00585 void MythUIImage::Clear(void) 00586 { 00587 QWriteLocker updateLocker(&d->m_UpdateLock); 00588 QMutexLocker locker(&m_ImagesLock); 00589 00590 while (!m_Images.isEmpty()) 00591 { 00592 QHash<int, MythImage *>::iterator it = m_Images.begin(); 00593 00594 if (*it) 00595 (*it)->DownRef(); 00596 00597 m_Images.remove(it.key()); 00598 } 00599 00600 m_Delays.clear(); 00601 00602 if (m_animatedImage) 00603 { 00604 m_LowNum = 0; 00605 m_HighNum = 0; 00606 m_animatedImage = false; 00607 } 00608 } 00609 00613 void MythUIImage::Reset(void) 00614 { 00615 d->m_UpdateLock.lockForWrite(); 00616 00617 SetMinArea(MythRect()); 00618 00619 if (m_imageProperties.filename != m_OrigFilename) 00620 { 00621 m_imageProperties.filename = m_OrigFilename; 00622 00623 if (m_animatedImage) 00624 { 00625 m_LowNum = 0; 00626 m_HighNum = 0; 00627 m_animatedImage = false; 00628 } 00629 emit DependChanged(true); 00630 00631 d->m_UpdateLock.unlock(); 00632 Load(); 00633 } 00634 else 00635 d->m_UpdateLock.unlock(); 00636 00637 MythUIType::Reset(); 00638 } 00639 00643 void MythUIImage::Init(void) 00644 { 00645 m_CurPos = 0; 00646 m_LastDisplay = QTime::currentTime(); 00647 00648 m_NeedLoad = false; 00649 00650 m_animationCycle = kCycleStart; 00651 m_animationReverse = false; 00652 m_animatedImage = false; 00653 00654 m_runningThreads = 0; 00655 } 00656 00660 void MythUIImage::SetFilename(const QString &filename) 00661 { 00662 QWriteLocker updateLocker(&d->m_UpdateLock); 00663 m_imageProperties.filename = filename; 00664 if (filename == m_OrigFilename) 00665 emit DependChanged(true); 00666 else 00667 emit DependChanged(false); 00668 } 00669 00674 void MythUIImage::SetFilepattern(const QString &filepattern, int low, 00675 int high) 00676 { 00677 QWriteLocker updateLocker(&d->m_UpdateLock); 00678 m_imageProperties.filename = filepattern; 00679 m_LowNum = low; 00680 m_HighNum = high; 00681 if (filepattern == m_OrigFilename) 00682 emit DependChanged(true); 00683 else 00684 emit DependChanged(false); 00685 } 00686 00690 void MythUIImage::SetImageCount(int low, int high) 00691 { 00692 QWriteLocker updateLocker(&d->m_UpdateLock); 00693 m_LowNum = low; 00694 m_HighNum = high; 00695 } 00696 00700 void MythUIImage::SetDelay(int delayms) 00701 { 00702 QWriteLocker updateLocker(&d->m_UpdateLock); 00703 m_Delay = delayms; 00704 m_LastDisplay = QTime::currentTime(); 00705 m_CurPos = 0; 00706 } 00707 00711 void MythUIImage::SetDelays(QVector<int> delays) 00712 { 00713 QWriteLocker updateLocker(&d->m_UpdateLock); 00714 QMutexLocker imageLocker(&m_ImagesLock); 00715 QVector<int>::iterator it; 00716 00717 for (it = delays.begin(); it != delays.end(); ++it) 00718 m_Delays[m_Delays.size()] = *it; 00719 00720 if (m_Delay == -1) 00721 m_Delay = m_Delays[0]; 00722 00723 m_LastDisplay = QTime::currentTime(); 00724 m_CurPos = 0; 00725 } 00726 00731 void MythUIImage::SetImage(MythImage *img) 00732 { 00733 d->m_UpdateLock.lockForWrite(); 00734 00735 if (!img) 00736 { 00737 d->m_UpdateLock.unlock(); 00738 Reset(); 00739 return; 00740 } 00741 00742 m_imageProperties.filename = img->GetFileName(); 00743 Clear(); 00744 m_Delay = -1; 00745 00746 img->UpRef(); 00747 00748 QSize forceSize = m_imageProperties.forceSize; 00749 if (!forceSize.isNull()) 00750 { 00751 int w = (forceSize.width() <= 0) ? img->width() : forceSize.width(); 00752 int h = (forceSize.height() <= 0) ? img->height() : forceSize.height(); 00753 img->Resize(QSize(w, h), m_imageProperties.preserveAspect); 00754 } 00755 00756 if (m_imageProperties.isReflected && !img->IsReflected()) 00757 img->Reflect(m_imageProperties.reflectAxis, 00758 m_imageProperties.reflectShear, 00759 m_imageProperties.reflectScale, 00760 m_imageProperties.reflectLength, 00761 m_imageProperties.reflectSpacing); 00762 00763 if (m_imageProperties.isGreyscale && !img->isGrayscale()) 00764 img->ToGreyscale(); 00765 00766 if (m_imageProperties.forceSize.isNull()) 00767 SetSize(img->size()); 00768 00769 m_ImagesLock.lock(); 00770 m_Images[0] = img; 00771 m_Delays.clear(); 00772 m_ImagesLock.unlock(); 00773 00774 m_CurPos = 0; 00775 m_Initiator = m_EnableInitiator; 00776 SetRedraw(); 00777 00778 d->m_UpdateLock.unlock(); 00779 } 00780 00786 void MythUIImage::SetImages(QVector<MythImage *> *images) 00787 { 00788 Clear(); 00789 00790 QWriteLocker updateLocker(&d->m_UpdateLock); 00791 QSize aSize = GetFullArea().size(); 00792 00793 QVector<MythImage *>::iterator it; 00794 00795 for (it = images->begin(); it != images->end(); ++it) 00796 { 00797 MythImage *im = (*it); 00798 00799 if (!im) 00800 { 00801 QMutexLocker locker(&m_ImagesLock); 00802 m_Images[m_Images.size()] = im; 00803 continue; 00804 } 00805 00806 im->UpRef(); 00807 00808 00809 QSize forceSize = m_imageProperties.forceSize; 00810 if (!forceSize.isNull()) 00811 { 00812 int w = (forceSize.width() <= 0) ? im->width() : forceSize.width(); 00813 int h = (forceSize.height() <= 0) ? im->height() : forceSize.height(); 00814 im->Resize(QSize(w, h), m_imageProperties.preserveAspect); 00815 } 00816 00817 if (m_imageProperties.isReflected && !im->IsReflected()) 00818 im->Reflect(m_imageProperties.reflectAxis, 00819 m_imageProperties.reflectShear, 00820 m_imageProperties.reflectScale, 00821 m_imageProperties.reflectLength, 00822 m_imageProperties.reflectSpacing); 00823 00824 if (m_imageProperties.isGreyscale && !im->isGrayscale()) 00825 im->ToGreyscale(); 00826 00827 m_ImagesLock.lock(); 00828 m_Images[m_Images.size()] = im; 00829 m_ImagesLock.unlock(); 00830 00831 aSize = aSize.expandedTo(im->size()); 00832 } 00833 00834 SetImageCount(1, m_Images.size()); 00835 00836 if (m_imageProperties.forceSize.isNull()) 00837 SetSize(aSize); 00838 00839 MythRect rect(GetFullArea()); 00840 rect.setSize(aSize); 00841 SetMinArea(rect); 00842 00843 m_CurPos = 0; 00844 m_animatedImage = true; 00845 m_Initiator = m_EnableInitiator; 00846 SetRedraw(); 00847 } 00848 00849 void MythUIImage::SetAnimationFrames(AnimationFrames frames) 00850 { 00851 QVector<int> delays; 00852 QVector<MythImage *> images; 00853 00854 AnimationFrames::iterator it; 00855 00856 for (it = frames.begin(); it != frames.end(); ++it) 00857 { 00858 images.append((*it).first); 00859 delays.append((*it).second); 00860 } 00861 00862 if (images.size()) 00863 { 00864 SetImages(&images); 00865 00866 if (m_Delay < 0 && delays.size()) 00867 SetDelays(delays); 00868 } 00869 else 00870 Reset(); 00871 } 00872 00876 void MythUIImage::ForceSize(const QSize &size) 00877 { 00878 if (m_imageProperties.forceSize == size) 00879 return; 00880 00881 d->m_UpdateLock.lockForWrite(); 00882 m_imageProperties.forceSize = size; 00883 d->m_UpdateLock.unlock(); 00884 00885 if (size.isEmpty()) 00886 return; 00887 00888 SetSize(m_imageProperties.forceSize); 00889 00890 Load(); 00891 return; 00892 } 00893 00897 void MythUIImage::SetSize(int width, int height) 00898 { 00899 SetSize(QSize(width, height)); 00900 } 00901 00905 void MythUIImage::SetSize(const QSize &size) 00906 { 00907 QWriteLocker updateLocker(&d->m_UpdateLock); 00908 MythUIType::SetSize(size); 00909 m_NeedLoad = true; 00910 } 00911 00916 void MythUIImage::SetCropRect(int x, int y, int width, int height) 00917 { 00918 SetCropRect(MythRect(x, y, width, height)); 00919 } 00920 00925 void MythUIImage::SetCropRect(const MythRect &rect) 00926 { 00927 QWriteLocker updateLocker(&d->m_UpdateLock); 00928 m_imageProperties.cropRect = rect; 00929 SetRedraw(); 00930 } 00931 00935 bool MythUIImage::Load(bool allowLoadInBackground, bool forceStat) 00936 { 00937 d->m_UpdateLock.lockForRead(); 00938 00939 m_Initiator = m_EnableInitiator; 00940 00941 QString bFilename = m_imageProperties.filename; 00942 bFilename.detach(); 00943 00944 d->m_UpdateLock.unlock(); 00945 00946 QString filename = bFilename; 00947 00948 if (bFilename.isEmpty()) 00949 { 00950 Clear(); 00951 SetMinArea(MythRect()); 00952 SetRedraw(); 00953 00954 return false; 00955 } 00956 00957 Clear(); 00958 00959 bool bPreferLoadInBackground = 00960 ((filename.startsWith("myth://")) || 00961 (filename.startsWith("http://")) || 00962 (filename.startsWith("https://")) || 00963 (filename.startsWith("ftp://"))); 00964 00965 if (getenv("DISABLETHREADEDMYTHUIIMAGE")) 00966 allowLoadInBackground = false; 00967 00968 QString imagelabel; 00969 00970 int j = 0; 00971 00972 for (int i = m_LowNum; i <= m_HighNum && !m_animatedImage; i++) 00973 { 00974 if (!m_animatedImage && m_HighNum != m_LowNum && 00975 bFilename.contains("%1")) 00976 filename = bFilename.arg(i); 00977 00978 ImageProperties imProps = m_imageProperties; 00979 imProps.filename = filename; 00980 imagelabel = ImageLoader::GenImageLabel(imProps); 00981 00982 // Only load in the background if allowed and the image is 00983 // not already in our mem cache 00984 int cacheMode = kCacheCheckMemoryOnly; 00985 00986 if (forceStat) 00987 cacheMode |= (int)kCacheForceStat; 00988 00989 int cacheMode2 = kCacheNormal; 00990 00991 if (forceStat) 00992 cacheMode2 |= (int)kCacheForceStat; 00993 00994 if ((allowLoadInBackground) && 00995 ((bPreferLoadInBackground) || 00996 (!GetMythUI()->LoadCacheImage(filename, imagelabel, 00997 GetPainter(), 00998 static_cast<ImageCacheMode>(cacheMode))))) 00999 { 01000 SetMinArea(MythRect()); 01001 LOG(VB_GUI | VB_FILE, LOG_DEBUG, LOC + 01002 QString("Load(), spawning thread to load '%1'").arg(filename)); 01003 01004 m_runningThreads++; 01005 ImageLoadThread *bImgThread; 01006 bImgThread = new ImageLoadThread(this, GetPainter(), 01007 imProps, 01008 bFilename, i, 01009 static_cast<ImageCacheMode>(cacheMode2)); 01010 GetMythUI()->GetImageThreadPool()->start(bImgThread, "ImageLoad"); 01011 } 01012 else 01013 { 01014 // Perform a blocking load 01015 LOG(VB_GUI | VB_FILE, LOG_DEBUG, LOC + 01016 QString("Load(), loading '%1' in foreground").arg(filename)); 01017 bool aborted = false; 01018 01019 if (ImageLoader::SupportsAnimation(filename)) 01020 { 01021 AnimationFrames *myFrames; 01022 01023 myFrames = ImageLoader::LoadAnimatedImage(GetPainter(), imProps, 01024 static_cast<ImageCacheMode>(cacheMode2), 01025 this, aborted); 01026 01027 // TODO We might want to handle an abort here more gracefully 01028 if (aborted) 01029 LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading animated" 01030 "image %1 in foreground") 01031 .arg(filename)); 01032 01033 SetAnimationFrames(*myFrames); 01034 01035 delete myFrames; 01036 } 01037 else 01038 { 01039 MythImage *image = NULL; 01040 01041 image = ImageLoader::LoadImage(GetPainter(), 01042 imProps, 01043 static_cast<ImageCacheMode>(cacheMode2), 01044 this, aborted); 01045 01046 // TODO We might want to handle an abort here more gracefully 01047 if (aborted) 01048 LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading animated" 01049 "image %1 in foreground") 01050 .arg(filename)); 01051 01052 if (image) 01053 { 01054 if (m_imageProperties.forceSize.isNull()) 01055 SetSize(image->size()); 01056 01057 MythRect rect(GetFullArea()); 01058 rect.setSize(image->size()); 01059 SetMinArea(rect); 01060 01061 m_ImagesLock.lock(); 01062 m_Images[j] = image; 01063 m_ImagesLock.unlock(); 01064 01065 SetRedraw(); 01066 d->m_UpdateLock.lockForWrite(); 01067 m_LastDisplay = QTime::currentTime(); 01068 d->m_UpdateLock.unlock(); 01069 } 01070 else 01071 { 01072 Reset(); 01073 01074 m_ImagesLock.lock(); 01075 m_Images[j] = NULL; 01076 m_ImagesLock.unlock(); 01077 } 01078 } 01079 } 01080 01081 ++j; 01082 } 01083 01084 return true; 01085 } 01086 01090 void MythUIImage::Pulse(void) 01091 { 01092 QWriteLocker updateLocker(&d->m_UpdateLock); 01093 01094 int delay = -1; 01095 01096 if (m_Delays.contains(m_CurPos)) 01097 delay = m_Delays[m_CurPos]; 01098 else if (m_Delay > 0) 01099 delay = m_Delay; 01100 01101 if (delay > 0 && 01102 abs(m_LastDisplay.msecsTo(QTime::currentTime())) > delay) 01103 { 01104 m_ImagesLock.lock(); 01105 01106 if (m_animationCycle == kCycleStart) 01107 { 01108 ++m_CurPos; 01109 01110 if (m_CurPos >= (uint)m_Images.size()) 01111 m_CurPos = 0; 01112 } 01113 else if (m_animationCycle == kCycleReverse) 01114 { 01115 if ((m_CurPos + 1) >= (uint)m_Images.size()) 01116 { 01117 m_animationReverse = true; 01118 } 01119 else if (m_CurPos == 0) 01120 { 01121 m_animationReverse = false; 01122 } 01123 01124 if (m_animationReverse) 01125 --m_CurPos; 01126 else 01127 ++m_CurPos; 01128 } 01129 01130 m_ImagesLock.unlock(); 01131 01132 SetRedraw(); 01133 m_LastDisplay = QTime::currentTime(); 01134 } 01135 01136 MythUIType::Pulse(); 01137 } 01138 01142 void MythUIImage::DrawSelf(MythPainter *p, int xoffset, int yoffset, 01143 int alphaMod, QRect clipRect) 01144 { 01145 m_ImagesLock.lock(); 01146 01147 if (m_Images.size() > 0) 01148 { 01149 d->m_UpdateLock.lockForWrite(); 01150 01151 if (m_CurPos >= (uint)m_Images.size()) 01152 m_CurPos = 0; 01153 01154 if (!m_Images[m_CurPos]) 01155 { 01156 unsigned int origPos = m_CurPos; 01157 m_CurPos++; 01158 01159 while (!m_Images[m_CurPos] && m_CurPos != origPos) 01160 { 01161 m_CurPos++; 01162 01163 if (m_CurPos >= (uint)m_Images.size()) 01164 m_CurPos = 0; 01165 } 01166 } 01167 01168 QRect area = GetArea().toQRect(); 01169 area.translate(xoffset, yoffset); 01170 01171 int alpha = CalcAlpha(alphaMod); 01172 01173 MythImage *currentImage = m_Images[m_CurPos]; 01174 01175 if (currentImage) 01176 currentImage->UpRef(); 01177 01178 m_ImagesLock.unlock(); 01179 d->m_UpdateLock.unlock(); 01180 01181 if (!currentImage) 01182 return; 01183 01184 d->m_UpdateLock.lockForRead(); 01185 01186 QRect currentImageArea = currentImage->rect(); 01187 01188 if (!m_imageProperties.forceSize.isNull()) 01189 area.setSize(area.size().expandedTo(currentImage->size())); 01190 01191 // Centre image in available space 01192 int x = 0; 01193 int y = 0; 01194 01195 if (area.width() > currentImageArea.width()) 01196 x = (area.width() - currentImageArea.width()) / 2; 01197 01198 if (area.height() > currentImageArea.height()) 01199 y = (area.height() - currentImageArea.height()) / 2; 01200 01201 if (x > 0 || y > 0) 01202 area.translate(x, y); 01203 01204 QRect srcRect; 01205 m_imageProperties.cropRect.CalculateArea(GetFullArea()); 01206 01207 if (!m_imageProperties.cropRect.isEmpty()) 01208 srcRect = m_imageProperties.cropRect.toQRect(); 01209 else 01210 srcRect = currentImageArea; 01211 01212 p->DrawImage(area, currentImage, srcRect, alpha); 01213 currentImage->DownRef(); 01214 d->m_UpdateLock.unlock(); 01215 } 01216 else 01217 m_ImagesLock.unlock(); 01218 } 01219 01223 bool MythUIImage::ParseElement( 01224 const QString &filename, QDomElement &element, bool showWarnings) 01225 { 01226 QWriteLocker updateLocker(&d->m_UpdateLock); 01227 01228 if (element.tagName() == "filename") 01229 { 01230 m_OrigFilename = m_imageProperties.filename = getFirstText(element); 01231 01232 if (m_imageProperties.filename.endsWith('/')) 01233 { 01234 QDir imageDir(m_imageProperties.filename); 01235 01236 if (!imageDir.exists()) 01237 { 01238 QString themeDir = GetMythUI()->GetThemeDir() + '/'; 01239 imageDir = themeDir + m_imageProperties.filename; 01240 } 01241 01242 QStringList imageTypes; 01243 01244 QList< QByteArray > exts = QImageReader::supportedImageFormats(); 01245 QList< QByteArray >::Iterator it = exts.begin(); 01246 01247 for (; it != exts.end(); ++it) 01248 { 01249 imageTypes.append(QString("*.").append(*it)); 01250 } 01251 01252 imageDir.setNameFilters(imageTypes); 01253 01254 QStringList imageList = imageDir.entryList(); 01255 QString randFile; 01256 01257 if (imageList.size()) 01258 randFile = QString("%1%2").arg(m_imageProperties.filename) 01259 .arg(imageList.takeAt(random() % imageList.size())); 01260 01261 m_OrigFilename = m_imageProperties.filename = randFile; 01262 } 01263 } 01264 else if (element.tagName() == "filepattern") 01265 { 01266 m_OrigFilename = m_imageProperties.filename = getFirstText(element); 01267 QString tmp = element.attribute("low"); 01268 01269 if (!tmp.isEmpty()) 01270 m_LowNum = tmp.toInt(); 01271 01272 tmp = element.attribute("high"); 01273 01274 if (!tmp.isEmpty()) 01275 m_HighNum = tmp.toInt(); 01276 01277 tmp = element.attribute("cycle", "start"); 01278 01279 if (tmp == "reverse") 01280 m_animationCycle = kCycleReverse; 01281 } 01282 else if (element.tagName() == "area") 01283 { 01284 SetArea(parseRect(element)); 01285 m_imageProperties.forceSize = m_Area.size(); 01286 } 01287 else if (element.tagName() == "preserveaspect") 01288 m_imageProperties.preserveAspect = parseBool(element); 01289 else if (element.tagName() == "crop") 01290 m_imageProperties.cropRect = parseRect(element); 01291 else if (element.tagName() == "delay") 01292 { 01293 QString value = getFirstText(element); 01294 01295 if (value.contains(",")) 01296 { 01297 QVector<int> delays; 01298 QStringList tokens = value.split(","); 01299 QStringList::iterator it = tokens.begin(); 01300 01301 for (; it != tokens.end(); ++it) 01302 { 01303 if ((*it).isEmpty()) 01304 { 01305 if (delays.size()) 01306 delays.append(delays[delays.size()-1]); 01307 else 01308 delays.append(0); // Default 0ms delay before first image 01309 } 01310 else 01311 { 01312 delays.append((*it).toInt()); 01313 } 01314 } 01315 01316 if (delays.size()) 01317 { 01318 m_Delay = delays[0]; 01319 SetDelays(delays); 01320 } 01321 } 01322 else 01323 { 01324 m_Delay = value.toInt(); 01325 } 01326 } 01327 else if (element.tagName() == "reflection") 01328 { 01329 m_imageProperties.isReflected = true; 01330 QString tmp = element.attribute("axis"); 01331 01332 if (!tmp.isEmpty()) 01333 { 01334 if (tmp.toLower() == "horizontal") 01335 m_imageProperties.reflectAxis = ReflectHorizontal; 01336 else 01337 m_imageProperties.reflectAxis = ReflectVertical; 01338 } 01339 01340 tmp = element.attribute("shear"); 01341 01342 if (!tmp.isEmpty()) 01343 m_imageProperties.reflectShear = tmp.toInt(); 01344 01345 tmp = element.attribute("scale"); 01346 01347 if (!tmp.isEmpty()) 01348 m_imageProperties.reflectScale = tmp.toInt(); 01349 01350 tmp = element.attribute("length"); 01351 01352 if (!tmp.isEmpty()) 01353 m_imageProperties.reflectLength = tmp.toInt(); 01354 01355 tmp = element.attribute("spacing"); 01356 01357 if (!tmp.isEmpty()) 01358 m_imageProperties.reflectSpacing = tmp.toInt(); 01359 } 01360 else if (element.tagName() == "mask") 01361 { 01362 QString maskfile = getFirstText(element); 01363 01364 MythImage *newMaskImage = GetPainter()->GetFormatImage(); 01365 01366 if (newMaskImage->Load(maskfile)) 01367 { 01368 m_imageProperties.SetMaskImage(newMaskImage); 01369 } 01370 else 01371 { 01372 newMaskImage->DownRef(); 01373 m_imageProperties.SetMaskImage(NULL); 01374 } 01375 } 01376 else if (element.tagName() == "grayscale" || 01377 element.tagName() == "greyscale") 01378 { 01379 m_imageProperties.isGreyscale = parseBool(element); 01380 } 01381 else 01382 { 01383 return MythUIType::ParseElement(filename, element, showWarnings); 01384 } 01385 01386 m_NeedLoad = true; 01387 01388 if (m_Parent && m_Parent->IsDeferredLoading(true)) 01389 m_NeedLoad = false; 01390 01391 return true; 01392 } 01393 01397 void MythUIImage::CopyFrom(MythUIType *base) 01398 { 01399 d->m_UpdateLock.lockForWrite(); 01400 MythUIImage *im = dynamic_cast<MythUIImage *>(base); 01401 01402 if (!im) 01403 { 01404 LOG(VB_GENERAL, LOG_ERR, 01405 QString("'%1' (%2) ERROR, bad parsing '%3' (%4)") 01406 .arg(objectName()).arg(GetXMLLocation()) 01407 .arg(base->objectName()).arg(base->GetXMLLocation())); 01408 d->m_UpdateLock.unlock(); 01409 return; 01410 } 01411 01412 m_OrigFilename = im->m_OrigFilename; 01413 01414 m_Delay = im->m_Delay; 01415 m_LowNum = im->m_LowNum; 01416 m_HighNum = im->m_HighNum; 01417 01418 m_LastDisplay = QTime::currentTime(); 01419 m_CurPos = 0; 01420 01421 m_imageProperties = im->m_imageProperties; 01422 01423 m_animationCycle = im->m_animationCycle; 01424 m_animatedImage = im->m_animatedImage; 01425 01426 MythUIType::CopyFrom(base); 01427 01428 m_NeedLoad = im->m_NeedLoad; 01429 01430 d->m_UpdateLock.unlock(); 01431 01432 d->m_UpdateLock.lockForRead(); 01433 01434 if (m_NeedLoad) 01435 { 01436 d->m_UpdateLock.unlock(); 01437 Load(); 01438 } 01439 else 01440 d->m_UpdateLock.unlock(); 01441 } 01442 01446 void MythUIImage::CreateCopy(MythUIType *parent) 01447 { 01448 QReadLocker updateLocker(&d->m_UpdateLock); 01449 MythUIImage *im = new MythUIImage(parent, objectName()); 01450 im->CopyFrom(this); 01451 } 01452 01456 void MythUIImage::Finalize(void) 01457 { 01458 d->m_UpdateLock.lockForRead(); 01459 01460 if (m_NeedLoad) 01461 { 01462 d->m_UpdateLock.unlock(); 01463 Load(); 01464 } 01465 else 01466 d->m_UpdateLock.unlock(); 01467 01468 MythUIType::Finalize(); 01469 } 01470 01474 void MythUIImage::LoadNow(void) 01475 { 01476 d->m_UpdateLock.lockForWrite(); 01477 01478 if (m_NeedLoad) 01479 { 01480 d->m_UpdateLock.unlock(); 01481 return; 01482 } 01483 01484 m_NeedLoad = true; 01485 d->m_UpdateLock.unlock(); 01486 01487 Load(false); 01488 01489 MythUIType::LoadNow(); 01490 } 01491 01495 void MythUIImage::customEvent(QEvent *event) 01496 { 01497 if (event->type() == ImageLoadEvent::kEventType) 01498 { 01499 MythImage *image = NULL; 01500 AnimationFrames *animationFrames = NULL; 01501 int number = 0; 01502 QString filename; 01503 bool aborted; 01504 01505 ImageLoadEvent *le = static_cast<ImageLoadEvent *>(event); 01506 01507 if (le->GetParent() != this) 01508 return; 01509 01510 image = le->GetImage(); 01511 number = le->GetNumber(); 01512 filename = le->GetFilename(); 01513 animationFrames = le->GetAnimationFrames(); 01514 aborted = le->GetAbortState(); 01515 01516 m_runningThreads--; 01517 01518 d->m_UpdateLock.lockForRead(); 01519 01520 // 1) We aborted loading the image for some reason (e.g. two requests 01521 // for same image) 01522 // 2) Filename changed since we started this image, so abort to avoid 01523 // rendering two different images in quick succession which causes 01524 // unsightly flickering 01525 if (aborted || 01526 (le->GetBasefile() != m_imageProperties.filename)) 01527 { 01528 d->m_UpdateLock.unlock(); 01529 01530 if (aborted) 01531 LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading image %1") 01532 .arg(filename)); 01533 01534 if (image) 01535 image->DownRef(); 01536 01537 if (animationFrames) 01538 { 01539 AnimationFrames::iterator it; 01540 01541 for (it = animationFrames->begin(); it != animationFrames->end(); 01542 ++it) 01543 { 01544 MythImage *im = (*it).first; 01545 im->DownRef(); 01546 } 01547 01548 delete animationFrames; 01549 } 01550 01551 return; 01552 } 01553 01554 d->m_UpdateLock.unlock(); 01555 01556 if (animationFrames) 01557 { 01558 SetAnimationFrames(*animationFrames); 01559 01560 delete animationFrames; 01561 01562 return; 01563 } 01564 01565 if (image) 01566 { 01567 d->m_UpdateLock.lockForWrite(); 01568 01569 if (m_imageProperties.forceSize.isNull()) 01570 SetSize(image->size()); 01571 01572 MythRect rect(GetFullArea()); 01573 rect.setSize(image->size()); 01574 SetMinArea(rect); 01575 01576 d->m_UpdateLock.unlock(); 01577 01578 m_ImagesLock.lock(); 01579 01580 if (m_Images[number]) 01581 { 01582 // If we got to this point, it means this same MythUIImage 01583 // was told to reload the same image, so we use the newest 01584 // copy of the image. 01585 m_Images[number]->DownRef(); // delete the original 01586 } 01587 01588 m_Images[number] = image; 01589 m_ImagesLock.unlock(); 01590 01591 SetRedraw(); 01592 01593 d->m_UpdateLock.lockForWrite(); 01594 m_LastDisplay = QTime::currentTime(); 01595 d->m_UpdateLock.unlock(); 01596 01597 return; 01598 } 01599 01600 // No Images were loaded, so trigger Reset to default 01601 Reset(); 01602 } 01603 01604 01605 }
1.7.6.1