MythTV  0.26-pre
mythuiimage.cpp
Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends