/** * \copydoc MythUIType::customEvent() */ void MythUIImage::customEvent(QEvent *event) { if (event->type() == ImageLoadEvent::kEventType) { ImageLoadEvent *le = dynamic_cast<ImageLoadEvent*>(event); if (le->GetParent() != this) return; MythImage *image = le->GetImage(); if (!image) return; d->m_UpdateLock.lockForRead(); if (le->GetBasefile() != m_Filename) { d->m_UpdateLock.unlock(); #if 0 VERBOSE(VB_GUI|VB_FILE|VB_EXTRA, LOC + QString("customEvent(): Expecting '%2', got '%3'") .arg(m_Filename).arg(le->GetBasefile())); #endif image->DownRef(); return; } d->m_UpdateLock.unlock(); QString filename = le->GetFilename(); int number = le->GetNumber(); d->m_UpdateLock.lockForWrite(); if (m_ForceSize.isNull()) SetSize(image->size()); d->m_UpdateLock.unlock(); m_ImagesLock.lock(); if (m_Images[number]) { // If we got to this point, it means this same MythUIImage // was told to reload the same image, so we use the newest // copy of the image. m_Images[number]->DownRef(); // delete the original } m_Images[number] = image; m_ImagesLock.unlock(); SetRedraw(); d->m_UpdateLock.lockForWrite(); m_LastDisplay = QTime::currentTime(); d->m_UpdateLock.unlock(); } }
/** * \brief Assign a set of MythImages to the widget for animation. * Use is strongly discouraged, use SetFilepattern() instead. * */ void MythUIImage::SetImages(QVector<MythImage *> &images) { Clear(); QWriteLocker updateLocker(&d->m_UpdateLock); QSize aSize = GetArea().size(); QVector<MythImage *>::iterator it; for (it = images.begin(); it != images.end(); ++it) { MythImage *im = (*it); if (!im) { QMutexLocker locker(&m_ImagesLock); m_Images[m_Images.size()] = im; continue; } im->UpRef(); if (!m_ForceSize.isNull()) { int w = (m_ForceSize.width() <= 0) ? im->width() : m_ForceSize.width(); int h = (m_ForceSize.height() <= 0) ? im->height() : m_ForceSize.height(); im->Resize(QSize(w, h), m_preserveAspect); } if (m_isReflected && !im->IsReflected()) im->Reflect(m_reflectAxis, m_reflectShear, m_reflectScale, m_reflectLength, m_reflectSpacing); if (m_isGreyscale && !im->isGrayscale()) im->ToGreyscale(); m_ImagesLock.lock(); m_Images[m_Images.size()] = im; m_ImagesLock.unlock(); aSize = aSize.expandedTo(im->size()); } SetImageCount(1, m_Images.size()); if (m_ForceSize.isNull()) SetSize(aSize); m_CurPos = 0; SetRedraw(); }
/** * \brief Assign a set of MythImages to the widget for animation. * Use is strongly discouraged, use SetFilepattern() instead. * */ void MythUIImage::SetImages(QVector<MythImage *> *images) { Clear(); QWriteLocker updateLocker(&d->m_UpdateLock); QSize aSize = GetFullArea().size(); m_imageProperties.isThemeImage = false; QVector<MythImage *>::iterator it; for (it = images->begin(); it != images->end(); ++it) { MythImage *im = (*it); if (!im) { QMutexLocker locker(&m_ImagesLock); m_Images[m_Images.size()] = im; continue; } im->IncrRef(); QSize forceSize = m_imageProperties.forceSize; if (!forceSize.isNull()) { int w = (forceSize.width() <= 0) ? im->width() : forceSize.width(); int h = (forceSize.height() <= 0) ? im->height() : forceSize.height(); im->Resize(QSize(w, h), m_imageProperties.preserveAspect); } if (m_imageProperties.isReflected && !im->IsReflected()) im->Reflect(m_imageProperties.reflectAxis, m_imageProperties.reflectShear, m_imageProperties.reflectScale, m_imageProperties.reflectLength, m_imageProperties.reflectSpacing); if (m_imageProperties.isGreyscale && !im->isGrayscale()) im->ToGreyscale(); if (m_imageProperties.isOriented && !im->IsOriented() && (m_imageProperties.orientation >= 1 && m_imageProperties.orientation <= 8)) im->Orientation(m_imageProperties.orientation); m_ImagesLock.lock(); m_Images[m_Images.size()] = im; m_ImagesLock.unlock(); aSize = aSize.expandedTo(im->size()); } SetImageCount(1, m_Images.size()); if (m_imageProperties.forceSize.isNull()) SetSize(aSize); MythRect rect(GetFullArea()); rect.setSize(aSize); SetMinArea(rect); m_CurPos = 0; m_animatedImage = true; m_Initiator = m_EnableInitiator; SetRedraw(); }
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; }
/** * \copydoc MythUIType::customEvent() */ void MythUIImage::customEvent(QEvent *event) { if (event->type() == ImageLoadEvent::kEventType) { MythImage *image = NULL; AnimationFrames *animationFrames = NULL; int number = 0; QString filename; bool aborted; ImageLoadEvent *le = static_cast<ImageLoadEvent *>(event); if (le->GetParent() != this) return; image = le->GetImage(); number = le->GetNumber(); filename = le->GetFilename(); animationFrames = le->GetAnimationFrames(); aborted = le->GetAbortState(); m_runningThreads--; d->m_UpdateLock.lockForRead(); // 1) We aborted loading the image for some reason (e.g. two requests // for same image) // 2) Filename changed since we started this image, so abort to avoid // rendering two different images in quick succession which causes // unsightly flickering if (aborted || (le->GetBasefile() != m_imageProperties.filename)) { d->m_UpdateLock.unlock(); if (aborted) LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading image %1") .arg(filename)); if (image) image->DecrRef(); if (animationFrames) { AnimationFrames::iterator it; for (it = animationFrames->begin(); it != animationFrames->end(); ++it) { MythImage *im = (*it).first; if (im) im->DecrRef(); } delete animationFrames; } return; } d->m_UpdateLock.unlock(); if (animationFrames) { SetAnimationFrames(*animationFrames); delete animationFrames; return; } if (image) { // We don't clear until we have the new image ready to display to // avoid unsightly flashing. This isn't currently supported for // animations. if ((m_HighNum == m_LowNum) && !m_animatedImage) Clear(); d->m_UpdateLock.lockForWrite(); if (m_imageProperties.forceSize.isNull()) SetSize(image->size()); MythRect rect(GetFullArea()); rect.setSize(image->size()); SetMinArea(rect); d->m_UpdateLock.unlock(); m_ImagesLock.lock(); if (m_Images[number]) { // If we got to this point, it means this same MythUIImage // was told to reload the same image, so we use the newest // copy of the image. m_Images[number]->DecrRef(); // delete the original } m_Images[number] = image; m_ImagesLock.unlock(); SetRedraw(); d->m_UpdateLock.lockForWrite(); m_LastDisplay = QTime::currentTime(); d->m_UpdateLock.unlock(); return; } // No Images were loaded, so trigger Reset to default Reset(); } }
/** * \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; }
/** * \copydoc MythUIType::DrawSelf() */ void MythUIImage::DrawSelf(MythPainter *p, int xoffset, int yoffset, int alphaMod, QRect clipRect) { m_ImagesLock.lock(); if (m_Images.size() > 0) { d->m_UpdateLock.lockForWrite(); if (m_CurPos >= (uint)m_Images.size()) m_CurPos = 0; if (!m_Images[m_CurPos]) { unsigned int origPos = m_CurPos; m_CurPos++; while (!m_Images[m_CurPos] && m_CurPos != origPos) { m_CurPos++; if (m_CurPos >= (uint)m_Images.size()) m_CurPos = 0; } } QRect area = GetArea().toQRect(); area.translate(xoffset, yoffset); int alpha = CalcAlpha(alphaMod); MythImage *currentImage = m_Images[m_CurPos]; if (currentImage) currentImage->IncrRef(); m_ImagesLock.unlock(); d->m_UpdateLock.unlock(); if (!currentImage) return; d->m_UpdateLock.lockForRead(); QRect currentImageArea = currentImage->rect(); if (!m_imageProperties.forceSize.isNull()) area.setSize(area.size().expandedTo(currentImage->size())); // Centre image in available space, accounting for zoom int x = 0, y = 0; QRect visibleImage = m_Effects.GetExtent(currentImageArea.size()); if (area.width() > visibleImage.width()) x = area.width() / 2 + visibleImage.topLeft().x(); if (area.height() > visibleImage.height()) y = area.height() / 2 + visibleImage.topLeft().y(); if ((x > 0 || y > 0)) area.translate(x, y); QRect srcRect; m_imageProperties.cropRect.CalculateArea(GetFullArea()); if (!m_imageProperties.cropRect.isEmpty()) srcRect = m_imageProperties.cropRect.toQRect(); else srcRect = currentImageArea; p->DrawImage(area, currentImage, srcRect, alpha); currentImage->DecrRef(); d->m_UpdateLock.unlock(); } else m_ImagesLock.unlock(); }
/** * \brief Load the image(s), wraps ImageLoader::LoadImage() */ bool MythUIImage::Load(bool allowLoadInBackground, bool forceStat) { d->m_UpdateLock.lockForRead(); m_Initiator = m_EnableInitiator; QString bFilename = m_imageProperties.filename; bFilename.detach(); d->m_UpdateLock.unlock(); QString filename = bFilename; if (bFilename.isEmpty()) { Clear(); SetMinArea(MythRect()); SetRedraw(); return false; } if (getenv("DISABLETHREADEDMYTHUIIMAGE")) allowLoadInBackground = false; // Don't clear the widget before we need to, otherwise it causes // unsightly flashing. We exclude animations for now since that requires a // deeper fix bool isAnimation = (m_HighNum != m_LowNum) || m_animatedImage; if (isAnimation) Clear(); QString imagelabel; int j = 0; for (int i = m_LowNum; i <= m_HighNum && !m_animatedImage; i++) { if (!m_animatedImage && m_HighNum != m_LowNum && bFilename.contains("%1")) filename = bFilename.arg(i); ImageProperties imProps = m_imageProperties; imProps.filename = filename; imagelabel = ImageLoader::GenImageLabel(imProps); // Only load in the background if allowed and the image is // not already in our mem cache int cacheMode = kCacheIgnoreDisk; if (forceStat) cacheMode |= (int)kCacheForceStat; int cacheMode2 = kCacheNormal; if (forceStat) cacheMode2 |= (int)kCacheForceStat; bool do_background_load = false; if (allowLoadInBackground) { MythImage *img = GetMythUI()->LoadCacheImage( filename, imagelabel, GetPainter(), static_cast<ImageCacheMode>(cacheMode)); if (img) img->DecrRef(); else do_background_load = true; } if (do_background_load) { SetMinArea(MythRect()); LOG(VB_GUI | VB_FILE, LOG_DEBUG, LOC + QString("Load(), spawning thread to load '%1'").arg(filename)); m_runningThreads++; ImageLoadThread *bImgThread; bImgThread = new ImageLoadThread(this, GetPainter(), imProps, bFilename, i, static_cast<ImageCacheMode>(cacheMode2)); GetMythUI()->GetImageThreadPool()->start(bImgThread, "ImageLoad"); } else { if (!isAnimation && !GetMythUI()->IsImageInCache(imagelabel)) Clear(); // Perform a blocking load LOG(VB_GUI | VB_FILE, LOG_DEBUG, LOC + QString("Load(), loading '%1' in foreground").arg(filename)); bool aborted = false; if (ImageLoader::SupportsAnimation(filename)) { AnimationFrames *myFrames; myFrames = ImageLoader::LoadAnimatedImage(GetPainter(), imProps, static_cast<ImageCacheMode>(cacheMode2), this, aborted); // TODO We might want to handle an abort here more gracefully if (aborted) LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading animated" "image %1 in foreground") .arg(filename)); SetAnimationFrames(*myFrames); delete myFrames; } else { MythImage *image = NULL; image = ImageLoader::LoadImage(GetPainter(), imProps, static_cast<ImageCacheMode>(cacheMode2), this, aborted); // TODO We might want to handle an abort here more gracefully if (aborted) LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading animated" "image %1 in foreground") .arg(filename)); if (image) { if (m_imageProperties.forceSize.isNull()) SetSize(image->size()); MythRect rect(GetFullArea()); rect.setSize(image->size()); SetMinArea(rect); m_ImagesLock.lock(); m_Images[j] = image; m_ImagesLock.unlock(); SetRedraw(); d->m_UpdateLock.lockForWrite(); m_LastDisplay = QTime::currentTime(); d->m_UpdateLock.unlock(); } else { Reset(); m_ImagesLock.lock(); m_Images[j] = NULL; m_ImagesLock.unlock(); } } } ++j; } return true; }
/** * \copydoc MythUIType::DrawSelf() */ void MythUIImage::DrawSelf(MythPainter *p, int xoffset, int yoffset, int alphaMod, QRect clipRect) { m_ImagesLock.lock(); if (m_Images.size() > 0) { d->m_UpdateLock.lockForWrite(); if (m_CurPos >= (uint)m_Images.size()) m_CurPos = 0; if (!m_Images[m_CurPos]) { unsigned int origPos = m_CurPos; m_CurPos++; while (!m_Images[m_CurPos] && m_CurPos != origPos) { m_CurPos++; if (m_CurPos >= (uint)m_Images.size()) m_CurPos = 0; } } QRect area = GetArea().toQRect(); area.translate(xoffset, yoffset); int alpha = CalcAlpha(alphaMod); MythImage *currentImage = m_Images[m_CurPos]; if (currentImage) currentImage->UpRef(); m_ImagesLock.unlock(); d->m_UpdateLock.unlock(); if (!currentImage) return; d->m_UpdateLock.lockForRead(); QRect currentImageArea = currentImage->rect(); if (!m_ForceSize.isNull()) area.setSize(area.size().expandedTo(currentImage->size())); // Centre image in available space int x = 0; int y = 0; if (area.width() > currentImageArea.width()) x = (area.width() - currentImageArea.width()) / 2; if (area.height() > currentImageArea.height()) y = (area.height() - currentImageArea.height()) / 2; if (x > 0 || y > 0) area.translate(x,y); QRect srcRect; m_cropRect.CalculateArea(GetArea()); if (!m_cropRect.isEmpty()) srcRect = m_cropRect.toQRect(); else srcRect = currentImageArea; p->DrawImage(area, currentImage, srcRect, alpha); currentImage->DownRef(); d->m_UpdateLock.unlock(); } else m_ImagesLock.unlock(); }
/** * \brief Load the image(s), wraps LoadImage() */ bool MythUIImage::Load(bool allowLoadInBackground, bool forceStat) { d->m_UpdateLock.lockForRead(); QSize bForceSize = m_ForceSize; QString bFilename = m_Filename; bFilename.detach(); d->m_UpdateLock.unlock(); QString filename = bFilename; if (bFilename.isEmpty()) { Clear(); SetRedraw(); return false; } Clear(); // SetRedraw(); // if (!IsVisible(true)) // return false; int w = -1; int h = -1; if (!bForceSize.isNull()) { if (bForceSize.width() != -1) w = bForceSize.width(); if (bForceSize.height() != -1) h = bForceSize.height(); } QString imagelabel; int j = 0; for (int i = m_LowNum; i <= m_HighNum && !m_animatedImage; i++) { if (!m_animatedImage && m_HighNum != m_LowNum && bFilename.contains("%1")) filename = bFilename.arg(i); imagelabel = GenImageLabel(filename, w, h); // Only load in the background if allowed and the image is // not already in our mem cache ImageCacheMode cacheMode = kCacheCheckMemoryOnly; if (forceStat) cacheMode = (ImageCacheMode) ((int)kCacheCheckMemoryOnly | (int)kCacheForceStat); ImageCacheMode cacheMode2 = (!forceStat) ? kCacheNormal : (ImageCacheMode) ((int)kCacheNormal | (int)kCacheForceStat); if ((allowLoadInBackground) && (!GetMythUI()->LoadCacheImage(filename, imagelabel, GetPainter(), cacheMode)) && (!getenv("DISABLETHREADEDMYTHUIIMAGE"))) { VERBOSE(VB_GUI|VB_FILE|VB_EXTRA, LOC + QString( "Load(), spawning thread to load '%1'").arg(filename)); ImageLoadThread *bImgThread = new ImageLoadThread( this, bFilename, filename, i, bForceSize, cacheMode2); GetMythUI()->GetImageThreadPool()->start(bImgThread); } else { // Perform a blocking load VERBOSE(VB_GUI|VB_FILE|VB_EXTRA, LOC + QString( "Load(), loading '%1' in foreground").arg(filename)); QString tmpFilename; if ((filename.startsWith("/")) || (filename.startsWith("http://")) || (filename.startsWith("https://")) || (filename.startsWith("ftp://"))) tmpFilename = filename; MythImageReader imageReader(tmpFilename); if (imageReader.supportsAnimation()) { LoadAnimatedImage( imageReader, filename, bForceSize, cacheMode2); } else { MythImage *image = LoadImage( imageReader, filename, bForceSize, cacheMode2); if (image) { if (bForceSize.isNull()) SetSize(image->size()); m_ImagesLock.lock(); m_Images[j] = image; m_ImagesLock.unlock(); SetRedraw(); d->m_UpdateLock.lockForWrite(); m_LastDisplay = QTime::currentTime(); d->m_UpdateLock.unlock(); } else { m_ImagesLock.lock(); m_Images[j] = NULL; m_ImagesLock.unlock(); } } } ++j; } return true; }