Index: libs/libmythui/mythuiimage.cpp =================================================================== --- libs/libmythui/mythuiimage.cpp (revision 22173) +++ libs/libmythui/mythuiimage.cpp (working copy) @@ -1,20 +1,81 @@ #include "mythuiimage.h" +// C/C++ #include +// QT #include #include #include #include +#include +#include +#include +// Libmythdb #include "mythverbose.h" +// Mythui #include "mythpainter.h" #include "mythmainwindow.h" #include "mythuihelper.h" #include "mythscreentype.h" +class ImageLoadThread; + +const int kImageLoadEventType = 35112; + +/*! + * \class ImageLoadThread + */ +class ImageLoadEvent : public QEvent +{ + public: + ImageLoadEvent(ImageLoadThread *thread, MythImage *image, + const QString &filename, int number) + : QEvent((QEvent::Type)kImageLoadEventType), + m_thread(thread), m_image(image), m_filename(filename), + m_number(number) { } + + ImageLoadThread *GetThread() const { return m_thread; } + MythImage *GetImage() const { return m_image; } + const QString GetFilename() const { return m_filename; } + const int GetNumber() const { return m_number; } + + private: + ImageLoadThread *m_thread; + MythImage *m_image; + QString m_filename; + int m_number; +}; + +/*! +* \class ImageLoadThread +*/ +class ImageLoadThread : public QRunnable +{ + public: + ImageLoadThread(MythUIImage *parent, const QString &filename, + int number) + : m_parent(parent), m_filename(filename), m_number(number) { }; + + void run() + { + MythImage *image = m_parent->LoadImage(m_filename, m_number); + ImageLoadEvent *le = new ImageLoadEvent(this, image, m_filename, + m_number); + QCoreApplication::postEvent(m_parent, le); + } + + private: + MythUIImage *m_parent; + QString m_filename; + int m_number; +}; + +///////////////////////////////////////////////////////////////// + MythUIImage::MythUIImage(const QString &filepattern, int low, int high, int delayms, MythUIType *parent, const QString &name) @@ -26,6 +87,8 @@ m_Delay = delayms; + m_imageLoadThread = NULL; + Init(); } @@ -65,10 +128,12 @@ */ void MythUIImage::Clear(void) { + QMutexLocker locker(&m_ImagesLock); while (!m_Images.isEmpty()) { - m_Images.back()->DownRef(); - m_Images.pop_back(); + QHash::iterator it = m_Images.begin(); + (*it)->DownRef(); + m_Images.remove(it.key()); } } @@ -192,7 +257,10 @@ if (m_ForceSize.isNull()) SetSize(img->size()); - m_Images.push_back(img); + m_ImagesLock.lock(); + m_Images[0] = img; + m_ImagesLock.unlock(); + m_CurPos = 0; SetRedraw(); } @@ -237,7 +305,9 @@ if (m_isGreyscale && !im->isGrayscale()) im->ToGreyscale(); - m_Images.push_back(im); + m_ImagesLock.lock(); + m_Images[m_Images.size()] = im; + m_ImagesLock.unlock(); aSize = aSize.expandedTo(im->size()); } @@ -263,8 +333,9 @@ return; QSize aSize = m_Area.size(); + QMutexLocker locker(&m_ImagesLock); - QVector::iterator it; + QHash::iterator it; for (it = m_Images.begin(); it != m_Images.end(); ++it) { MythImage *im = (*it); @@ -319,7 +390,7 @@ * \brief Generates a unique identifying string for this image which is used * as a key in the image cache. */ -QString MythUIImage::GenImageLabel(const QString &filename, int w, int h) +QString MythUIImage::GenImageLabel(const QString &filename, int w, int h) const { QString imagelabel; QString s_Attrib; @@ -347,165 +418,212 @@ * \brief Generates a unique identifying string for this image which is used * as a key in the image cache. */ -QString MythUIImage::GenImageLabel(int w, int h) +QString MythUIImage::GenImageLabel(int w, int h) const { return GenImageLabel(m_Filename, w, h); } /** - * \brief Load the image(s) + * \brief Load the image(s), wraps LoadImage() */ -bool MythUIImage::Load(void) +bool MythUIImage::Load(bool allowLoadInBackground) { Clear(); SetRedraw(); -// if (!IsVisible(true)) -// return false; - if (m_Filename.isEmpty() && (!m_gradient)) return false; - QSize newSize; +// if (!IsVisible(true)) +// return false; - VERBOSE(VB_FILE, QString("MythUIImage::Load (%1) Object %2").arg(m_Filename).arg(objectName())); + int w = -1; + int h = -1; + if (!m_ForceSize.isNull()) + { + if (m_ForceSize.width() != -1) + w = m_ForceSize.width(); + + if (m_ForceSize.height() != -1) + h = m_ForceSize.height(); + } + + QString imagelabel; + QString filename = m_Filename; + for (int i = m_LowNum; i <= m_HighNum; i++) { - MythImage *image = NULL; - bool bNeedLoad = false; + if (m_HighNum >= 1) + filename = m_Filename.arg(i); - if (m_gradient) + imagelabel = GenImageLabel(filename, w, h); + + // Only load in the background if allowed and the image is + // not already in our mem cache + if ((allowLoadInBackground) && + (!GetMythUI()->LoadCacheImage(filename, imagelabel, false))) { - QSize gradsize; - if (!m_Area.isEmpty()) - gradsize = m_Area.size(); - else - gradsize = QSize(10, 10); - - image = MythImage::Gradient(gradsize, m_gradientStart, - m_gradientEnd, m_gradientAlpha, - m_gradientDirection); - image->UpRef(); + m_imageLoadThread = new ImageLoadThread(this, m_Filename, i); + GetMythUI()->GetImageLoaderThreadPool()->start(m_imageLoadThread); } else - bNeedLoad = true; + { + // Perform a blocking load + MythImage *image = LoadImage(filename, i); + if (image) + { + if (m_ForceSize.isNull()) + SetSize(image->size()); - int w = -1; - int h = -1; + m_ImagesLock.lock(); + m_Images[i] = image; + m_ImagesLock.unlock(); - bool bForceResize = false; - bool bFoundInCache = false; + SetRedraw(); + m_LastDisplay = QTime::currentTime(); + } + } + } - QString imagelabel; + return true; +} - if (!m_ForceSize.isNull()) - { - if (m_ForceSize.width() != -1) - w = m_ForceSize.width(); +/** +* \brief Load an image +*/ +MythImage *MythUIImage::LoadImage(const QString &imFile, int imageNumber) const +{ + QString filename = imFile; - if (m_ForceSize.height() != -1) - h = m_ForceSize.height(); + VERBOSE(VB_FILE, QString("MythUIImage::LoadImage(%1) Object %2").arg(filename) + .arg(objectName())); - bForceResize = true; - } + MythImage *image = NULL; + bool bNeedLoad = false; - if (bNeedLoad) - { + if (m_gradient) + { + QSize gradsize; + if (!m_Area.isEmpty()) + gradsize = m_Area.size(); + else + gradsize = QSize(10, 10); - QString filename = m_Filename; - if (m_HighNum >= 1) - filename = QString(m_Filename).arg(i); + image = MythImage::Gradient(gradsize, m_gradientStart, + m_gradientEnd, m_gradientAlpha, + m_gradientDirection); + image->UpRef(); + } + else + bNeedLoad = true; - imagelabel = GenImageLabel(filename, w, h); - image = GetMythUI()->LoadCacheImage(filename, imagelabel); - if (image) - { - image->UpRef(); - VERBOSE(VB_FILE, QString("MythUIImage::Load found in cache :%1:").arg(imagelabel)); - if (m_isReflected) - image->setIsReflected(true); + bool bForceResize = false; + bool bFoundInCache = false; - bFoundInCache = true; - } - else - { - VERBOSE(VB_FILE, QString("MythUIImage::Load Not Found in " - "cache. Loading Directly :%1:") - .arg(filename)); - image = GetMythPainter()->GetFormatImage(); - image->UpRef(); - if (!image->Load(filename)) - { - VERBOSE(VB_FILE, QString("MythUIImage::Load Could not load :%1:").arg(filename)); - image->DownRef(); - SetRedraw(); - return false; - } - } - } - else - imagelabel = GenImageLabel(w,h); + QString imagelabel; - if (!bFoundInCache) - { - if (bForceResize) - image->Resize(QSize(w, h), m_preserveAspect); + int w = -1; + int h = -1; - if (m_isMasked) - { - QRect imageArea = image->rect(); - QRect maskArea = m_maskImage->rect(); + if (!m_ForceSize.isNull()) + { + if (m_ForceSize.width() != -1) + w = m_ForceSize.width(); - // Crop the mask to the image - int x = 0; - int y = 0; - if (maskArea.width() > imageArea.width()) - x = (maskArea.width() - imageArea.width()) / 2; - if (maskArea.height() > imageArea.height()) - y = (maskArea.height() - imageArea.height()) / 2; + if (m_ForceSize.height() != -1) + h = m_ForceSize.height(); - if (x > 0 || y > 0) - imageArea.translate(x,y); - QImage mask = m_maskImage->copy(imageArea); - image->setAlphaChannel(mask.alphaChannel()); - } + bForceResize = true; + } - if (m_isReflected) - image->Reflect(m_reflectAxis, m_reflectShear, m_reflectScale, - m_reflectLength, m_reflectSpacing); + if (bNeedLoad) + { + if (imageNumber >= 1) + filename = QString(filename).arg(imageNumber); - if (m_isGreyscale) - image->ToGreyscale(); + imagelabel = GenImageLabel(filename, w, h); - // Save scaled copy to cache - if (bNeedLoad) - GetMythUI()->CacheImage(imagelabel, image); - } - - if (!bForceResize) + image = GetMythUI()->LoadCacheImage(filename, imagelabel); + if (image) { - newSize = newSize.expandedTo(image->size()); - SetSize(newSize); - } + image->UpRef(); + VERBOSE(VB_FILE, QString("MythUIImage::LoadImage found in cache :%1:").arg(imagelabel)); + if (m_isReflected) + image->setIsReflected(true); - if (image->isNull()) - { - VERBOSE(VB_FILE, QString("MythUIImage::Load Image is NULL :%1:").arg(m_Filename)); - image->DownRef(); + bFoundInCache = true; } else { - image->SetChanged(); - m_Images.push_back(image); + VERBOSE(VB_FILE, QString("MythUIImage::LoadImage Not Found in " + "cache. Loading Directly :%1:") + .arg(filename)); + image = GetMythPainter()->GetFormatImage(); + image->UpRef(); + if (!image->Load(filename)) + { + VERBOSE(VB_FILE, QString("MythUIImage::LoadImage Could not load " + ":%1:").arg(filename)); + image->DownRef(); + return NULL; + } } } + else + imagelabel = GenImageLabel(w,h); - m_LastDisplay = QTime::currentTime(); + if (!bFoundInCache) + { + if (bForceResize) + image->Resize(QSize(w, h), m_preserveAspect); - return true; + if (m_isMasked) + { + QRect imageArea = image->rect(); + QRect maskArea = m_maskImage->rect(); + + // Crop the mask to the image + int x = 0; + int y = 0; + if (maskArea.width() > imageArea.width()) + x = (maskArea.width() - imageArea.width()) / 2; + if (maskArea.height() > imageArea.height()) + y = (maskArea.height() - imageArea.height()) / 2; + + if (x > 0 || y > 0) + imageArea.translate(x,y); + QImage mask = m_maskImage->copy(imageArea); + image->setAlphaChannel(mask.alphaChannel()); + } + + if (m_isReflected) + image->Reflect(m_reflectAxis, m_reflectShear, m_reflectScale, + m_reflectLength, m_reflectSpacing); + + if (m_isGreyscale) + image->ToGreyscale(); + + // Save scaled copy to cache + if (bNeedLoad) + GetMythUI()->CacheImage(imagelabel, image); + } + + if (image->isNull()) + { + VERBOSE(VB_FILE, QString("MythUIImage::LoadImage Image is NULL :%1:") + .arg(filename)); + image->DownRef(); + return NULL; + } + else + { + image->SetChanged(); + } + + return image; } /** @@ -516,9 +634,15 @@ if (m_Delay > 0 && abs(m_LastDisplay.msecsTo(QTime::currentTime())) > m_Delay) { - m_CurPos++; - if (m_CurPos >= (uint)m_Images.size()) - m_CurPos = 0; + m_ImagesLock.lock(); + unsigned int origPos = m_CurPos; + do + { + m_CurPos++; + if (m_CurPos >= (uint)m_Images.size()) + m_CurPos = 0; + } while (!m_Images.contains(m_CurPos) && m_CurPos != origPos); + m_ImagesLock.unlock(); SetRedraw(); m_LastDisplay = QTime::currentTime(); @@ -533,10 +657,22 @@ void MythUIImage::DrawSelf(MythPainter *p, int xoffset, int yoffset, int alphaMod, QRect clipRect) { + m_ImagesLock.lock(); if (m_Images.size() > 0) { if (m_CurPos > (uint)m_Images.size()) m_CurPos = 0; + if (!m_Images.contains(m_CurPos)) + { + unsigned int origPos = m_CurPos; + m_CurPos++; + while (!m_Images.contains(m_CurPos) && m_CurPos != origPos) + { + m_CurPos++; + if (m_CurPos >= (uint)m_Images.size()) + m_CurPos = 0; + } + } QRect area = m_Area.toQRect(); area.translate(xoffset, yoffset); @@ -544,6 +680,7 @@ int alpha = CalcAlpha(alphaMod); MythImage *currentImage = m_Images[m_CurPos]; + m_ImagesLock.unlock(); QRect currentImageArea = currentImage->rect(); if (!m_ForceSize.isNull()) @@ -569,6 +706,8 @@ p->DrawImage(area, currentImage, srcRect, alpha); } + else + m_ImagesLock.unlock(); } /** @@ -784,8 +923,65 @@ return; m_NeedLoad = true; - Load(); + Load(false); MythUIType::LoadNow(); } +/** +* \copydoc MythUIType::customEvent() +*/ +void MythUIImage::customEvent(QEvent *event) +{ + if (event->type() == kImageLoadEventType) + { + ImageLoadEvent *le = dynamic_cast(event); + + ImageLoadThread *thread = le->GetThread(); + + if (thread && m_imageLoadThread && thread != m_imageLoadThread) + return; + + MythImage *image = le->GetImage(); + + if (image) + { + QString filename = le->GetFilename(); + + if (filename == m_Filename) + { + int number = le->GetNumber(); + + if (m_ForceSize.isNull()) + SetSize(image->size()); + + m_ImagesLock.lock(); + if (m_Images.contains(number)) + { + // There should be no image at this location, if + // we find one then something is wrong. + VERBOSE(VB_IMPORTANT, QString("DEBUG Image Thread: " + "Found image in m_Images position %1 where " + "there should be none. Filename: %2") + .arg(number).arg(filename)); + image->DownRef(); + } + else + { + m_Images[number] = image; + } + m_ImagesLock.unlock(); + + SetRedraw(); + m_LastDisplay = QTime::currentTime(); + } + else + { + // should never get here unless a non-thread sent the event + VERBOSE(VB_IMPORTANT, QString("Mis-Match File: %1 Not: %2") + .arg(filename).arg(m_Filename)); + image->DownRef(); + } + } + } +} Index: libs/libmythui/mythuihelper.h =================================================================== --- libs/libmythui/mythuihelper.h (revision 22173) +++ libs/libmythui/mythuihelper.h (working copy) @@ -5,6 +5,7 @@ #include #include #include +#include #include "mythexp.h" @@ -64,7 +65,8 @@ QPixmap *LoadScalePixmap(QString filename, bool fromcache = true); QImage *LoadScaleImage(QString filename, bool fromcache = true); - MythImage *LoadCacheImage(QString srcfile, QString label); + MythImage *LoadCacheImage(QString srcfile, QString label, + bool allowLoadFromDisk = true); void ThemeWidget(QWidget *widget); @@ -113,6 +115,8 @@ QString RemoveCurrentLocation(void); QString GetCurrentLocation(bool fullPath = false, bool mainStackOnly = true); + QThreadPool *GetImageLoaderThreadPool(void) {return imageLoaderThreadPool;} + protected: MythUIHelper(); ~MythUIHelper(); @@ -130,6 +134,8 @@ QMutex m_locationLock; QStringList m_currentLocation; + + QThreadPool *imageLoaderThreadPool; }; MPUBLIC MythUIHelper *GetMythUI(); Index: libs/libmythui/mythuihelper.cpp =================================================================== --- libs/libmythui/mythuihelper.cpp (revision 22173) +++ libs/libmythui/mythuihelper.cpp (working copy) @@ -325,6 +325,8 @@ : m_cacheSize(0) { d = new MythUIHelperPrivate(this); + + imageLoaderThreadPool = new QThreadPool(); } MythUIHelper::~MythUIHelper() @@ -1216,7 +1218,8 @@ return ret; } -MythImage *MythUIHelper::LoadCacheImage(QString srcfile, QString label) +MythImage *MythUIHelper::LoadCacheImage(QString srcfile, QString label, + bool allowLoadFromDisk) { //VERBOSE(VB_GENERAL, QString("LoadCacheImage %1:%2").arg(srcfile).arg(label)); @@ -1234,7 +1237,7 @@ { // Check Memory Cache ret = GetImageFromCache(label); - if (!ret) + if (!ret && allowLoadFromDisk) { // Load file from disk cache to memory cache ret = GetMythPainter()->GetFormatImage(); Index: libs/libmythui/mythuiimage.h =================================================================== --- libs/libmythui/mythuiimage.h (revision 22173) +++ libs/libmythui/mythuiimage.h (working copy) @@ -2,11 +2,14 @@ #define MYTHUI_IMAGE_H_ #include +#include +#include #include "mythuitype.h" #include "mythimage.h" class MythScreenType; +class ImageLoadThread; /** * \class MythUIImage @@ -43,7 +46,7 @@ void SetDelay(int delayms); void Reset(void); - bool Load(void); + bool Load(bool allowLoadInBackground = true); bool IsGradient(void) const { return m_gradient; } @@ -57,6 +60,8 @@ void Init(void); void Clear(void); + MythImage* LoadImage(const QString &imFile, int imageNumber = 0) const; + void customEvent(QEvent *event); virtual bool ParseElement(QDomElement &element); virtual void CopyFrom(MythUIType *base); @@ -70,13 +75,14 @@ void SetCropRect(int x, int y, int width, int height); void SetCropRect(const MythRect &rect); - QString GenImageLabel(const QString &filename, int w, int h); - QString GenImageLabel(int w, int h); + QString GenImageLabel(const QString &filename, int w, int h) const; + QString GenImageLabel(int w, int h) const; QString m_Filename; QString m_OrigFilename; - QVector m_Images; + QHash m_Images; + QMutex m_ImagesLock; MythRect m_cropRect; QSize m_ForceSize; @@ -110,10 +116,13 @@ bool m_isGreyscale; + ImageLoadThread *m_imageLoadThread; + friend class MythThemeBase; friend class MythUIButtonListItem; friend class MythUIProgressBar; friend class MythUITextEdit; + friend class ImageLoadThread; }; #endif