MythTV  master
mythimage.cpp
Go to the documentation of this file.
1 // Own header
2 #include "mythimage.h"
3 
4 // C++ headers
5 #include <cstdint>
6 #include <iostream>
7 
8 // QT headers
9 #include <QImageReader>
10 #include <QMatrix>
11 #include <QNetworkReply>
12 #include <QPainter>
13 #include <QRgb>
14 
15 // Mythdb headers
16 #include "mythdownloadmanager.h"
17 #include "mythlogging.h"
18 
19 // Myth headers
20 #include "remotefile.h"
21 
22 // MythUI headers
23 #include "mythuihelper.h"
24 #include "mythmainwindow.h"
25 
26 MythUIHelper *MythImage::s_ui = nullptr;
27 
28 MythImage::MythImage(MythPainter *parent, const char *name) :
29  ReferenceCounter(name)
30 {
31  if (!parent)
32  LOG(VB_GENERAL, LOG_ERR, "Image created without parent!");
33 
34  m_Parent = parent;
35  m_FileName = "";
36 
37  if (!s_ui)
38  s_ui = GetMythUI();
39 }
40 
42 {
43  if (m_Parent)
45 }
46 
48 {
49  int cnt = ReferenceCounter::IncrRef();
50  if ((2 == cnt) && s_ui && m_cached)
52  return cnt;
53 }
54 
56 {
57  bool cached = m_cached;
58  int cnt = ReferenceCounter::DecrRef();
59  if (cached)
60  {
61  if (s_ui && (1 == cnt))
62  s_ui->IncludeInCacheSize(this);
63 
64  if (0 == cnt)
65  {
66  LOG(VB_GENERAL, LOG_INFO,
67  "Image should be removed from cache prior to deletion.");
68  }
69  }
70  return cnt;
71 }
72 
73 void MythImage::SetIsInCache(bool bCached)
74 {
75  IncrRef();
76  m_cached = bCached;
77  DecrRef();
78 }
79 
80 void MythImage::Assign(const QImage &img)
81 {
82  if (m_cached)
83  {
84  SetIsInCache(false);
85  *(static_cast<QImage*>(this)) = img;
87  }
88  else
89  {
90  *(static_cast<QImage*>(this)) = img;
91  }
92  SetChanged();
93 }
94 
95 void MythImage::Assign(const QPixmap &pix)
96 {
97  Assign(pix.toImage());
98 }
99 
100 QImage MythImage::ApplyExifOrientation(QImage &image, int orientation)
101 {
102  QTransform transform;
103 
104  switch (orientation)
105  {
106  case 1: // normal
107  return image;
108  case 2: // mirror horizontal
109  return image.mirrored(true, false);
110  case 3: // rotate 180
111  transform.rotate(180);
112  return image.transformed(transform);
113  case 4: // mirror vertical
114  return image.mirrored(false, true);
115  case 5: // mirror horizontal and rotate 270 CCW
116  transform.rotate(270);
117  return image.mirrored(true, false).transformed(transform);
118  case 6: // rotate 90 CW
119  transform.rotate(90);
120  return image.transformed(transform);
121  case 7: // mirror horizontal and rotate 90 CW
122  transform.rotate(90);
123  return image.mirrored(true, false).transformed(transform);
124  case 8: // rotate 270 CW
125  transform.rotate(270);
126  return image.transformed(transform);
127  }
128  return image;
129 }
130 
137 void MythImage::Orientation(int orientation)
138 {
139  if (!m_isOriented)
140  {
141  Assign(ApplyExifOrientation(*this, orientation));
142  m_isOriented = true;
143  }
144 }
145 
146 void MythImage::Resize(const QSize &newSize, bool preserveAspect)
147 {
148  if ((size() == newSize) && !isNull())
149  return;
150 
151  if (m_isGradient)
152  {
153  *(static_cast<QImage *> (this)) = QImage(newSize, QImage::Format_ARGB32);
155  SetChanged();
156  }
157  else
158  {
159  Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio;
160  if (preserveAspect)
161  mode = Qt::KeepAspectRatio;
162 
163  Assign(scaled(newSize, mode, Qt::SmoothTransformation));
164  }
165 }
166 
167 void MythImage::Reflect(ReflectAxis axis, int shear, int scale, int length,
168  int spacing)
169 {
170  if (m_isReflected)
171  return;
172 
173  QImage mirrorImage;
174  FillDirection fillDirection = FillTopToBottom;
175  if (axis == ReflectVertical)
176  {
177  mirrorImage = mirrored(false,true);
178  if (length < 100)
179  {
180  int height = (int)((float)mirrorImage.height() * (float)length/100);
181  mirrorImage = mirrorImage.copy(0,0,mirrorImage.width(),height);
182  }
183  fillDirection = FillTopToBottom;
184  }
185  else if (axis == ReflectHorizontal)
186  {
187  mirrorImage = mirrored(true,false);
188  if (length < 100)
189  {
190  int width = (int)((float)mirrorImage.width() * (float)length/100);
191  mirrorImage = mirrorImage.copy(0,0,width,mirrorImage.height());
192  }
193  fillDirection = FillLeftToRight;
194  }
195 
196  QImage alphaChannel(mirrorImage.size(), QImage::Format_ARGB32);
197  MakeGradient(alphaChannel, QColor("#AAAAAA"), QColor("#000000"), 255,
198  false, fillDirection);
199  mirrorImage.setAlphaChannel(alphaChannel);
200 
201  QMatrix shearMatrix;
202  if (axis == ReflectVertical)
203  {
204  shearMatrix.scale(1,(float)scale/100);
205  shearMatrix.shear((float)shear/100,0);
206  }
207  else if (axis == ReflectHorizontal)
208  {
209  shearMatrix.scale((float)scale/100,1);
210  shearMatrix.shear(0,(float)shear/100);
211  }
212 
213  mirrorImage = mirrorImage.transformed(shearMatrix, Qt::SmoothTransformation);
214 
215  QSize newsize;
216  if (axis == ReflectVertical)
217  newsize = QSize(mirrorImage.width(), height()+spacing+mirrorImage.height());
218  else if (axis == ReflectHorizontal)
219  newsize = QSize(width()+spacing+mirrorImage.width(), mirrorImage.height());
220 
221  QImage temp(newsize, QImage::Format_ARGB32);
222  temp.fill(Qt::transparent);
223 
224  QPainter newpainter(&temp);
225  newpainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
226  if (axis == ReflectVertical)
227  {
228  if (shear < 0)
229  {
230  newpainter.drawImage(mirrorImage.width()-width(), 0,
231  *(static_cast<QImage*>(this)));
232  }
233  else
234  {
235  newpainter.drawImage(0, 0, *(static_cast<QImage*>(this)));
236  }
237 
238  newpainter.drawImage(0, height()+spacing, mirrorImage);
239  }
240  else if (axis == ReflectHorizontal)
241  {
242  if (shear < 0)
243  {
244  newpainter.drawImage(0, mirrorImage.height()-height(),
245  *(static_cast<QImage*>(this)));
246  }
247  else
248  {
249  newpainter.drawImage(0, 0, *(static_cast<QImage*>(this)));
250  }
251 
252  newpainter.drawImage(width()+spacing, 0, mirrorImage);
253  }
254 
255  newpainter.end();
256 
257  Assign(temp);
258 
259  m_isReflected = true;
260 }
261 
263 {
264  if (isGrayscale())
265  return;
266 
267  for (int y = 0; y < height(); ++y)
268  {
269  for (int x = 0; x < width(); ++x)
270  {
271  QRgb oldPixel = pixel(x, y);
272  int greyVal = qGray(oldPixel);
273  setPixel(x, y, qRgba(greyVal, greyVal, greyVal, qAlpha(oldPixel)));
274  }
275  }
276 }
277 
279 {
280  if (!reader || !reader->canRead())
281  return false;
282 
283  auto *im = new QImage;
284 
285  if (im && reader->read(im))
286  {
287  Assign(*im);
288  delete im;
289  return true;
290  }
291 
292  delete im;
293  return false;
294 }
295 
296 bool MythImage::Load(const QString &filename)
297 {
298  if (filename.isEmpty())
299  return false;
300 
301  QImage *im = nullptr;
302  if (filename.startsWith("myth://"))
303  {
304  // Attempting a file transfer on a file that doesn't exist throws
305  // a lot of noisy warnings on the backend and frontend, so to avoid
306  // that first check it's there
307  QUrl url(filename);
308  QString fname = url.path();
309 
310  if (url.hasFragment())
311  fname += '#' + url.fragment();
312 
313  QString mythUrl = RemoteFile::FindFile(fname, url.host(), url.userName());
314  if (!mythUrl.isEmpty())
315  {
316  auto *rf = new RemoteFile(mythUrl, false, false, 0);
317 
318  QByteArray data;
319  bool ret = rf->SaveAs(data);
320 
321  delete rf;
322 
323  if (ret)
324  {
325  im = new QImage();
326  im->loadFromData(data);
327  }
328  }
329 #if 0
330  else
331  LOG(VB_GENERAL, LOG_ERR,
332  QString("MythImage::Load failed to load remote image %1")
333  .arg(filename));
334 #endif
335 
336  }
337  else if ((filename.startsWith("http://")) ||
338  (filename.startsWith("https://")) ||
339  (filename.startsWith("ftp://")))
340  {
341  QByteArray data;
343  {
344  im = new QImage();
345  im->loadFromData(data);
346  }
347  }
348  else
349  {
350  QString path = filename;
351  if (path.startsWith('/') ||
352  GetMythUI()->FindThemeFile(path))
353  im = new QImage(path);
354  }
355 
356  if (im && im->isNull())
357  {
358  delete im;
359  im = nullptr;
360  }
361 
363  if (im)
364  {
365  Assign(*im);
366  delete im;
367  return true;
368  }
369  LOG(VB_GUI, LOG_WARNING, QString("MythImage::Load(%1) failed").arg(filename));
370 
371  return false;
372 }
373 
374 void MythImage::MakeGradient(QImage &image, const QColor &begin,
375  const QColor &end, int alpha, bool drawBoundary,
376  FillDirection direction)
377 {
378  // Gradient fill colours
379  QColor startColor = begin;
380  QColor endColor = end;
381  startColor.setAlpha(alpha);
382  endColor.setAlpha(alpha);
383 
384  // Define Gradient
385  QPoint pointA(0,0);
386  QPoint pointB;
387  if (direction == FillTopToBottom)
388  {
389  pointB = QPoint(0,image.height());
390  }
391  else if (direction == FillLeftToRight)
392  {
393  pointB = QPoint(image.width(),0);
394  }
395 
396  QLinearGradient gradient(pointA, pointB);
397  gradient.setColorAt(0, startColor);
398  gradient.setColorAt(1, endColor);
399 
400  // Draw Gradient
401  QPainter painter(&image);
402  painter.setCompositionMode(QPainter::CompositionMode_Source);
403  painter.fillRect(0, 0, image.width(), image.height(), gradient);
404 
405  if (drawBoundary)
406  {
407  // Draw boundary rect
408  QColor black(0, 0, 0, alpha);
409  painter.setPen(black);
410  QPen pen = painter.pen();
411  pen.setWidth(1);
412  painter.drawRect(image.rect());
413  }
414  painter.end();
415 }
416 
418  const QSize & size, const QColor &begin,
419  const QColor &end, uint alpha,
420  FillDirection direction)
421 {
422  QImage img(size.width(), size.height(), QImage::Format_ARGB32);
423 
424  MakeGradient(img, begin, end, alpha, true, direction);
425 
426  MythImage *ret = painter->GetFormatImage();
427  ret->Assign(img);
428  ret->m_isGradient = true;
429  ret->m_gradBegin = begin;
430  ret->m_gradEnd = end;
431  ret->m_gradAlpha = alpha;
432  ret->m_gradDirection = direction;
433  return ret;
434 }
435 
436 #define SCALEBITS 8
437 #define ONE_HALF (1 << (SCALEBITS - 1))
438 #define FIX(x) ((int) ((x) * (1L<<SCALEBITS) /*+ 0.5*/))
439 
441 {
442  if (m_isYUV)
443  return;
444 
445  m_isYUV = true;
446 
447  for (int i = 0; i < height(); i ++)
448  {
449  QRgb *data = (QRgb*)scanLine(i);
450  for (int j = 0; j < width(); j++)
451  {
452  int r = qRed(data[j]);
453  int g = qGreen(data[j]);
454  int b = qBlue(data[j]);
455  int a = qAlpha(data[j]);
456 
457  int r1 = (FIX(0.299) * r + FIX(0.587) * g +
458  FIX(0.114) * b + ONE_HALF) >> SCALEBITS;
459  int g1 = ((- FIX(0.169) * r - FIX(0.331) * g +
460  FIX(0.499) * b + ONE_HALF) >> SCALEBITS) + 128;
461  int b1 = ((FIX(0.499) * r - FIX(0.418) * g -
462  FIX(0.0813) * b + ONE_HALF) >> SCALEBITS) + 128;
463 
464  data[j] = qRgba(r1, g1, b1, a);
465  }
466  }
467 }
468 
470  : m_fileName(std::move(fileName))
471 {
472  if ((m_fileName.startsWith("http://")) ||
473  (m_fileName.startsWith("https://")) ||
474  (m_fileName.startsWith("ftp://")))
475  {
477  if (m_networkReply)
478  setDevice(m_networkReply);
479  }
480  else if (!m_fileName.isEmpty())
481  {
482  if (!m_fileName.startsWith("/") && !QFile::exists(m_fileName))
483  {
484  QString tmpFile = GetMythUI()->GetThemeDir() + '/' + m_fileName;
485  if (QFile::exists(tmpFile))
486  m_fileName = tmpFile;
487  }
488  setFileName(m_fileName);
489  }
490 }
491 
493 {
494  if (m_networkReply)
495  {
496  setDevice(nullptr);
497  m_networkReply->deleteLater();
498  m_networkReply = nullptr;
499  }
500 }
bool Load(MythImageReader *reader)
Definition: mythimage.cpp:278
MythImage(MythPainter *parent, const char *name="MythImage")
Creates a reference counted image, call DecrRef() to delete.
Definition: mythimage.cpp:28
#define ONE_HALF
Definition: mythimage.cpp:437
MythImageReader(QString fileName)
Definition: mythimage.cpp:469
virtual void SetChanged(bool change=true)
Definition: mythimage.h:49
void DeleteFormatImage(MythImage *im)
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
General purpose reference counter.
~MythImage() override
Definition: mythimage.cpp:41
static QString FindFile(const QString &filename, const QString &host, const QString &storageGroup, bool useRegex=false, bool allowFallback=false)
Search all BE's for a file in the give storage group.
static MythImage * Gradient(MythPainter *painter, const QSize &size, const QColor &begin, const QColor &end, uint alpha, FillDirection direction=FillTopToBottom)
Create a gradient image.
Definition: mythimage.cpp:417
bool download(const QString &url, const QString &dest, bool reload=false)
Downloads a URL to a file in blocking mode.
#define SCALEBITS
Definition: mythimage.cpp:436
QString GetThemeDir(void)
static MythUIHelper * s_ui
Definition: mythimage.h:132
ReflectAxis
Definition: mythimage.h:18
void SetIsInCache(bool bCached)
Definition: mythimage.cpp:73
bool FindThemeFile(QString &path)
bool m_isOriented
Definition: mythimage.h:122
int m_gradAlpha
Definition: mythimage.h:119
virtual int IncrRef(void)
Increments reference count.
void Orientation(int orientation)
Changes the orientation angle of the image according to the exif rotation values.
Definition: mythimage.cpp:137
QNetworkReply * m_networkReply
Definition: mythimage.h:32
bool m_isReflected
Definition: mythimage.h:123
int IncrRef(void) override
Increments reference count.
Definition: mythimage.cpp:47
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
MythDownloadManager * GetMythDownloadManager(void)
Gets the pointer to the MythDownloadManager singleton.
void ExcludeFromCacheSize(MythImage *im)
bool m_isGradient
Definition: mythimage.h:116
unsigned int uint
Definition: compat.h:140
QString m_fileName
Definition: mythimage.h:31
void ToGreyscale()
Definition: mythimage.cpp:262
MythUIHelper * GetMythUI()
void SetFileName(QString fname)
Definition: mythimage.h:89
FillDirection m_gradDirection
Definition: mythimage.h:120
bool m_cached
Definition: mythimage.h:130
QColor m_gradEnd
Definition: mythimage.h:118
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
void Reflect(ReflectAxis axis, int shear, int scale, int length, int spacing=0)
Definition: mythimage.cpp:167
static guint32 * pixel
--------------------------------------------------—**
Definition: goom_core.c:33
void IncludeInCacheSize(MythImage *im)
bool m_isYUV
Definition: mythimage.h:124
void ConvertToYUV(void)
Definition: mythimage.cpp:440
#define FIX(x)
Definition: mythimage.cpp:438
void Resize(const QSize &newSize, bool preserveAspect=false)
Definition: mythimage.cpp:146
MythPainter * m_Parent
Definition: mythimage.h:114
static void MakeGradient(QImage &image, const QColor &begin, const QColor &end, int alpha, bool drawBoundary=true, FillDirection direction=FillTopToBottom)
Definition: mythimage.cpp:374
void Assign(const QImage &img)
Definition: mythimage.cpp:80
QColor m_gradBegin
Definition: mythimage.h:117
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
QString m_FileName
Definition: mythimage.h:128
FillDirection
Definition: mythimage.h:19
static QImage ApplyExifOrientation(QImage &image, int orientation)
Definition: mythimage.cpp:100