void MythPainter::DrawTextLayout(const QRect & canvasRect, const LayoutVector & layouts, const FormatVector & formats, const MythFontProperties & font, int alpha, const QRect & destRect) { if (canvasRect.isNull()) return; QRect canvas(canvasRect); QRect dest(destRect); MythImage *im = GetImageFromTextLayout(layouts, formats, font, canvas, dest); if (!im) { LOG(VB_GENERAL, LOG_ERR, QString("MythPainter::DrawTextLayout: " "Unable to create image.")); return; } if (im->isNull()) { LOG(VB_GENERAL, LOG_DEBUG, QString("MythPainter::DrawTextLayout: " "Rendered image is null.")); im->DecrRef(); return; } QRect srcRect(0, 0, dest.width(), dest.height()); DrawImage(dest, im, srcRect, alpha); im->DecrRef(); }
// Called by the video player to redraw the image. void MHIContext::UpdateOSD(InteractiveScreen *osdWindow, MythPainter *osdPainter) { if (!osdWindow || !osdPainter) return; QMutexLocker locker(&m_display_lock); m_updated = false; osdWindow->DeleteAllChildren(); // Copy all the display items into the display. list<MHIImageData*>::iterator it = m_display.begin(); for (int count = 0; it != m_display.end(); ++it, count++) { MHIImageData *data = *it; MythImage* image = osdPainter->GetFormatImage(); if (!image) continue; image->Assign(data->m_image); MythUIImage *uiimage = new MythUIImage(osdWindow, QString("itv%1") .arg(count)); if (uiimage) { uiimage->SetImage(image); uiimage->SetArea(MythRect(data->m_x, data->m_y, data->m_image.width(), data->m_image.height())); } image->DecrRef(); } osdWindow->OptimiseDisplayedArea(); // N.B. bypasses OSD class hence no expiry set osdWindow->SetVisible(true); }
void ZMEvents::eventVisible(MythUIButtonListItem *item) { if (!item) return; if (item->HasImage()) return; Event *event = qVariantValue<Event*> (item->GetData()); if (event) { QImage image; if (ZMClient *zm = ZMClient::get()) { zm->getAnalyseFrame(event, 0, image); if (!image.isNull()) { MythImage *mimage = GetMythPainter()->GetFormatImage(); mimage->Assign(image); item->SetImage(mimage); mimage->SetChanged(); mimage->DecrRef(); } } } }
void Player::updateFrame(const unsigned char* buffer) { QImage image(buffer, m_monitor.width, m_monitor.height, QImage::Format_RGB888); MythImage *img = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage(); img->Assign(image); m_frameImage->SetImage(img); img->DecrRef(); }
void MythPainter::DrawEllipse(const QRect &area, const QBrush &fillBrush, const QPen &linePen, int alpha) { MythImage *im = GetImageFromRect(area, 0, 1, fillBrush, linePen); if (im) { DrawImage(area.x(), area.y(), im, alpha); im->DecrRef(); } }
void MythPainter::DrawRoundRect(const QRect &area, int cornerRadius, const QBrush &fillBrush, const QPen &linePen, int alpha) { MythImage *im = GetImageFromRect(area, cornerRadius, 0, fillBrush, linePen); if (im) { DrawImage(area.x(), area.y(), im, alpha); im->DecrRef(); } }
void MythPainter::DrawText(const QRect &r, const QString &msg, int flags, const MythFontProperties &font, int alpha, const QRect &boundRect) { MythImage *im = GetImageFromString(msg, flags, r, font); if (!im) return; QRect destRect(boundRect); QRect srcRect(0,0,r.width(),r.height()); if (!boundRect.isEmpty() && boundRect != r) { int x = 0; int y = 0; int width = boundRect.width(); int height = boundRect.height(); if (boundRect.x() > r.x()) { x = boundRect.x()-r.x(); } else if (r.x() > boundRect.x()) { destRect.setX(r.x()); width = (boundRect.x() + boundRect.width()) - r.x(); } if (boundRect.y() > r.y()) { y = boundRect.y()-r.y(); } else if (r.y() > boundRect.y()) { destRect.setY(r.y()); height = (boundRect.y() + boundRect.height()) - r.y(); } if (width <= 0 || height <= 0) return; srcRect.setRect(x,y,width,height); } DrawImage(destRect, im, srcRect, alpha); im->DecrRef(); }
void ZMEvents::eventChanged(MythUIButtonListItem *item) { (void) item; if (m_eventNoText) { if (m_eventGrid->GetCount() > 0) m_eventNoText->SetText(QString("%1/%2") .arg(m_eventGrid->GetCurrentPos() + 1).arg(m_eventGrid->GetCount())); else m_eventNoText->SetText("0/0"); } // update the images for all the visible items for (int x = m_eventGrid->GetCurrentPos() - m_eventGrid->GetVisibleCount(); x < m_eventGrid->GetCurrentPos() + (int)m_eventGrid->GetVisibleCount(); x++) { if (x < 0 || x > (int)m_eventGrid->GetCount() - 1) continue; MythUIButtonListItem *gridItem = m_eventGrid->GetItemAt(x); if (gridItem && !gridItem->HasImage()) { if (x < 0 || x > (int)m_eventList->size() - 1) continue; Event *event = m_eventList->at(x); if (event) { QImage image; if (class ZMClient *zm = ZMClient::get()) { zm->getAnalyseFrame(event, 0, image); if (!image.isNull()) { MythImage *mimage = GetMythPainter()->GetFormatImage(); mimage->Assign(image); gridItem->SetImage(mimage); mimage->SetChanged(); mimage->DecrRef(); } } } } } }
void MythYUVAPainter::DrawRect(const QRect &area, const QBrush &fillBrush, const QPen &linePen, int alpha) { QBrush brush(fillBrush); switch (fillBrush.style()) { case Qt::LinearGradientPattern: case Qt::RadialGradientPattern: case Qt::ConicalGradientPattern: { QGradient gradient = *fillBrush.gradient(); QGradientStops stops = gradient.stops(); for (QGradientStops::iterator it = stops.begin(); it != stops.end(); ++it) { it->second = rgb_to_yuv(it->second); it->second.setAlpha(alpha); } gradient.setStops(stops); brush = gradient; } break; default: brush.setColor(rgb_to_yuv(brush.color())); break; } QPen pen(linePen); pen.setColor(rgb_to_yuv(pen.color())); // We pull an image here, in the hopes that when DrawRect // pulls an image this will still be in the cache and have // the right properties. MythImage *im = GetImageFromRect(area, 0, 0, brush, pen); if (im) { im->SetToYUV(); im->DecrRef(); im = NULL; } MythQImagePainter::DrawRect(area, brush, pen, alpha); }
void MythYUVAPainter::DrawEllipse(const QRect &area, const QBrush &fillBrush, const QPen &linePen, int alpha) { QBrush brush(fillBrush); brush.setColor(rgb_to_yuv(brush.color())); QPen pen(linePen); pen.setColor(rgb_to_yuv(pen.color())); // We pull an image here, in the hopes that when DrawRect // pulls an image this will still be in the cache and have // the right properties. MythImage *im = GetImageFromRect(area, 0, 1, brush, pen); if (im) { im->SetToYUV(); im->DecrRef(); im = NULL; } MythQImagePainter::DrawEllipse(area, brush, pen, alpha); }
void MythPainter::ExpireImages(int64_t max) { bool recompute = false; while (!m_StringExpireList.empty()) { if (m_SoftwareCacheSize < max) break; QString oldmsg = m_StringExpireList.front(); m_StringExpireList.pop_front(); QMap<QString, MythImage*>::iterator it = m_StringToImageMap.find(oldmsg); if (it == m_StringToImageMap.end()) { recompute = true; continue; } MythImage *oldim = *it; it = m_StringToImageMap.erase(it); if (oldim) { m_SoftwareCacheSize -= oldim->bytesPerLine() * oldim->height(); if (m_SoftwareCacheSize < 0) { m_SoftwareCacheSize = 0; recompute = true; } oldim->DecrRef(); } } if (recompute) { m_SoftwareCacheSize = 0; QMap<QString, MythImage*>::iterator it = m_StringToImageMap.begin(); for (; it != m_StringToImageMap.end(); ++it) m_SoftwareCacheSize += (*it)->bytesPerLine() * (*it)->height(); } }
void MythYUVAPainter::DrawText(const QRect &dest, const QString &msg, int flags, const MythFontProperties &font, int alpha, const QRect &boundRect) { MythFontProperties *converted = GetConvertedFont(font); if (converted) { // We pull an image here, in the hopes that when DrawText // pulls an image this will still be in the cache and have // the right properties. MythImage *im = GetImageFromString(msg, flags, dest, *converted); if (im) { im->SetToYUV(); im->DecrRef(); im = NULL; } MythQImagePainter::DrawText(dest, msg, flags, *converted, alpha, boundRect); } }
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; }
/** * \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->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(); }
/** * Update the various fields of a MythNotificationScreen. */ void MythNotificationScreen::Init(void) { if (!m_refresh) // nothing got changed so far, return return; AdjustYPosition(); if (m_artworkImage && (m_update & kImage)) { if (!m_imagePath.isNull()) { // We have a path to the image, use it m_artworkImage->SetFilename(m_imagePath); m_artworkImage->Load(); } else if (!m_image.isNull()) { // We don't have a path to the image, but the image itself MythImage *img = m_artworkImage->GetPainter()->GetFormatImage(); img->Assign(m_image); m_artworkImage->SetImage(img); img->DecrRef(); } else { // Will default to displaying whatever placeholder image is defined // in the xml by the themer, means we can show _something_ rather than // a big empty hole. Generally you always want to call Reset() in // these circumstances m_artworkImage->Reset(); } } if (m_update != kNone) { InfoMap tmap; tmap["title"] = m_title; if (m_update & kImage) { tmap["image"] = m_imagePath; } tmap["origin"] = m_origin; tmap["description"] = m_description; tmap["extra"] = m_extra; if (m_update & kDuration) { tmap["progress_text"] = m_progresstext; tmap["progress"] = QString("%1").arg((int)(m_progress * 100)); } SetTextFromMap(tmap); } if (m_update & kMetaData) { if (m_titleText && m_title.isNull()) { m_titleText->Reset(); } if (m_originText && m_origin.isNull()) { m_originText->Reset(); } if (m_descriptionText && m_description.isNull()) { m_descriptionText->Reset(); } if (m_extraText && m_extra.isNull()) { m_extraText->Reset(); } } if (m_update & kDuration) { if (m_progresstextText && m_progresstext.isEmpty()) { m_progresstextText->Reset(); } if (m_progressBar) { if (m_progress >= 0) { m_progressBar->SetStart(0); m_progressBar->SetTotal(100); m_progressBar->SetUsed(100 * m_progress); } else { // Same as above, calling Reset() allows for a sane, themer defined //default to be displayed m_progressBar->Reset(); } } } if (m_progressBar) { m_progressBar->SetVisible((m_content & kDuration) != 0); } SetErrorState(); if (m_mediaState && (m_update & kImage)) { m_mediaState->DisplayState(m_update & kNoArtwork ? "noartwork" : "ok"); LOG(VB_GUI, LOG_DEBUG, LOC + QString("Init: Set media state to %1").arg(m_update & kNoArtwork ? "noartwork" : "ok")); } // No field will be refreshed the next time unless specified otherwise m_update = kNone; if (GetScreenStack() && !m_added) { GetScreenStack()->AddScreen(this); m_added = true; } m_refresh = false; }
/** * \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::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(); } }
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; }
bool ThumbFinder::getFrameImage(bool needKeyFrame, int64_t requiredPTS) { AVPacket pkt; AVFrame orig; AVFrame retbuf; memset(&orig, 0, sizeof(AVFrame)); memset(&retbuf, 0, sizeof(AVFrame)); av_init_packet(&pkt); int frameFinished = 0; int keyFrame; int frameCount = 0; bool gotKeyFrame = false; while (av_read_frame(m_inputFC, &pkt) >= 0 && !frameFinished) { if (pkt.stream_index == m_videostream) { frameCount++; keyFrame = pkt.flags & AV_PKT_FLAG_KEY; if (m_startPTS == -1 && pkt.dts != AV_NOPTS_VALUE) { m_startPTS = pkt.dts; m_frameTime = pkt.duration; } if (keyFrame) gotKeyFrame = true; if (!gotKeyFrame && needKeyFrame) { av_packet_unref(&pkt); continue; } if (m_firstIFramePTS == -1) m_firstIFramePTS = pkt.dts; av_frame_unref(m_frame); frameFinished = 0; int ret = avcodec_receive_frame(m_codecCtx, m_frame); if (ret == 0) frameFinished = 1; if (ret == 0 || ret == AVERROR(EAGAIN)) ret = avcodec_send_packet(m_codecCtx, &pkt); if (requiredPTS != -1 && pkt.dts != AV_NOPTS_VALUE && pkt.dts < requiredPTS) frameFinished = false; m_currentPTS = pkt.dts; } av_packet_unref(&pkt); } if (frameFinished) { av_image_fill_arrays(retbuf.data, retbuf.linesize, m_outputbuf, AV_PIX_FMT_RGB32, m_frameWidth, m_frameHeight, IMAGE_ALIGN); AVFrame *tmp = m_frame; m_deinterlacer->DeinterlaceSingle(tmp, tmp); m_copy.Copy(&retbuf, AV_PIX_FMT_RGB32, tmp, m_codecCtx->pix_fmt, m_frameWidth, m_frameHeight); QImage img(m_outputbuf, m_frameWidth, m_frameHeight, QImage::Format_RGB32); QByteArray ffile = m_frameFile.toLocal8Bit(); if (!img.save(ffile.constData(), "JPEG")) { LOG(VB_GENERAL, LOG_ERR, "Failed to save thumb: " + m_frameFile); } if (m_updateFrame) { MythImage *mimage = GetMythMainWindow()->GetCurrentPainter()->GetFormatImage(); mimage->Assign(img); m_frameImage->SetImage(mimage); mimage->DecrRef(); } updateCurrentPos(); } 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; }