00001
00002 #include "mythuiimage.h"
00003
00004
00005 #include <cstdlib>
00006 #include <time.h>
00007
00008
00009 #include <stdint.h>
00010
00011
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
00022 #include "mythlogging.h"
00023
00024
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
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
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
00215 ImageProperties imProps,
00216 ImageCacheMode cacheMode,
00217
00218
00219
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
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
00352 ImageProperties imProps,
00353 ImageCacheMode cacheMode,
00354
00355
00356
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
00436 AnimationFrames *m_images;
00437
00438
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
00465
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
00570
00571
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
00983
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
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
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
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
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);
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
01521
01522
01523
01524
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
01583
01584
01585 m_Images[number]->DownRef();
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
01601 Reset();
01602 }
01603
01604
01605 }