MythImage *MythUIHelper::LoadCacheImage(QString srcfile, QString label, MythPainter *painter, ImageCacheMode cacheMode) { LOG(VB_GUI | VB_FILE, LOG_INFO, LOC + QString("LoadCacheImage(%1,%2)").arg(srcfile).arg(label)); if (srcfile.isEmpty() || label.isEmpty()) return NULL; if (!(kCacheForceStat & cacheMode)) { // Some screens include certain images dozens or even hundreds of // times. Even if the image is in the cache, there is still a // stat system call on the original file to see if it has changed. // This code relaxes the original-file check so that the check // isn't repeated if it was already done within kImageCacheTimeout // seconds. // This only applies to the MEMORY cache const uint kImageCacheTimeout = 60; uint now = MythDate::current().toTime_t(); QMutexLocker locker(d->m_cacheLock); if (d->imageCache.contains(label) && d->CacheTrack[label] + kImageCacheTimeout > now) { d->imageCache[label]->IncrRef(); return d->imageCache[label]; } } MythImage *ret = NULL; // Check Memory Cache ret = GetImageFromCache(label); // If the image is in the memory or we are not ignoring the disk cache // then proceed to check whether the source file is newer than our cached // copy if (ret || !(cacheMode & kCacheIgnoreDisk)) { // Create url to image in disk cache QString cachefilepath = GetThemeCacheDir() + '/' + label; QFileInfo cacheFileInfo(cachefilepath); // If the file isn't in the disk cache, then we don't want to bother // checking the last modified times of the original if (!cacheFileInfo.exists()) return NULL; // Now compare the time on the source versus our cached copy QDateTime srcLastModified; // For internet images this involves querying the headers of the remote // image. This is slow even without redownloading the whole image if ((srcfile.startsWith("http://")) || (srcfile.startsWith("https://")) || (srcfile.startsWith("ftp://"))) { // If the image is in the memory cache then skip the last modified // check, since memory cached images are loaded in the foreground // this can cause an intolerable delay. The images won't stay in // the cache forever and so eventually they will be checked. if (ret) srcLastModified = cacheFileInfo.lastModified(); else { srcLastModified = GetMythDownloadManager()->GetLastModified(srcfile); } } else if (srcfile.startsWith("myth://")) srcLastModified = RemoteFile::LastModified(srcfile); else { if (!FindThemeFile(srcfile)) return NULL; QFileInfo original(srcfile); if (original.exists()) srcLastModified = original.lastModified(); } // Now compare the timestamps, if the cached image is newer than the // source image we can use it, otherwise we want to remove it from the // cache if (cacheFileInfo.lastModified() >= srcLastModified) { // If we haven't already loaded the image from the memory cache // and we're not ignoring the disk cache, then it's time to load // it from there instead if (!ret && (cacheMode == kCacheNormal)) { if (painter) ret = painter->GetFormatImage(); // Load file from disk cache to memory cache if (ret->Load(cachefilepath)) { // Add to ram cache, and skip saving to disk since that is // where we found this in the first place. CacheImage(label, ret, true); } else { LOG(VB_GUI | VB_FILE, LOG_WARNING, LOC + QString("LoadCacheImage: Could not load :%1") .arg(cachefilepath)); ret->SetIsInCache(false); ret->DecrRef(); ret = NULL; } } } else { ret = NULL; // If file has changed on disk, then remove it from the memory // and disk cache RemoveFromCacheByURL(label); } } return ret; }
static MythImage *LoadImage(MythPainter *painter, // Must be a copy for thread safety ImageProperties imProps, ImageCacheMode cacheMode, // Included only to check address, could be // replaced by generating a unique value for // each MythUIImage object? const MythUIImage *parent, bool &aborted, MythImageReader *imageReader = NULL) { QString cacheKey = GenImageLabel(imProps); if (!PreLoad(cacheKey, parent)) { aborted = true; return NULL; } QString filename = imProps.filename; MythImage *image = NULL; bool bResize = false; bool bFoundInCache = false; int w = -1; int h = -1; if (!imProps.forceSize.isNull()) { if (imProps.forceSize.width() != -1) w = imProps.forceSize.width(); if (imProps.forceSize.height() != -1) h = imProps.forceSize.height(); bResize = true; } if (!imageReader) { image = GetMythUI()->LoadCacheImage(filename, cacheKey, painter, cacheMode); } if (image) { if (VERBOSE_LEVEL_CHECK(VB_GUI | VB_FILE, LOG_INFO)) { image->IncrRef(); int cnt = image->DecrRef(); LOG(VB_GUI | VB_FILE, LOG_INFO, QString("ImageLoader::LoadImage(%1) Found in cache, " "RefCount = %2") .arg(cacheKey).arg(cnt)); } if (imProps.isReflected) image->setIsReflected(true); if (imProps.isOriented) image->setIsOriented(true); bFoundInCache = true; } else { LOG(VB_GUI | VB_FILE, LOG_INFO, QString("ImageLoader::LoadImage(%1) NOT Found in cache. " "Loading Directly").arg(cacheKey)); image = painter->GetFormatImage(); bool ok = false; if (imageReader) ok = image->Load(imageReader); else ok = image->Load(filename); if (!ok) { image->DecrRef(); image = NULL; } } if (image && image->isNull()) { LOG(VB_GUI | VB_FILE, LOG_INFO, QString("ImageLoader::LoadImage(%1) Image is NULL") .arg(filename)); image->DecrRef(); image = NULL; } if (image && !bFoundInCache) { if (imProps.isReflected) image->Reflect(imProps.reflectAxis, imProps.reflectShear, imProps.reflectScale, imProps.reflectLength, imProps.reflectSpacing); if (imProps.isGreyscale) image->ToGreyscale(); if (imProps.isOriented) image->Orientation(imProps.orientation); // Even if an explicit size wasn't defined this image may still need // to be scaled because of a difference between the theme resolution // and the screen resolution. We want to avoid scaling twice. if (!bResize && imProps.isThemeImage) { float wmult; // Width multipler float hmult; // Height multipler GetMythUI()->GetScreenSettings(wmult, hmult); if (wmult != 1.0f || hmult != 1.0f) { w = image->size().width() * wmult; h = image->size().height() * hmult; bResize = true; } } if (bResize) image->Resize(QSize(w, h), imProps.preserveAspect); if (imProps.isMasked) { QRect imageArea = image->rect(); QRect maskArea = imProps.GetMaskImageRect(); // 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 = imProps.GetMaskImageSubset(imageArea); image->setAlphaChannel(mask.alphaChannel()); } if (!imageReader) GetMythUI()->CacheImage(cacheKey, image); } if (image) image->SetChanged(); PostLoad(cacheKey); return image; }
/** * \brief Load an image */ MythImage *MythUIImage::LoadImage( MythImageReader &imageReader, const QString &imFile, QSize bForceSize, int cacheMode) { QString filename = imFile; m_loadingImagesLock.lock(); // Check to see if the image is being loaded by us in another thread if ((m_loadingImages.contains(filename)) && (m_loadingImages[filename] == this)) { VERBOSE(VB_GUI|VB_FILE|VB_EXTRA, LOC + QString( "MythUIImage::LoadImage(%1), this " "file is already being loaded by this same MythUIImage in " "another thread.").arg(filename)); m_loadingImagesLock.unlock(); return NULL; } // Check to see if the exact same image is being loaded anywhere else while (m_loadingImages.contains(filename)) m_loadingImagesCond.wait(&m_loadingImagesLock); m_loadingImages[filename] = this; m_loadingImagesLock.unlock(); VERBOSE(VB_GUI|VB_FILE, LOC + QString("LoadImage(%2) Object %3") .arg(filename).arg(objectName())); MythImage *image = NULL; bool bForceResize = false; bool bFoundInCache = false; QString imagelabel; int w = -1; int h = -1; if (!bForceSize.isNull()) { if (bForceSize.width() != -1) w = bForceSize.width(); if (bForceSize.height() != -1) h = bForceSize.height(); bForceResize = true; } imagelabel = GenImageLabel(filename, w, h); if (!imageReader.supportsAnimation()) { image = GetMythUI()->LoadCacheImage( filename, imagelabel, GetPainter(), (ImageCacheMode) cacheMode); } if (image) { image->UpRef(); VERBOSE(VB_GUI|VB_FILE, LOC + QString("LoadImage found in cache :%1: RefCount = %2") .arg(imagelabel).arg(image->RefCount())); if (m_isReflected) image->setIsReflected(true); bFoundInCache = true; } else { VERBOSE(VB_GUI|VB_FILE, LOC + QString("LoadImage Not Found in cache. " "Loading Directly :%1:").arg(filename)); image = GetPainter()->GetFormatImage(); image->UpRef(); bool ok = false; if (imageReader.supportsAnimation()) ok = image->Load(imageReader); else ok = image->Load(filename); if (!ok) { image->DownRef(); m_loadingImagesLock.lock(); m_loadingImages.remove(filename); m_loadingImagesCond.wakeAll(); m_loadingImagesLock.unlock(); return NULL; } } if (!bFoundInCache) { if (bForceResize) image->Resize(QSize(w, h), m_preserveAspect); 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); d->m_UpdateLock.lockForWrite(); QImage mask = m_maskImage->copy(imageArea); d->m_UpdateLock.unlock(); image->setAlphaChannel(mask.alphaChannel()); } if (m_isReflected) image->Reflect(m_reflectAxis, m_reflectShear, m_reflectScale, m_reflectLength, m_reflectSpacing); if (m_isGreyscale) image->ToGreyscale(); if (!imageReader.supportsAnimation()) GetMythUI()->CacheImage(imagelabel, image); } if (image->isNull()) { VERBOSE(VB_GUI|VB_FILE, LOC + QString("LoadImage Image is NULL :%1:") .arg(filename)); image->DownRef(); Reset(); m_loadingImagesLock.lock(); m_loadingImages.remove(filename); m_loadingImagesCond.wakeAll(); m_loadingImagesLock.unlock(); return NULL; } image->SetChanged(); m_loadingImagesLock.lock(); m_loadingImages.remove(filename); m_loadingImagesCond.wakeAll(); m_loadingImagesLock.unlock(); return image; }
/** * \copydoc MythUIType::ParseElement() */ bool MythUIImage::ParseElement( const QString &filename, QDomElement &element, bool showWarnings) { QWriteLocker updateLocker(&d->m_UpdateLock); if (element.tagName() == "filename") { m_imageProperties.isThemeImage = true; // This is an image distributed with the them m_OrigFilename = m_imageProperties.filename = getFirstText(element); if (m_imageProperties.filename.endsWith('/')) { m_showingRandomImage = true; m_imageDirectory = m_imageProperties.filename; FindRandomImage(); } } else if (element.tagName() == "filepattern") { m_imageProperties.isThemeImage = true; // This is an image distributed with the theme m_OrigFilename = m_imageProperties.filename = getFirstText(element); QString tmp = element.attribute("low"); if (!tmp.isEmpty()) m_LowNum = tmp.toInt(); tmp = element.attribute("high"); if (!tmp.isEmpty()) m_HighNum = tmp.toInt(); tmp = element.attribute("cycle", "start"); if (tmp == "reverse") m_animationCycle = kCycleReverse; } else if (element.tagName() == "area") { SetArea(parseRect(element)); m_imageProperties.forceSize = m_Area.size(); } else if (element.tagName() == "preserveaspect") m_imageProperties.preserveAspect = parseBool(element); else if (element.tagName() == "crop") m_imageProperties.cropRect = parseRect(element); else if (element.tagName() == "delay") { QString value = getFirstText(element); if (value.contains(",")) { QVector<int> delays; QStringList tokens = value.split(","); QStringList::iterator it = tokens.begin(); for (; it != tokens.end(); ++it) { if ((*it).isEmpty()) { if (delays.size()) delays.append(delays[delays.size()-1]); else delays.append(0); // Default 0ms delay before first image } else { delays.append((*it).toInt()); } } if (delays.size()) { m_Delay = delays[0]; SetDelays(delays); } } else { m_Delay = value.toInt(); } } else if (element.tagName() == "reflection") { m_imageProperties.isReflected = true; QString tmp = element.attribute("axis"); if (!tmp.isEmpty()) { if (tmp.toLower() == "horizontal") m_imageProperties.reflectAxis = ReflectHorizontal; else m_imageProperties.reflectAxis = ReflectVertical; } tmp = element.attribute("shear"); if (!tmp.isEmpty()) m_imageProperties.reflectShear = tmp.toInt(); tmp = element.attribute("scale"); if (!tmp.isEmpty()) m_imageProperties.reflectScale = tmp.toInt(); tmp = element.attribute("length"); if (!tmp.isEmpty()) m_imageProperties.reflectLength = tmp.toInt(); tmp = element.attribute("spacing"); if (!tmp.isEmpty()) m_imageProperties.reflectSpacing = tmp.toInt(); } else if (element.tagName() == "mask") { QString maskfile = getFirstText(element); MythImage *newMaskImage = GetPainter()->GetFormatImage(); if (newMaskImage->Load(maskfile)) { float wmult; // Width multipler float hmult; // Height multipler GetMythUI()->GetScreenSettings(wmult, hmult); if (wmult != 1.0f || hmult != 1.0f) { int width = newMaskImage->size().width() * wmult; int height = newMaskImage->size().height() * hmult; newMaskImage->Resize(QSize(width, height)); } m_imageProperties.SetMaskImage(newMaskImage); } else m_imageProperties.SetMaskImage(NULL); newMaskImage->DecrRef(); } else if (element.tagName() == "grayscale" || element.tagName() == "greyscale") { m_imageProperties.isGreyscale = parseBool(element); } else { return MythUIType::ParseElement(filename, element, showWarnings); } m_NeedLoad = true; if (m_Parent && m_Parent->IsDeferredLoading(true)) m_NeedLoad = false; return true; }
MythImage *MythUIHelper::LoadCacheImage(QString srcfile, QString label, MythPainter *painter, ImageCacheMode cacheMode) { LOG(VB_GUI | VB_FILE, LOG_INFO, LOC + QString("LoadCacheImage(%1,%2)").arg(srcfile).arg(label)); if (srcfile.isEmpty() || label.isEmpty()) return NULL; if (!(kCacheForceStat & cacheMode)) { // Some screens include certain images dozens or even hundreds of // times. Even if the image is in the cache, there is still a // stat system call on the original file to see if it has changed. // This code relaxes the original-file check so that the check // isn't repeated if it was already done within kImageCacheTimeout // seconds. const uint kImageCacheTimeout = 5; uint now = MythDate::current().toTime_t(); QMutexLocker locker(d->m_cacheLock); if (d->imageCache.contains(label) && d->CacheTrack[label] + kImageCacheTimeout > now) { d->imageCache[label]->IncrRef(); return d->imageCache[label]; } } QString cachefilepath = GetThemeCacheDir() + '/' + label; QFileInfo fi(cachefilepath); MythImage *ret = NULL; if (!!(cacheMode & kCacheIgnoreDisk) || fi.exists()) { // Now compare the time on the source versus our cached copy if (!(cacheMode & kCacheIgnoreDisk)) FindThemeFile(srcfile); QDateTime srcLastModified; QFileInfo original(srcfile); if ((srcfile.startsWith("http://")) || (srcfile.startsWith("https://")) || (srcfile.startsWith("ftp://"))) { srcLastModified = GetMythDownloadManager()->GetLastModified(srcfile); } else if (srcfile.startsWith("myth://")) srcLastModified = RemoteFile::LastModified(srcfile); else if (original.exists()) srcLastModified = original.lastModified(); if (!!(cacheMode & kCacheIgnoreDisk) || (fi.lastModified() > srcLastModified)) { // Check Memory Cache ret = GetImageFromCache(label); if (!ret && (cacheMode == kCacheNormal) && painter) { // Load file from disk cache to memory cache ret = painter->GetFormatImage(); if (!ret->Load(cachefilepath, false)) { LOG(VB_GUI | VB_FILE, LOG_WARNING, LOC + QString("LoadCacheImage: Could not load :%1") .arg(cachefilepath)); ret->SetIsInCache(false); ret->DecrRef(); ret = NULL; } else { // Add to ram cache, and skip saving to disk since that is // where we found this in the first place. CacheImage(label, ret, true); } } } else { // If file has changed on disk, then remove it from the memory // and disk cache RemoveFromCacheByURL(label); } } return ret; }