MythTV  master
mythuiimage.cpp
Go to the documentation of this file.
1 
2 #include "mythuiimage.h"
3 
4 // C++
5 #include <cmath>
6 #include <cstdint>
7 #include <cstdlib>
8 #include <ctime>
9 
10 // QT
11 #include <QCoreApplication>
12 #include <QDir>
13 #include <QDomDocument>
14 #include <QEvent>
15 #include <QFile>
16 #include <QImageReader>
17 #include <QReadWriteLock>
18 #include <QRunnable>
19 
20 // libmythbase
21 #include "mythlogging.h"
22 #include "mthreadpool.h"
23 
24 // Mythui
25 #include "mythpainter.h"
26 #include "mythmainwindow.h"
27 #include "mythuihelper.h"
28 #include "mythscreentype.h"
29 
30 #ifdef _MSC_VER
31 # include "compat.h" // random
32 #endif
33 
34 class ImageLoadThread;
35 
36 #define LOC QString("MythUIImage(0x%1): ").arg((uint64_t)this,0,16)
37 
39 
41 {
42  Copy(other);
43 }
44 
46 {
47  if (this == &other)
48  return *this;
49 
50  Copy(other);
51 
52  return *this;
53 }
54 
56 {
57  if (m_maskImage)
59 }
60 
62 {
63  m_filename = other.m_filename;
64 
65  m_cropRect = other.m_cropRect;
66  m_forceSize = other.m_forceSize;
67 
71  m_isOriented = other.m_isOriented;
72 
79 
81 
83  m_isMasked = other.m_isMasked;
85 }
86 
88 {
89  if (image)
90  image->IncrRef();
91  if (m_maskImage)
93 
94  m_maskImage = image;
96 }
97 
102 {
103  public:
104  ImageLoader() = default;
105  ~ImageLoader() = default;
106 
107  static QHash<QString, const MythUIImage *> m_loadingImages;
108  static QMutex m_loadingImagesLock;
109  static QWaitCondition m_loadingImagesCond;
110 
111  static bool PreLoad(const QString &cacheKey, const MythUIImage *uitype)
112  {
113  m_loadingImagesLock.lock();
114 
115  // Check to see if the image is being loaded by us in another thread
116  if ((m_loadingImages.contains(cacheKey)) &&
117  (m_loadingImages[cacheKey] == uitype))
118  {
119  LOG(VB_GUI | VB_FILE, LOG_DEBUG,
120  QString("ImageLoader::PreLoad(%1), this "
121  "file is already being loaded by this same MythUIImage "
122  "in another thread.").arg(cacheKey));
123  m_loadingImagesLock.unlock();
124  return false;
125  }
126 
127  // Check to see if the exact same image is being loaded anywhere else
128  while (m_loadingImages.contains(cacheKey))
130 
131  m_loadingImages[cacheKey] = uitype;
132  m_loadingImagesLock.unlock();
133 
134  return true;
135  }
136 
137  static void PostLoad(const QString &cacheKey)
138  {
139  m_loadingImagesLock.lock();
140  m_loadingImages.remove(cacheKey);
141  m_loadingImagesCond.wakeAll();
142  m_loadingImagesLock.unlock();
143  }
144 
145  static bool SupportsAnimation(const QString &filename)
146  {
147  QString extension = filename.section('.', -1);
148  return !filename.startsWith("myth://") &&
149  (extension == "gif" ||
150  extension == "apng" ||
151  extension == "mng");
152  }
153 
158  static QString GenImageLabel(const ImageProperties &imProps)
159  {
160  QString imagelabel;
161  QString s_Attrib;
162 
163  if (imProps.m_isMasked)
164  s_Attrib = "masked";
165 
166  if (imProps.m_isReflected)
167  s_Attrib += "reflected";
168 
169  if (imProps.m_isGreyscale)
170  s_Attrib += "greyscale";
171 
172  if (imProps.m_isOriented)
173  {
174  s_Attrib += "orientation";
175  s_Attrib += QString("%1").arg(imProps.m_orientation);
176  }
177 
178  int w = -1;
179  int h = -1;
180  if (!imProps.m_forceSize.isNull())
181  {
182  if (imProps.m_forceSize.width() != -1)
183  w = imProps.m_forceSize.width();
184 
185  if (imProps.m_forceSize.height() != -1)
186  h = imProps.m_forceSize.height();
187  }
188 
189 
190  imagelabel = QString("%1-%2-%3x%4.png")
191  .arg(imProps.m_filename)
192  .arg(s_Attrib)
193  .arg(w)
194  .arg(h);
195  imagelabel.replace('/', '-');
196 #ifdef Q_OS_ANDROID
197  imagelabel.replace(':', '-');
198 #endif
199 
200  return imagelabel;
201  }
202 
203  static MythImage *LoadImage(MythPainter *painter,
204  // Must be a copy for thread safety
205  ImageProperties imProps,
206  ImageCacheMode cacheMode,
207  // Included only to check address, could be
208  // replaced by generating a unique value for
209  // each MythUIImage object?
210  const MythUIImage *parent,
211  bool &aborted,
212  MythImageReader *imageReader = nullptr)
213  {
214  QString cacheKey = GenImageLabel(imProps);
215  if (!PreLoad(cacheKey, parent))
216  {
217  aborted = true;
218  return nullptr;
219  }
220 
221  QString filename = imProps.m_filename;
222  MythImage *image = nullptr;
223 
224  bool bResize = false;
225  bool bFoundInCache = false;
226 
227  int w = -1;
228  int h = -1;
229 
230  if (!imProps.m_forceSize.isNull())
231  {
232  if (imProps.m_forceSize.width() != -1)
233  w = imProps.m_forceSize.width();
234 
235  if (imProps.m_forceSize.height() != -1)
236  h = imProps.m_forceSize.height();
237 
238  bResize = true;
239  }
240 
241  if (!imageReader)
242  {
243  image = GetMythUI()->LoadCacheImage(filename, cacheKey,
244  painter, cacheMode);
245  }
246 
247  if (image)
248  {
249  if (VERBOSE_LEVEL_CHECK(VB_GUI | VB_FILE, LOG_INFO))
250  {
251  image->IncrRef();
252  int cnt = image->DecrRef();
253  LOG(VB_GUI | VB_FILE, LOG_INFO,
254  QString("ImageLoader::LoadImage(%1) Found in cache, "
255  "RefCount = %2")
256  .arg(cacheKey).arg(cnt));
257  }
258 
259  if (imProps.m_isReflected)
260  image->setIsReflected(true);
261 
262  if (imProps.m_isOriented)
263  image->setIsOriented(true);
264 
265  bFoundInCache = true;
266  }
267  else
268  {
269  LOG(VB_GUI | VB_FILE, LOG_INFO,
270  QString("ImageLoader::LoadImage(%1) NOT Found in cache. "
271  "Loading Directly").arg(cacheKey));
272 
273  image = painter->GetFormatImage();
274  bool ok = false;
275 
276  if (imageReader)
277  ok = image->Load(imageReader);
278  else
279  ok = image->Load(filename);
280 
281  if (!ok)
282  {
283  image->DecrRef();
284  image = nullptr;
285  }
286  }
287 
288  if (image && image->isNull())
289  {
290  LOG(VB_GUI | VB_FILE, LOG_INFO,
291  QString("ImageLoader::LoadImage(%1) Image is NULL")
292  .arg(filename));
293 
294  image->DecrRef();
295  image = nullptr;
296  }
297 
298  if (image && !bFoundInCache)
299  {
300  if (imProps.m_isReflected)
301  {
302  image->Reflect(imProps.m_reflectAxis, imProps.m_reflectShear,
303  imProps.m_reflectScale, imProps.m_reflectLength,
304  imProps.m_reflectSpacing);
305  }
306 
307  if (imProps.m_isGreyscale)
308  image->ToGreyscale();
309 
310  if (imProps.m_isOriented)
311  image->Orientation(imProps.m_orientation);
312 
313  // Even if an explicit size wasn't defined this image may still need
314  // to be scaled because of a difference between the theme resolution
315  // and the screen resolution. We want to avoid scaling twice.
316  if (!bResize && imProps.m_isThemeImage)
317  {
318  float wmult = NAN; // Width multipler
319  float hmult = NAN; // Height multipler
320  GetMythUI()->GetScreenSettings(wmult, hmult);
321  if (wmult != 1.0F || hmult != 1.0F)
322  {
323  w = image->size().width() * wmult;
324  h = image->size().height() * hmult;
325  bResize = true;
326  }
327  }
328 
329  if (bResize)
330  image->Resize(QSize(w, h), imProps.m_preserveAspect);
331 
332  if (imProps.m_isMasked)
333  {
334  MythImage *newMaskImage = painter->GetFormatImage();
335  if (newMaskImage->Load(imProps.GetMaskImageFilename()))
336  {
337  float wmult = NAN; // Width multipler
338  float hmult = NAN; // Height multipler
339  GetMythUI()->GetScreenSettings(wmult, hmult);
340  if (wmult != 1.0F || hmult != 1.0F)
341  {
342  int width = newMaskImage->size().width() * wmult;
343  int height = newMaskImage->size().height() * hmult;
344  newMaskImage->Resize(QSize(width, height));
345  }
346 
347  imProps.SetMaskImage(newMaskImage);
348  }
349  else
350  imProps.SetMaskImage(nullptr);
351  newMaskImage->DecrRef();
352 
353  QRect imageArea = image->rect();
354  QRect maskArea = imProps.GetMaskImageRect();
355 
356  // Crop the mask to the image
357  int x = 0;
358  int y = 0;
359 
360  if (maskArea.width() > imageArea.width())
361  x = (maskArea.width() - imageArea.width()) / 2;
362 
363  if (maskArea.height() > imageArea.height())
364  y = (maskArea.height() - imageArea.height()) / 2;
365 
366  if (x > 0 || y > 0)
367  imageArea.translate(x, y);
368 
369  QImage mask = imProps.GetMaskImageSubset(imageArea);
370  image->setAlphaChannel(mask.alphaChannel());
371  }
372 
373  if (!imageReader)
374  GetMythUI()->CacheImage(cacheKey, image);
375  }
376 
377  if (image)
378  image->SetChanged();
379 
380  PostLoad(cacheKey);
381 
382  return image;
383  }
384 
386  // Must be a copy for thread safety
387  const ImageProperties& imProps,
388  ImageCacheMode cacheMode,
389  // Included only to check address, could be
390  // replaced by generating a unique value for
391  // each MythUIImage object?
392  const MythUIImage *parent,
393  bool &aborted)
394  {
395  QString filename = QString("frame-%1-") + imProps.m_filename;
396  QString frameFilename;
397  int imageCount = 1;
398 
399  auto *imageReader = new MythImageReader(imProps.m_filename);
400  auto *images = new AnimationFrames();
401 
402  while (imageReader->canRead() && !aborted)
403  {
404  frameFilename = filename.arg(imageCount);
405 
406  ImageProperties frameProps = imProps;
407  frameProps.m_filename = frameFilename;
408 
409  MythImage *im = LoadImage(painter, frameProps, cacheMode, parent,
410  aborted, imageReader);
411 
412  if (!im)
413  aborted = true;
414 
415  images->append(AnimationFrame(im, imageReader->nextImageDelay()));
416  imageCount++;
417  }
418 
419  delete imageReader;
420 
421  return images;
422  }
423 
424 };
425 
426 QHash<QString, const MythUIImage *> ImageLoader::m_loadingImages;
428 QWaitCondition ImageLoader::m_loadingImagesCond;
429 
433 class ImageLoadEvent : public QEvent
434 {
435  public:
436  ImageLoadEvent(const MythUIImage *parent, MythImage *image,
437  QString basefile, QString filename,
438  int number, bool aborted)
439  : QEvent(kEventType),
440  m_parent(parent), m_image(image), m_basefile(std::move(basefile)),
441  m_filename(std::move(filename)), m_number(number),
442  m_aborted(aborted) { }
443 
445  QString basefile,
446  QString filename, bool aborted)
447  : QEvent(kEventType),
448  m_parent(parent), m_basefile(std::move(basefile)),
449  m_filename(std::move(filename)),
450  m_images(frames), m_aborted(aborted) { }
451 
452  const MythUIImage *GetParent() const { return m_parent; }
453  MythImage *GetImage() const { return m_image; }
454  QString GetBasefile() const { return m_basefile; }
455  QString GetFilename() const { return m_filename; }
456  int GetNumber() const { return m_number; }
458  bool GetAbortState() const { return m_aborted; }
459 
460  static Type kEventType;
461 
462  private:
463  const MythUIImage *m_parent {nullptr};
464  MythImage *m_image {nullptr};
465  QString m_basefile;
466  QString m_filename;
467  int m_number {0};
468 
469  // Animated Images
471 
472  // Image Load
473  bool m_aborted;
474 };
475 
476 QEvent::Type ImageLoadEvent::kEventType =
477  (QEvent::Type) QEvent::registerEventType();
478 
482 class ImageLoadThread : public QRunnable
483 {
484  public:
486  const ImageProperties &imProps, QString basefile,
487  int number, ImageCacheMode mode) :
488  m_parent(parent), m_painter(painter), m_imageProperties(imProps),
489  m_basefile(std::move(basefile)), m_number(number), m_cacheMode(mode)
490  {
491  }
492 
493  void run() override // QRunnable
494  {
495  bool aborted = false;
497 
498  // NOTE Do NOT use MythImageReader::supportsAnimation here, it defeats
499  // the point of caching remote images
501  {
502  AnimationFrames *frames =
506  aborted);
507 
508  if (frames && frames->count() > 1)
509  {
510  auto *le = new ImageLoadEvent(m_parent, frames, m_basefile,
512  aborted);
513  QCoreApplication::postEvent(m_parent, le);
514 
515  return;
516  }
517  delete frames;
518  }
519 
523  aborted);
524 
525  auto *le = new ImageLoadEvent(m_parent, image, m_basefile,
527  m_number, aborted);
528  QCoreApplication::postEvent(m_parent, le);
529  }
530 
531 private:
532  MythUIImage *m_parent {nullptr};
533  MythPainter *m_painter {nullptr};
535  QString m_basefile;
536  int m_number;
538 };
539 
542 {
543 public:
545  : m_parent(p) { }
546  ~MythUIImagePrivate() = default;
547 
548  MythUIImage *m_parent {nullptr};
549 
550  QReadWriteLock m_updateLock {QReadWriteLock::Recursive};
551 };
552 
554 
555 MythUIImage::MythUIImage(const QString &filepattern,
556  int low, int high, int delayms,
557  MythUIType *parent, const QString &name)
558  : MythUIType(parent, name)
559 {
560  m_imageProperties.m_filename = filepattern;
561  m_LowNum = low;
562  m_HighNum = high;
563 
564  m_Delay = delayms;
565  m_EnableInitiator = true;
566 
567  d = new MythUIImagePrivate(this);
568  emit DependChanged(false);
569 }
570 
572  const QString &name)
573  : MythUIType(parent, name)
574 {
577 
578  m_LowNum = 0;
579  m_HighNum = 0;
580  m_Delay = -1;
581  m_EnableInitiator = true;
582 
583  d = new MythUIImagePrivate(this);
584  emit DependChanged(false);
585 }
586 
587 MythUIImage::MythUIImage(MythUIType *parent, const QString &name)
588  : MythUIType(parent, name)
589 {
590  m_LowNum = 0;
591  m_HighNum = 0;
592  m_Delay = -1;
593  m_EnableInitiator = true;
594 
595  d = new MythUIImagePrivate(this);
596 }
597 
599 {
600  // Wait until all image loading threads are complete or bad things
601  // may happen if this MythUIImage disappears when a queued thread
602  // needs it.
603  if (m_runningThreads > 0)
604  {
606  }
607 
608  Clear();
609 
610  delete d;
611 }
612 
617 {
618  QWriteLocker updateLocker(&d->m_updateLock);
619  QMutexLocker locker(&m_ImagesLock);
620 
621  while (!m_Images.isEmpty())
622  {
623  QHash<int, MythImage *>::iterator it = m_Images.begin();
624 
625  if (*it)
626  (*it)->DecrRef();
627 
628  m_Images.remove(it.key());
629  }
630 
631  m_Delays.clear();
632 
633  if (m_animatedImage)
634  {
635  m_LowNum = 0;
636  m_HighNum = 0;
637  m_animatedImage = false;
638  }
639 }
640 
645 {
646  d->m_updateLock.lockForWrite();
647 
648  SetMinArea(MythRect());
649 
651  {
654 
655  if (m_animatedImage)
656  {
657  m_LowNum = 0;
658  m_HighNum = 0;
659  m_animatedImage = false;
660  }
661  emit DependChanged(true);
662 
663  d->m_updateLock.unlock();
664  Load();
665  }
666  else
667  d->m_updateLock.unlock();
668 
670 }
671 
675 void MythUIImage::SetFilename(const QString &filename)
676 {
677  QWriteLocker updateLocker(&d->m_updateLock);
680  if (filename == m_OrigFilename)
681  emit DependChanged(true);
682  else
683  emit DependChanged(false);
684 }
685 
690 void MythUIImage::SetFilepattern(const QString &filepattern, int low,
691  int high)
692 {
693  QWriteLocker updateLocker(&d->m_updateLock);
695  m_imageProperties.m_filename = filepattern;
696  m_LowNum = low;
697  m_HighNum = high;
698  if (filepattern == m_OrigFilename)
699  emit DependChanged(true);
700  else
701  emit DependChanged(false);
702 }
703 
707 void MythUIImage::SetImageCount(int low, int high)
708 {
709  QWriteLocker updateLocker(&d->m_updateLock);
710  m_LowNum = low;
711  m_HighNum = high;
712 }
713 
717 void MythUIImage::SetDelay(int delayms)
718 {
719  QWriteLocker updateLocker(&d->m_updateLock);
720  m_Delay = delayms;
721  m_LastDisplay = QTime::currentTime();
722  m_CurPos = 0;
723 }
724 
728 void MythUIImage::SetDelays(const QVector<int>& delays)
729 {
730  QWriteLocker updateLocker(&d->m_updateLock);
731  QMutexLocker imageLocker(&m_ImagesLock);
732 
733  for (int delay : qAsConst(delays))
734  m_Delays[m_Delays.size()] = delay;
735 
736  if (m_Delay == -1)
737  m_Delay = m_Delays[0];
738 
739  m_LastDisplay = QTime::currentTime();
740  m_CurPos = 0;
741 }
742 
748 {
749  d->m_updateLock.lockForWrite();
750 
751  if (!img)
752  {
753  d->m_updateLock.unlock();
754  Reset();
755  return;
756  }
757 
760 
761  img->IncrRef();
762 
763  QSize forceSize = m_imageProperties.m_forceSize;
764  if (!forceSize.isNull())
765  {
766  int w = (forceSize.width() <= 0) ? img->width() : forceSize.width();
767  int h = (forceSize.height() <= 0) ? img->height() : forceSize.height();
768  img->Resize(QSize(w, h), m_imageProperties.m_preserveAspect);
769  }
770 
772  {
778  }
779 
780  if (m_imageProperties.m_isGreyscale && !img->isGrayscale())
781  img->ToGreyscale();
782 
783  Clear();
784  m_Delay = -1;
785 
788 
789  if (m_imageProperties.m_forceSize.isNull())
790  SetSize(img->size());
791 
792  m_ImagesLock.lock();
793  m_Images[0] = img;
794  m_Delays.clear();
795  m_ImagesLock.unlock();
796 
797  m_CurPos = 0;
799  SetRedraw();
800 
801  d->m_updateLock.unlock();
802 }
803 
809 void MythUIImage::SetImages(QVector<MythImage *> *images)
810 {
811  Clear();
812 
813  QWriteLocker updateLocker(&d->m_updateLock);
814  QSize aSize = GetFullArea().size();
815 
817 
818  for (auto *im : qAsConst(*images))
819  {
820  if (!im)
821  {
822  QMutexLocker locker(&m_ImagesLock);
823  m_Images[m_Images.size()] = im;
824  continue;
825  }
826 
827  im->IncrRef();
828 
829 
830  QSize forceSize = m_imageProperties.m_forceSize;
831  if (!forceSize.isNull())
832  {
833  int w = (forceSize.width() <= 0) ? im->width() : forceSize.width();
834  int h = (forceSize.height() <= 0) ? im->height() : forceSize.height();
835  im->Resize(QSize(w, h), m_imageProperties.m_preserveAspect);
836  }
837 
838  if (m_imageProperties.m_isReflected && !im->IsReflected())
839  {
840  im->Reflect(m_imageProperties.m_reflectAxis,
845  }
846 
847  if (m_imageProperties.m_isGreyscale && !im->isGrayscale())
848  im->ToGreyscale();
849 
850  if (m_imageProperties.m_isOriented && !im->IsOriented())
851  im->Orientation(m_imageProperties.m_orientation);
852 
853  m_ImagesLock.lock();
854  m_Images[m_Images.size()] = im;
855  m_ImagesLock.unlock();
856 
857  aSize = aSize.expandedTo(im->size());
858  }
859 
860  SetImageCount(1, m_Images.size());
861 
862  if (m_imageProperties.m_forceSize.isNull())
863  SetSize(aSize);
864 
865  MythRect rect(GetFullArea());
866  rect.setSize(aSize);
867  SetMinArea(rect);
868 
869  m_CurPos = 0;
870  m_animatedImage = true;
872  SetRedraw();
873 }
874 
876 {
877  QVector<int> delays;
878  QVector<MythImage *> images;
879 
880  for (const auto & frame : qAsConst(frames))
881  {
882  images.append(frame.first);
883  delays.append(frame.second);
884  }
885 
886  if (!images.empty())
887  {
888  SetImages(&images);
889 
890  if (m_Delay < 0 && !delays.empty())
891  SetDelays(delays);
892  }
893  else
894  Reset();
895 }
896 
900 void MythUIImage::ForceSize(const QSize &size)
901 {
902  if (m_imageProperties.m_forceSize == size)
903  return;
904 
905  d->m_updateLock.lockForWrite();
907  d->m_updateLock.unlock();
908 
909  if (size.isEmpty())
910  return;
911 
913 
914  Load();
915 }
916 
920 void MythUIImage::SetOrientation(int orientation)
921 {
923  m_imageProperties.m_orientation = orientation;
924 }
925 
929 void MythUIImage::SetSize(int width, int height)
930 {
931  SetSize(QSize(width, height));
932 }
933 
937 void MythUIImage::SetSize(const QSize &size)
938 {
939  QWriteLocker updateLocker(&d->m_updateLock);
940  MythUIType::SetSize(size);
941  m_NeedLoad = true;
942 }
943 
948 void MythUIImage::SetCropRect(int x, int y, int width, int height)
949 {
950  SetCropRect(MythRect(x, y, width, height));
951 }
952 
958 {
959  QWriteLocker updateLocker(&d->m_updateLock);
961  SetRedraw();
962 }
963 
967 bool MythUIImage::Load(bool allowLoadInBackground, bool forceStat)
968 {
969  d->m_updateLock.lockForRead();
970 
972 
973  QString bFilename = m_imageProperties.m_filename;
974 
975  d->m_updateLock.unlock();
976 
977  QString filename = bFilename;
978 
979  if (bFilename.isEmpty())
980  {
981  Clear();
982  SetMinArea(MythRect());
983  SetRedraw();
984 
985  return false;
986  }
987 
988  if (getenv("DISABLETHREADEDMYTHUIIMAGE"))
989  allowLoadInBackground = false;
990 
991  // Don't clear the widget before we need to, otherwise it causes
992  // unsightly flashing. We exclude animations for now since that requires a
993  // deeper fix
994  bool isAnimation = (m_HighNum != m_LowNum) || m_animatedImage;
995 
996  if (isAnimation)
997  Clear();
998 
999  bool complete = true;
1000 
1001  QString imagelabel;
1002 
1003  int j = 0;
1004 
1005  for (int i = m_LowNum; i <= m_HighNum && !m_animatedImage; i++)
1006  {
1007  if (!m_animatedImage && m_HighNum != m_LowNum &&
1008  bFilename.contains("%1"))
1009  filename = bFilename.arg(i);
1010 
1012  imProps.m_filename = filename;
1013  imagelabel = ImageLoader::GenImageLabel(imProps);
1014 
1015  // Only load in the background if allowed and the image is
1016  // not already in our mem cache
1017  int cacheMode = kCacheIgnoreDisk;
1018 
1019  if (forceStat)
1020  cacheMode |= (int)kCacheForceStat;
1021 
1022  int cacheMode2 = kCacheNormal;
1023 
1024  if (forceStat)
1025  cacheMode2 |= (int)kCacheForceStat;
1026 
1027  bool do_background_load = false;
1028  if (allowLoadInBackground)
1029  {
1031  filename, imagelabel, GetPainter(),
1032  static_cast<ImageCacheMode>(cacheMode));
1033  if (img)
1034  img->DecrRef();
1035  else
1036  do_background_load = true;
1037  }
1038 
1039  if (do_background_load)
1040  {
1041  SetMinArea(MythRect());
1042  LOG(VB_GUI | VB_FILE, LOG_DEBUG, LOC +
1043  QString("Load(), spawning thread to load '%1'").arg(filename));
1044 
1045  m_runningThreads++;
1046  auto *bImgThread = new ImageLoadThread(this, GetPainter(),
1047  imProps, bFilename, i,
1048  static_cast<ImageCacheMode>(cacheMode2));
1049  GetMythUI()->GetImageThreadPool()->start(bImgThread, "ImageLoad");
1050  }
1051  else
1052  {
1053  if (!isAnimation && !GetMythUI()->IsImageInCache(imagelabel))
1054  Clear();
1055 
1056  // Perform a blocking load
1057  LOG(VB_GUI | VB_FILE, LOG_DEBUG, LOC +
1058  QString("Load(), loading '%1' in foreground").arg(filename));
1059  bool aborted = false;
1060 
1062  {
1063  AnimationFrames *myFrames =
1065  static_cast<ImageCacheMode>(cacheMode2),
1066  this, aborted);
1067 
1068  // TODO We might want to handle an abort here more gracefully
1069  if (aborted)
1070  {
1071  LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading animated"
1072  "image %1 in foreground")
1073  .arg(filename));
1074  }
1075 
1076  SetAnimationFrames(*myFrames);
1077 
1078  delete myFrames;
1079  }
1080  else
1081  {
1082  MythImage *image = nullptr;
1083 
1085  imProps,
1086  static_cast<ImageCacheMode>(cacheMode2),
1087  this, aborted);
1088 
1089  // TODO We might want to handle an abort here more gracefully
1090  if (aborted)
1091  {
1092  LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading animated"
1093  "image %1 in foreground")
1094  .arg(filename));
1095  }
1096 
1097  if (image)
1098  {
1099  if (m_imageProperties.m_forceSize.isNull())
1100  SetSize(image->size());
1101 
1102  MythRect rect(GetFullArea());
1103  rect.setSize(image->size());
1104  SetMinArea(rect);
1105 
1106  m_ImagesLock.lock();
1107  m_Images[j] = image;
1108  m_ImagesLock.unlock();
1109 
1110  SetRedraw();
1111  d->m_updateLock.lockForWrite();
1112  m_LastDisplay = QTime::currentTime();
1113  d->m_updateLock.unlock();
1114  }
1115  else
1116  {
1117  Reset();
1118 
1119  m_ImagesLock.lock();
1120  m_Images[j] = nullptr;
1121  m_ImagesLock.unlock();
1122  }
1123  }
1124  }
1125 
1126  ++j;
1127 
1128  // Load is complete if no image is loading in background
1129  complete &= !do_background_load;
1130  }
1131 
1132  if (complete)
1133  emit LoadComplete();
1134 
1135  return true;
1136 }
1137 
1142 {
1143  d->m_updateLock.lockForWrite();
1144 
1145  int delay = -1;
1146 
1147  if (m_Delays.contains(m_CurPos))
1148  delay = m_Delays[m_CurPos];
1149  else if (m_Delay > 0)
1150  delay = m_Delay;
1151 
1152  if (delay > 0 &&
1153  abs(m_LastDisplay.msecsTo(QTime::currentTime())) > delay)
1154  {
1156  {
1157  FindRandomImage();
1158  d->m_updateLock.unlock();
1159  Load();
1160  d->m_updateLock.lockForWrite();
1161  }
1162  else
1163  {
1164  m_ImagesLock.lock();
1165 
1167  {
1168  ++m_CurPos;
1169 
1170  if (m_CurPos >= (uint)m_Images.size())
1171  m_CurPos = 0;
1172  }
1173  else if (m_animationCycle == kCycleReverse)
1174  {
1175  if ((m_CurPos + 1) >= (uint)m_Images.size())
1176  {
1177  m_animationReverse = true;
1178  }
1179  else if (m_CurPos == 0)
1180  {
1181  m_animationReverse = false;
1182  }
1183 
1184  if (m_animationReverse)
1185  --m_CurPos;
1186  else
1187  ++m_CurPos;
1188  }
1189 
1190  m_ImagesLock.unlock();
1191 
1192  SetRedraw();
1193  }
1194 
1195  m_LastDisplay = QTime::currentTime();
1196  }
1197 
1199 
1200  d->m_updateLock.unlock();
1201 }
1202 
1206 void MythUIImage::DrawSelf(MythPainter *p, int xoffset, int yoffset,
1207  int alphaMod, QRect clipRect)
1208 {
1209  m_ImagesLock.lock();
1210 
1211  if (!m_Images.empty())
1212  {
1213  d->m_updateLock.lockForWrite();
1214 
1215  if (m_CurPos >= (uint)m_Images.size())
1216  m_CurPos = 0;
1217 
1218  if (!m_Images[m_CurPos])
1219  {
1220  unsigned int origPos = m_CurPos;
1221  m_CurPos++;
1222 
1223  while (!m_Images[m_CurPos] && m_CurPos != origPos)
1224  {
1225  m_CurPos++;
1226 
1227  if (m_CurPos >= (uint)m_Images.size())
1228  m_CurPos = 0;
1229  }
1230  }
1231 
1232  QRect area = GetArea().toQRect();
1233  area.translate(xoffset, yoffset);
1234 
1235  int alpha = CalcAlpha(alphaMod);
1236 
1237  MythImage *currentImage = m_Images[m_CurPos];
1238 
1239  if (currentImage)
1240  currentImage->IncrRef();
1241 
1242  m_ImagesLock.unlock();
1243  d->m_updateLock.unlock();
1244 
1245  if (!currentImage)
1246  return;
1247 
1248  d->m_updateLock.lockForRead();
1249 
1250  QRect currentImageArea = currentImage->rect();
1251 
1252  if (!m_imageProperties.m_forceSize.isNull())
1253  area.setSize(area.size().expandedTo(currentImage->size()));
1254 
1255  // Centre image in available space, accounting for zoom
1256  int x = 0;
1257  int y = 0;
1258  QRect visibleImage = m_Effects.GetExtent(currentImageArea.size());
1259 
1260  if (area.width() > visibleImage.width())
1261  x = area.width() / 2 + visibleImage.topLeft().x();
1262 
1263  if (area.height() > visibleImage.height())
1264  y = area.height() / 2 + visibleImage.topLeft().y();
1265 
1266  if ((x > 0 || y > 0))
1267  area.translate(x, y);
1268 
1269  QRect srcRect;
1271 
1272  if (!m_imageProperties.m_cropRect.isEmpty())
1273  srcRect = m_imageProperties.m_cropRect.toQRect();
1274  else
1275  srcRect = currentImageArea;
1276 
1277  p->SetClipRect(clipRect);
1278  p->DrawImage(area, currentImage, srcRect, alpha);
1279  currentImage->DecrRef();
1280  d->m_updateLock.unlock();
1281  }
1282  else
1283  m_ImagesLock.unlock();
1284 }
1285 
1290  const QString &filename, QDomElement &element, bool showWarnings)
1291 {
1292  QWriteLocker updateLocker(&d->m_updateLock);
1293 
1294  if (element.tagName() == "filename")
1295  {
1296  m_imageProperties.m_isThemeImage = true; // This is an image distributed with the theme
1298 
1299  if (m_imageProperties.m_filename.endsWith('/'))
1300  {
1301  m_showingRandomImage = true;
1303 
1304  FindRandomImage();
1305  }
1306  }
1307  else if (element.tagName() == "filepattern")
1308  {
1309  m_imageProperties.m_isThemeImage = true; // This is an image distributed with the theme
1311  QString tmp = element.attribute("low");
1312 
1313  if (!tmp.isEmpty())
1314  m_LowNum = tmp.toInt();
1315 
1316  tmp = element.attribute("high");
1317 
1318  if (!tmp.isEmpty())
1319  m_HighNum = tmp.toInt();
1320 
1321  tmp = element.attribute("cycle", "start");
1322 
1323  if (tmp == "reverse")
1325  }
1326  else if (element.tagName() == "area")
1327  {
1328  SetArea(parseRect(element));
1330  }
1331  else if (element.tagName() == "preserveaspect")
1333  else if (element.tagName() == "crop")
1335  else if (element.tagName() == "delay")
1336  {
1337  QString value = getFirstText(element);
1338 
1339  if (value.contains(","))
1340  {
1341  QVector<int> delays;
1342  QStringList tokens = value.split(",");
1343  for (const auto & token : qAsConst(tokens))
1344  {
1345  if (token.isEmpty())
1346  {
1347  if (!delays.empty())
1348  delays.append(delays[delays.size()-1]);
1349  else
1350  delays.append(0); // Default 0ms delay before first image
1351  }
1352  else
1353  {
1354  delays.append(token.toInt());
1355  }
1356  }
1357 
1358  if (!delays.empty())
1359  {
1360  m_Delay = delays[0];
1361  SetDelays(delays);
1362  }
1363  }
1364  else
1365  {
1366  m_Delay = value.toInt();
1367  }
1368  }
1369  else if (element.tagName() == "reflection")
1370  {
1372  QString tmp = element.attribute("axis");
1373 
1374  if (!tmp.isEmpty())
1375  {
1376  if (tmp.toLower() == "horizontal")
1378  else
1380  }
1381 
1382  tmp = element.attribute("shear");
1383 
1384  if (!tmp.isEmpty())
1386 
1387  tmp = element.attribute("scale");
1388 
1389  if (!tmp.isEmpty())
1391 
1392  tmp = element.attribute("length");
1393 
1394  if (!tmp.isEmpty())
1396 
1397  tmp = element.attribute("spacing");
1398 
1399  if (!tmp.isEmpty())
1401  }
1402  else if (element.tagName() == "mask")
1403  {
1406  }
1407  else if (element.tagName() == "grayscale" ||
1408  element.tagName() == "greyscale")
1409  {
1411  }
1412  else
1413  {
1414  return MythUIType::ParseElement(filename, element, showWarnings);
1415  }
1416 
1417  m_NeedLoad = true;
1418 
1419  if (m_Parent && m_Parent->IsDeferredLoading(true))
1420  m_NeedLoad = false;
1421 
1422  return true;
1423 }
1424 
1429 {
1430  d->m_updateLock.lockForWrite();
1431  auto *im = dynamic_cast<MythUIImage *>(base);
1432  if (!im)
1433  {
1434  LOG(VB_GENERAL, LOG_ERR,
1435  QString("'%1' (%2) ERROR, bad parsing '%3' (%4)")
1436  .arg(objectName()).arg(GetXMLLocation())
1437  .arg(base->objectName()).arg(base->GetXMLLocation()));
1438  d->m_updateLock.unlock();
1439  return;
1440  }
1441 
1442  m_OrigFilename = im->m_OrigFilename;
1443 
1444  m_Delay = im->m_Delay;
1445  m_LowNum = im->m_LowNum;
1446  m_HighNum = im->m_HighNum;
1447 
1448  m_LastDisplay = QTime::currentTime();
1449  m_CurPos = 0;
1450 
1451  m_imageProperties = im->m_imageProperties;
1452 
1453  m_animationCycle = im->m_animationCycle;
1454  m_animatedImage = im->m_animatedImage;
1455 
1456  m_showingRandomImage = im->m_showingRandomImage;
1457  m_imageDirectory = im->m_imageDirectory;
1458 
1459  MythUIType::CopyFrom(base);
1460 
1461  // We need to update forceSize in case the parent area has changed
1462  // however we only want to set forceSize if it was previously in use
1463  if (!m_imageProperties.m_forceSize.isNull())
1465 
1466  m_NeedLoad = im->m_NeedLoad;
1467 
1468  d->m_updateLock.unlock();
1469 
1470  d->m_updateLock.lockForRead();
1471 
1472  if (m_NeedLoad)
1473  {
1474  d->m_updateLock.unlock();
1475  Load();
1476  }
1477  else
1478  d->m_updateLock.unlock();
1479 }
1480 
1485 {
1486  QReadLocker updateLocker(&d->m_updateLock);
1487  auto *im = new MythUIImage(parent, objectName());
1488  im->CopyFrom(this);
1489 }
1490 
1495 {
1496  d->m_updateLock.lockForRead();
1497 
1498  if (m_NeedLoad)
1499  {
1500  d->m_updateLock.unlock();
1501  Load();
1502  }
1503  else
1504  d->m_updateLock.unlock();
1505 
1507 }
1508 
1513 {
1514  d->m_updateLock.lockForWrite();
1515 
1516  if (m_NeedLoad)
1517  {
1518  d->m_updateLock.unlock();
1519  return;
1520  }
1521 
1522  m_NeedLoad = true;
1523  d->m_updateLock.unlock();
1524 
1525  Load(false);
1526 
1528 }
1529 
1533 void MythUIImage::customEvent(QEvent *event)
1534 {
1535  if (event->type() == ImageLoadEvent::kEventType)
1536  {
1537  auto *le = dynamic_cast<ImageLoadEvent *>(event);
1538  if (le->GetParent() != this)
1539  return;
1540 
1541  MythImage *image = le->GetImage();
1542  int number = le->GetNumber();
1543  QString filename = le->GetFilename();
1544  AnimationFrames *animationFrames = le->GetAnimationFrames();
1545  bool aborted = le->GetAbortState();
1546 
1547  m_runningThreads--;
1548 
1549  d->m_updateLock.lockForRead();
1550  QString propFilename = m_imageProperties.m_filename;
1551  d->m_updateLock.unlock();
1552 
1553  // 1) We aborted loading the image for some reason (e.g. two requests
1554  // for same image)
1555  // 2) Filename changed since we started this image, so abort to avoid
1556  // rendering two different images in quick succession which causes
1557  // unsightly flickering
1558  if (aborted || (le->GetBasefile() != propFilename))
1559  {
1560  if (aborted)
1561  LOG(VB_GUI, LOG_DEBUG, QString("Aborted loading image %1")
1562  .arg(filename));
1563 
1564  if (image)
1565  image->DecrRef();
1566 
1567  if (animationFrames)
1568  {
1569  for (const auto & frame : qAsConst(*animationFrames))
1570  {
1571  MythImage *im = frame.first;
1572  if (im)
1573  im->DecrRef();
1574  }
1575 
1576  delete animationFrames;
1577  }
1578  }
1579  else if (animationFrames)
1580  {
1581  SetAnimationFrames(*animationFrames);
1582 
1583  delete animationFrames;
1584  }
1585  else if (image)
1586  {
1587  // We don't clear until we have the new image ready to display to
1588  // avoid unsightly flashing. This isn't currently supported for
1589  // animations.
1590  if ((m_HighNum == m_LowNum) && !m_animatedImage)
1591  Clear();
1592 
1593  d->m_updateLock.lockForWrite();
1594 
1595  if (m_imageProperties.m_forceSize.isNull())
1596  SetSize(image->size());
1597 
1598  MythRect rect(GetFullArea());
1599  rect.setSize(image->size());
1600  SetMinArea(rect);
1601 
1602  d->m_updateLock.unlock();
1603 
1604  m_ImagesLock.lock();
1605 
1606  if (m_Images[number])
1607  {
1608  // If we got to this point, it means this same MythUIImage
1609  // was told to reload the same image, so we use the newest
1610  // copy of the image.
1611  m_Images[number]->DecrRef(); // delete the original
1612  }
1613 
1614  m_Images[number] = image;
1615  m_ImagesLock.unlock();
1616 
1617  SetRedraw();
1618 
1619  d->m_updateLock.lockForWrite();
1620  m_LastDisplay = QTime::currentTime();
1621  d->m_updateLock.unlock();
1622  }
1623  else
1624  {
1625  // No Images were loaded, so trigger Reset to default
1626  Reset();
1627  }
1628 
1629  // NOLINTNEXTLINE(readability-misleading-indentation)
1630  emit LoadComplete();
1631  }
1632 }
1633 
1635 {
1636  QDir imageDir(m_imageDirectory);
1637 
1638  if (!imageDir.exists())
1639  {
1640  QString themeDir = GetMythUI()->GetThemeDir() + '/';
1641  imageDir.setPath(themeDir + m_imageDirectory);
1642  }
1643 
1644  QStringList imageTypes;
1645 
1646  QList< QByteArray > exts = QImageReader::supportedImageFormats();
1647  for (const auto & ext : qAsConst(exts))
1648  {
1649  imageTypes.append(QString("*.").append(ext));
1650  }
1651 
1652  imageDir.setNameFilters(imageTypes);
1653 
1654  QStringList imageList = imageDir.entryList();
1655  QString randFile;
1656 
1657  if (!imageList.empty())
1658  {
1659  // try to find a different image
1660  do
1661  {
1662  randFile = QString("%1%2").arg(m_imageDirectory)
1663  .arg(imageList.takeAt(random() % imageList.size()));
1664 
1665  } while (imageList.size() > 1 && randFile == m_OrigFilename);
1666  }
1667 
1669 }
void SetFilepattern(const QString &filepattern, int low, int high)
Must be followed by a call to Load() to load the image.
MythImage * CacheImage(const QString &url, MythImage *im, bool nodisk=false)
void Clear(void)
Remove all images from the widget.
MythImage * m_image
~ImageLoader()=default
return token
Definition: musicutils.cpp:74
void CalculateArea(const MythRect &parentArea)
Definition: mythrect.cpp:32
QReadWriteLock m_updateLock
AnimationFrames * GetAnimationFrames() const
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
virtual MythPainter * GetPainter(void)
QString m_OrigFilename
Definition: mythuiimage.h:168
int CalcAlpha(int alphamod) const
Definition: mythuitype.cpp:458
bool Load(MythImageReader *reader)
Definition: mythimage.cpp:279
ImageCacheMode
Definition: mythuihelper.h:24
ImageProperties m_imageProperties
const MythUIImage * m_parent
int GetNumber() const
static void PostLoad(const QString &cacheKey)
MythUIImage * m_parent
static QMutex m_loadingImagesLock
void Finalize(void) override
Perform any post-xml parsing initialisation tasks.
ImageProperties m_imageProperties
Definition: mythuiimage.h:183
virtual void SetChanged(bool change=true)
Definition: mythimage.h:50
QRect GetScreenSettings(void)
void SetRedraw(void)
Definition: mythuitype.cpp:293
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
QRect GetExtent(const QSize &size) const
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:97
AnimationCycle m_animationCycle
Definition: mythuiimage.h:193
QString m_filename
Definition: mythuiimage.h:58
QTime m_LastDisplay
Definition: mythuiimage.h:179
void SetCropRect(int x, int y, int width, int height)
Crop the image using the given rectangle, useful for removing unsightly edges from imported images or...
ImageProperties & operator=(const ImageProperties &other)
Definition: mythuiimage.cpp:45
static QHash< QString, const MythUIImage * > m_loadingImages
QString GetMaskImageFilename()
Definition: mythuiimage.h:37
UIEffects m_Effects
Definition: mythuitype.h:257
void SetSize(int width, int height)
Set the size of the widget.
static QString GenImageLabel(const ImageProperties &imProps)
Generates a unique identifying string for this image which is used as a key in the image cache.
friend class MythUIImagePrivate
Definition: mythuiimage.h:197
void waitForDone(void)
void SetAnimationFrames(const AnimationFrames &frames)
static guint32 * tmp
Definition: goom_core.cpp:30
QString GetThemeDir(void)
void SetMaskImageFilename(const QString &filename)
Definition: mythuiimage.h:33
void setIsOriented(bool oriented)
Definition: mythimage.h:91
QString m_basefile
The base class on which all widgets and screens are based.
Definition: mythuitype.h:63
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
ReflectAxis m_reflectAxis
Definition: mythuiimage.h:69
QString GetXMLLocation(void) const
Definition: mythuitype.h:155
QString GetBasefile() const
bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings) override
Parse the xml definition of this widget setting the state of the object accordingly.
static bool PreLoad(const QString &cacheKey, const MythUIImage *uitype)
QString GetFilename() const
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
static QWaitCondition m_loadingImagesCond
bool IsReflected() const
Definition: mythimage.h:54
ImageLoadThread(MythUIImage *parent, MythPainter *painter, const ImageProperties &imProps, QString basefile, int number, ImageCacheMode mode)
void SetOrientation(int orientation)
Saves the exif orientation value of the first image in the widget.
void Copy(const ImageProperties &other)
Definition: mythuiimage.cpp:61
bool m_NeedLoad
Definition: mythuiimage.h:181
QRect toQRect(void) const
Definition: mythrect.cpp:362
QString m_maskImageFilename
Definition: mythuiimage.h:84
void Orientation(int orientation)
Changes the orientation angle of the image according to the exif rotation values.
Definition: mythimage.cpp:137
QString m_filename
bool m_animatedImage
Definition: mythuiimage.h:195
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
bool m_showingRandomImage
Definition: mythuiimage.h:187
virtual void SetMinArea(const MythRect &rect)
Set the minimum area based on the given size.
Definition: mythuitype.cpp:799
void customEvent(QEvent *event) override
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:864
void DependChanged(bool isDefault)
void SetMaskImage(MythImage *image)
Definition: mythuiimage.cpp:87
static QString getFirstText(QDomElement &element)
void SetImageCount(int low, int high)
Set the integer range for an animated image pattern.
static Type kEventType
virtual void SetSize(const QSize &size)
Definition: mythuitype.cpp:548
int IncrRef(void) override
Increments reference count.
Definition: mythimage.cpp:47
QHash< int, int > m_Delays
Definition: mythuiimage.h:171
bool IsOriented() const
Definition: mythimage.h:55
ImageLoadEvent(const MythUIImage *parent, AnimationFrames *frames, QString basefile, QString filename, bool aborted)
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
Definition: mythuitype.cpp:440
bool m_Initiator
Definition: mythuitype.h:241
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:17
static bool SupportsAnimation(const QString &filename)
void SetDelays(const QVector< int > &delays)
Sets the delays between each image in an animation.
void Reset(void) override
Reset the image back to the default defined in the theme.
MythRect m_Area
Definition: mythuitype.h:249
unsigned int uint
Definition: compat.h:140
AnimationFrames * m_images
ImageProperties()=default
ImageCacheMode m_cacheMode
QPair< MythImage *, int > AnimationFrame
Definition: mythuiimage.h:87
static AnimationFrames * LoadAnimatedImage(MythPainter *painter, const ImageProperties &imProps, ImageCacheMode cacheMode, const MythUIImage *parent, bool &aborted)
void ToGreyscale()
Definition: mythimage.cpp:263
MythUIHelper * GetMythUI()
#define LOC
Definition: mythuiimage.cpp:36
void SetDelay(int delayms)
Set the delay between each image in an animation.
MythUIImagePrivate * d
Definition: mythuiimage.h:190
virtual void LoadNow(void)
Cause images in this and child widgets to be loaded.
MythRect m_cropRect
Definition: mythuiimage.h:60
~MythUIImage() override
void FindRandomImage(void)
MythImage * LoadCacheImage(QString srcfile, const QString &label, MythPainter *painter, ImageCacheMode cacheMode=kCacheNormal)
Returns a reference counted image from the cache.
MythImage * GetImage() const
void LoadComplete()
friend class ImageLoadThread
Definition: mythuiimage.h:203
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:589
virtual void Reset(void)
Reset the widget to it's original state, should not reset changes made by the theme.
Definition: mythuitype.cpp:69
int m_runningThreads
Definition: mythuiimage.h:185
MythUIImagePrivate(MythUIImage *p)
QString m_imageDirectory
Definition: mythuiimage.h:188
void start(QRunnable *runnable, const QString &debugName, int priority=0)
bool GetAbortState() const
void CreateCopy(MythUIType *parent) override
Copy the state of this widget to the one given, it must be of the same type.
bool IsDeferredLoading(bool recurse=false) const
MythPainter * m_painter
QImage GetMaskImageSubset(const QRect &imageArea)
Definition: mythuiimage.h:48
void Reflect(ReflectAxis axis, int shear, int scale, int length, int spacing=0)
Definition: mythimage.cpp:168
virtual MythRect GetFullArea(void) const
Definition: mythuitype.cpp:872
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
bool m_preserveAspect
Definition: mythuiimage.h:63
MythUIImage * m_parent
bool m_animationReverse
Definition: mythuiimage.h:194
virtual void CopyFrom(MythUIType *base)
Copy this widgets state from another.
QHash< int, MythImage * > m_Images
Definition: mythuiimage.h:170
QVector< AnimationFrame > AnimationFrames
Definition: mythuiimage.h:88
MythUIType * m_Parent
Definition: mythuitype.h:270
~MythUIImagePrivate()=default
ImageLoadEvent(const MythUIImage *parent, MythImage *image, QString basefile, QString filename, int number, bool aborted)
static MythRect parseRect(const QString &text, bool normalize=true)
QString GetFileName(void) const
Definition: mythimage.h:88
unsigned int m_CurPos
Definition: mythuiimage.h:178
void DrawSelf(MythPainter *p, int xoffset, int yoffset, int alphaMod, QRect clipRect) override
void Resize(const QSize &newSize, bool preserveAspect=false)
Definition: mythimage.cpp:146
QMutex m_ImagesLock
Definition: mythuiimage.h:172
MythUIImage(const QString &filepattern, int low, int high, int delayms, MythUIType *parent, const QString &name)
static long int random(void)
Definition: compat.h:149
bool m_EnableInitiator
Definition: mythuitype.h:240
void ForceSize(const QSize &size)
Force the dimensions of the widget and image to the given size.
static MythImage * LoadImage(MythPainter *painter, ImageProperties imProps, ImageCacheMode cacheMode, const MythUIImage *parent, bool &aborted, MythImageReader *imageReader=nullptr)
MThreadPool * GetImageThreadPool(void)
QRect GetMaskImageRect(void)
Definition: mythuiimage.h:41
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
const MythUIImage * GetParent() const
MythImage * m_maskImage
Definition: mythuiimage.h:83
void run() override
ImageLoader()=default
static bool parseBool(const QString &text)
void setIsReflected(bool reflected)
Definition: mythimage.h:90
void SetImages(QVector< MythImage * > *images)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
virtual bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings)
Parse the xml definition of this widget setting the state of the object accordingly.
void Pulse(void) override
Pulse is called 70 times a second to trigger a single frame of an animation.
virtual void Finalize(void)
Perform any post-xml parsing initialisation tasks.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
void LoadNow(void) override
Cause images in this and child widgets to be loaded.