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 <QPainter>
11 #include <QMatrix>
12 #include <QNetworkReply>
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) :
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  newpainter.drawImage(mirrorImage.width()-width(), 0,
230  *(static_cast<QImage*>(this)));
231  else
232  newpainter.drawImage(0, 0,
233  *(static_cast<QImage*>(this)));
234 
235  newpainter.drawImage(0, height()+spacing, mirrorImage);
236  }
237  else if (axis == ReflectHorizontal)
238  {
239  if (shear < 0)
240  newpainter.drawImage(0, mirrorImage.height()-height(),
241  *(static_cast<QImage*>(this)));
242  else
243  newpainter.drawImage(0, 0, *(static_cast<QImage*>(this)));
244 
245  newpainter.drawImage(width()+spacing, 0, mirrorImage);
246  }
247 
248  newpainter.end();
249 
250  Assign(temp);
251 
252  m_isReflected = true;
253 }
254 
256 {
257  if (isGrayscale())
258  return;
259 
260  for (int y = 0; y < height(); ++y)
261  {
262  for (int x = 0; x < width(); ++x)
263  {
264  QRgb oldPixel = pixel(x, y);
265  int greyVal = qGray(oldPixel);
266  setPixel(x, y, qRgba(greyVal, greyVal, greyVal, qAlpha(oldPixel)));
267  }
268  }
269 }
270 
272 {
273  if (!reader || !reader->canRead())
274  return false;
275 
276  QImage *im = new QImage;
277 
278  if (im && reader->read(im))
279  {
280  Assign(*im);
281  delete im;
282  return true;
283  }
284 
285  delete im;
286  return false;
287 }
288 
289 bool MythImage::Load(const QString &filename)
290 {
291  if (filename.isEmpty())
292  return false;
293 
294  QImage *im = nullptr;
295  if (filename.startsWith("myth://"))
296  {
297  // Attempting a file transfer on a file that doesn't exist throws
298  // a lot of noisy warnings on the backend and frontend, so to avoid
299  // that first check it's there
300  QUrl url(filename);
301  QString fname = url.path();
302 
303  if (url.hasFragment())
304  fname += '#' + url.fragment();
305 
306  QString mythUrl = RemoteFile::FindFile(fname, url.host(), url.userName());
307  if (!mythUrl.isEmpty())
308  {
309  RemoteFile *rf = new RemoteFile(mythUrl, false, false, 0);
310 
311  QByteArray data;
312  bool ret = rf->SaveAs(data);
313 
314  delete rf;
315 
316  if (ret)
317  {
318  im = new QImage();
319  im->loadFromData(data);
320  }
321  }
322 #if 0
323  else
324  LOG(VB_GENERAL, LOG_ERR,
325  QString("MythImage::Load failed to load remote image %1")
326  .arg(filename));
327 #endif
328 
329  }
330  else if ((filename.startsWith("http://")) ||
331  (filename.startsWith("https://")) ||
332  (filename.startsWith("ftp://")))
333  {
334  QByteArray data;
335  if (GetMythDownloadManager()->download(filename, &data))
336  {
337  im = new QImage();
338  im->loadFromData(data);
339  }
340  }
341  else
342  {
343  QString path = filename;
344  if (path.startsWith('/') ||
345  GetMythUI()->FindThemeFile(path))
346  im = new QImage(path);
347  }
348 
349  if (im && im->isNull())
350  {
351  delete im;
352  im = nullptr;
353  }
354 
355  SetFileName(filename);
356  if (im)
357  {
358  Assign(*im);
359  delete im;
360  return true;
361  }
362  LOG(VB_GUI, LOG_WARNING, QString("MythImage::Load(%1) failed").arg(filename));
363 
364  return false;
365 }
366 
367 void MythImage::MakeGradient(QImage &image, const QColor &begin,
368  const QColor &end, int alpha, bool drawBoundary,
369  FillDirection direction)
370 {
371  // Gradient fill colours
372  QColor startColor = begin;
373  QColor endColor = end;
374  startColor.setAlpha(alpha);
375  endColor.setAlpha(alpha);
376 
377  // Define Gradient
378  QPoint pointA(0,0);
379  QPoint pointB;
380  if (direction == FillTopToBottom)
381  {
382  pointB = QPoint(0,image.height());
383  }
384  else if (direction == FillLeftToRight)
385  {
386  pointB = QPoint(image.width(),0);
387  }
388 
389  QLinearGradient gradient(pointA, pointB);
390  gradient.setColorAt(0, startColor);
391  gradient.setColorAt(1, endColor);
392 
393  // Draw Gradient
394  QPainter painter(&image);
395  painter.setCompositionMode(QPainter::CompositionMode_Source);
396  painter.fillRect(0, 0, image.width(), image.height(), gradient);
397 
398  if (drawBoundary)
399  {
400  // Draw boundary rect
401  QColor black(0, 0, 0, alpha);
402  painter.setPen(black);
403  QPen pen = painter.pen();
404  pen.setWidth(1);
405  painter.drawRect(image.rect());
406  }
407  painter.end();
408 }
409 
411  const QSize & size, const QColor &begin,
412  const QColor &end, uint alpha,
413  FillDirection direction)
414 {
415  QImage img(size.width(), size.height(), QImage::Format_ARGB32);
416 
417  MakeGradient(img, begin, end, alpha, true, direction);
418 
419  MythImage *ret = painter->GetFormatImage();
420  ret->Assign(img);
421  ret->m_isGradient = true;
422  ret->m_gradBegin = begin;
423  ret->m_gradEnd = end;
424  ret->m_gradAlpha = alpha;
425  ret->m_gradDirection = direction;
426  return ret;
427 }
428 
429 #define SCALEBITS 8
430 #define ONE_HALF (1 << (SCALEBITS - 1))
431 #define FIX(x) ((int) ((x) * (1L<<SCALEBITS) /*+ 0.5*/))
432 
434 {
435  if (m_isYUV)
436  return;
437 
438  m_isYUV = true;
439 
440  int r, r1, g, g1, b, b1, a;
441  for (int i = 0; i < height(); i ++)
442  {
443  QRgb *data = (QRgb*)scanLine(i);
444  for (int j = 0; j < width(); j++)
445  {
446  r = qRed(data[j]);
447  g = qGreen(data[j]);
448  b = qBlue(data[j]);
449  a = qAlpha(data[j]);
450 
451  r1 = (FIX(0.299) * r + FIX(0.587) * g +
452  FIX(0.114) * b + ONE_HALF) >> SCALEBITS;
453  g1 = ((- FIX(0.169) * r - FIX(0.331) * g +
454  FIX(0.499) * b + ONE_HALF) >> SCALEBITS) + 128;
455  b1 = ((FIX(0.499) * r - FIX(0.418) * g -
456  FIX(0.0813) * b + ONE_HALF) >> SCALEBITS) + 128;
457 
458  data[j] = qRgba(r1, g1, b1, a);
459  }
460  }
461 }
462 
463 MythImageReader::MythImageReader(const QString &fileName)
464  : m_fileName(fileName)
465 {
466  if ((m_fileName.startsWith("http://")) ||
467  (m_fileName.startsWith("https://")) ||
468  (m_fileName.startsWith("ftp://")))
469  {
471  if (m_networkReply)
472  setDevice(m_networkReply);
473  }
474  else if (!m_fileName.isEmpty())
475  {
476  if (!m_fileName.startsWith("/") && !QFile::exists(m_fileName))
477  {
478  QString tmpFile = GetMythUI()->GetThemeDir() + '/' + m_fileName;
479  if (QFile::exists(tmpFile))
480  m_fileName = tmpFile;
481  }
482  setFileName(m_fileName);
483  }
484 }
485 
487 {
488  if (m_networkReply)
489  {
490  setDevice(nullptr);
491  m_networkReply->deleteLater();
492  m_networkReply = nullptr;
493  }
494 }
bool Load(MythImageReader *reader)
Definition: mythimage.cpp:271
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:430
virtual void SetChanged(bool change=true)
Definition: mythimage.h:45
void DeleteFormatImage(MythImage *im)
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
General purpose reference counter.
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:410
#define SCALEBITS
Definition: mythimage.cpp:429
unsigned int uint
Definition: compat.h:140
QString GetThemeDir(void)
static MythUIHelper * s_ui
Definition: mythimage.h:128
ReflectAxis
Definition: mythimage.h:14
unsigned char r
Definition: ParseText.cpp:329
virtual ~MythImage()
Definition: mythimage.cpp:41
void SetIsInCache(bool bCached)
Definition: mythimage.cpp:73
bool FindThemeFile(QString &path)
bool m_isOriented
Definition: mythimage.h:118
unsigned char b
Definition: ParseText.cpp:329
int m_gradAlpha
Definition: mythimage.h:115
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:28
bool m_isReflected
Definition: mythimage.h:119
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.
bool download(const QString &url, const QString &dest, const bool reload=false)
Downloads a URL to a file in blocking mode.
void ExcludeFromCacheSize(MythImage *im)
bool m_isGradient
Definition: mythimage.h:112
QString m_fileName
Definition: mythimage.h:27
const char * name
Definition: ParseText.cpp:328
void ToGreyscale()
Definition: mythimage.cpp:255
MythUIHelper * GetMythUI()
void SetFileName(QString fname)
Definition: mythimage.h:85
FillDirection m_gradDirection
Definition: mythimage.h:116
bool m_cached
Definition: mythimage.h:126
QColor m_gradEnd
Definition: mythimage.h:114
bool SaveAs(QByteArray &data)
#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:120
void ConvertToYUV(void)
Definition: mythimage.cpp:433
#define FIX(x)
Definition: mythimage.cpp:431
void Resize(const QSize &newSize, bool preserveAspect=false)
Definition: mythimage.cpp:146
MythPainter * m_Parent
Definition: mythimage.h:110
static void MakeGradient(QImage &image, const QColor &begin, const QColor &end, int alpha, bool drawBoundary=true, FillDirection direction=FillTopToBottom)
Definition: mythimage.cpp:367
void Assign(const QImage &img)
Definition: mythimage.cpp:80
QColor m_gradBegin
Definition: mythimage.h:113
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:124
MythImageReader(const QString &fileName)
Definition: mythimage.cpp:463
FillDirection
Definition: mythimage.h:15
unsigned char g
Definition: ParseText.cpp:329
static QImage ApplyExifOrientation(QImage &image, int orientation)
Definition: mythimage.cpp:100