MythTV  master
mythpainter.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 #include <complex>
3 #include <cstdint>
4 
5 // QT headers
6 #include <QRect>
7 #include <QPainter>
8 
9 // libmythbase headers
10 #include "mythlogging.h"
11 #include "compat.h"
12 #include "mythcorecontext.h"
13 
14 // libmythui headers
15 #include "mythfontproperties.h"
16 #include "mythimage.h"
17 #include "mythuianimation.h" // UIEffects
18 
19 // Own header
20 #include "mythpainter.h"
21 
23 {
24  SetMaximumCacheSizes(64, 48);
25 }
26 
28 {
29  ExpireImages(0);
30 
31  QMutexLocker locker(&m_allocationLock);
32 
33  if (!m_allocatedImages.isEmpty())
34  {
35  LOG(VB_GENERAL, LOG_WARNING,
36  QString("MythPainter: %1 images not yet de-allocated.")
37  .arg(m_allocatedImages.size()));
38  }
39 
40  foreach (auto image, m_allocatedImages)
41  image->SetParent(nullptr);
42  m_allocatedImages.clear();
43 }
44 
45 void MythPainter::SetClipRect(const QRect & /*clipRect*/)
46 {
47 }
48 
49 void MythPainter::SetClipRegion(const QRegion & /*clipRegion*/)
50 {
51 }
52 
53 void MythPainter::Clear(QPaintDevice */*device*/, const QRegion &/*region*/)
54 {
55 }
56 
57 void MythPainter::DrawImage(int x, int y, MythImage *im, int alpha)
58 {
59  if (!im)
60  {
61  LOG(VB_GENERAL, LOG_ERR,
62  "Null image pointer passed to MythPainter::DrawImage()");
63  return;
64  }
65  QRect dest = QRect(x, y, im->width(), im->height());
66  QRect src = im->rect();
67  DrawImage(dest, im, src, alpha);
68 }
69 
70 void MythPainter::DrawImage(const QPoint &topLeft, MythImage *im, int alpha)
71 {
72  DrawImage(topLeft.x(), topLeft.y(), im, alpha);
73 }
74 
75 void MythPainter::DrawText(const QRect &r, const QString &msg,
76  int flags, const MythFontProperties &font,
77  int alpha, const QRect &boundRect)
78 {
79  MythImage *im = GetImageFromString(msg, flags, r, font);
80  if (!im)
81  return;
82 
83  QRect destRect(boundRect);
84  QRect srcRect(0,0,r.width(),r.height());
85  if (!boundRect.isEmpty() && boundRect != r)
86  {
87  int x = 0;
88  int y = 0;
89  int width = boundRect.width();
90  int height = boundRect.height();
91 
92  if (boundRect.x() > r.x())
93  {
94  x = boundRect.x()-r.x();
95  }
96  else if (r.x() > boundRect.x())
97  {
98  destRect.setX(r.x());
99  width = (boundRect.x() + boundRect.width()) - r.x();
100  }
101 
102  if (boundRect.y() > r.y())
103  {
104  y = boundRect.y()-r.y();
105  }
106  else if (r.y() > boundRect.y())
107  {
108  destRect.setY(r.y());
109  height = (boundRect.y() + boundRect.height()) - r.y();
110  }
111 
112  if (width <= 0 || height <= 0)
113  return;
114 
115  srcRect.setRect(x,y,width,height);
116  }
117 
118  DrawImage(destRect, im, srcRect, alpha);
119  im->DecrRef();
120 }
121 
122 void MythPainter::DrawTextLayout(const QRect & canvasRect,
123  const LayoutVector & layouts,
124  const FormatVector & formats,
125  const MythFontProperties & font, int alpha,
126  const QRect & destRect)
127 {
128  if (canvasRect.isNull())
129  return;
130 
131  QRect canvas(canvasRect);
132  QRect dest(destRect);
133 
134  MythImage *im = GetImageFromTextLayout(layouts, formats, font,
135  canvas, dest);
136  if (!im)
137  {
138  LOG(VB_GENERAL, LOG_ERR, QString("MythPainter::DrawTextLayout: "
139  "Unable to create image."));
140  return;
141  }
142  if (im->isNull())
143  {
144  LOG(VB_GENERAL, LOG_DEBUG, QString("MythPainter::DrawTextLayout: "
145  "Rendered image is null."));
146  im->DecrRef();
147  return;
148  }
149 
150  QRect srcRect(0, 0, dest.width(), dest.height());
151  DrawImage(dest, im, srcRect, alpha);
152 
153  im->DecrRef();
154 }
155 
156 void MythPainter::DrawRect(const QRect &area, const QBrush &fillBrush,
157  const QPen &linePen, int alpha)
158 {
159  MythImage *im = GetImageFromRect(area, 0, 0, fillBrush, linePen);
160  if (im)
161  {
162  DrawImage(area.x(), area.y(), im, alpha);
163  im->DecrRef();
164  }
165 }
166 
167 void MythPainter::DrawRoundRect(const QRect &area, int cornerRadius,
168  const QBrush &fillBrush, const QPen &linePen,
169  int alpha)
170 {
171  MythImage *im = GetImageFromRect(area, cornerRadius, 0, fillBrush, linePen);
172  if (im)
173  {
174  DrawImage(area.x(), area.y(), im, alpha);
175  im->DecrRef();
176  }
177 }
178 
179 void MythPainter::DrawEllipse(const QRect &area, const QBrush &fillBrush,
180  const QPen &linePen, int alpha)
181 {
182  MythImage *im = GetImageFromRect(area, 0, 1, fillBrush, linePen);
183  if (im)
184  {
185  DrawImage(area.x(), area.y(), im, alpha);
186  im->DecrRef();
187  }
188 }
189 
190 void MythPainter::PushTransformation(const UIEffects &zoom, QPointF center)
191 {
192  (void)zoom;
193  (void)center;
194 }
195 
196 void MythPainter::DrawTextPriv(MythImage *im, const QString &msg, int flags,
197  const QRect &r, const MythFontProperties &font)
198 {
199  if (!im)
200  return;
201 
202  QColor outlineColor;
203  int outlineSize = 0;
204  int outlineAlpha = 255;
205  if (font.hasOutline())
206  font.GetOutline(outlineColor, outlineSize, outlineAlpha);
207 
208  QPoint shadowOffset(0, 0);
209  QColor shadowColor;
210  int shadowAlpha = 255;
211  if (font.hasShadow())
212  font.GetShadow(shadowOffset, shadowColor, shadowAlpha);
213 
214  QFontMetrics fm(font.face());
215  int totalHeight = fm.height() + outlineSize +
216  std::max(outlineSize, std::abs(shadowOffset.y()));
217 
218  // initialPaddingX is the number of pixels from the left of the
219  // input QRect to the left of the actual text. It is always 0
220  // because we don't add padding to the text rectangle.
221  int initialPaddingX = 0;
222 
223  // initialPaddingY is the number of pixels from the top of the
224  // input QRect to the top of the actual text. It may be nonzero
225  // because of extra vertical padding.
226  int initialPaddingY = (r.height() - totalHeight) / 2;
227  // Hack. Normally we vertically center the text due to some
228  // (solvable) issues in the SubtitleScreen code - the text rect
229  // and the background rect are both created with PAD_WIDTH extra
230  // padding, and to honor Qt::AlignTop, the text rect needs to be
231  // without padding. This doesn't work for Qt::TextWordWrap, since
232  // the first line will be vertically centered with subsequence
233  // lines below. So if Qt::TextWordWrap is set, we do top
234  // alignment.
235  if (flags & Qt::TextWordWrap)
236  initialPaddingY = 0;
237 
238  // textOffsetX is the number of pixels from r.left() to the left
239  // edge of the core text. This assumes that flags contains
240  // Qt::AlignLeft.
241  int textOffsetX =
242  initialPaddingX + std::max(outlineSize, -shadowOffset.x());
243 
244  // textOffsetY is the number of pixels from r.top() to the top
245  // edge of the core text. This assumes that flags contains
246  // Qt::AlignTop.
247  int textOffsetY =
248  initialPaddingY + std::max(outlineSize, -shadowOffset.y());
249 
250  QImage pm(r.size(), QImage::Format_ARGB32);
251  QColor fillcolor = font.color();
252  if (font.hasOutline())
253  fillcolor = outlineColor;
254  fillcolor.setAlpha(0);
255  pm.fill(fillcolor.rgba());
256 
257  QPainter tmp(&pm);
258  QFont tmpfont = font.face();
259  tmpfont.setStyleStrategy(QFont::OpenGLCompatible);
260  tmp.setFont(tmpfont);
261 
262  QPainterPath path;
263  if (font.hasOutline())
264  path.addText(0, 0, tmpfont, msg);
265 
266  if (font.hasShadow())
267  {
268  QRect a = QRect(0, 0, r.width(), r.height());
269  a.translate(shadowOffset.x() + textOffsetX,
270  shadowOffset.y() + textOffsetY);
271 
272  shadowColor.setAlpha(shadowAlpha);
273  tmp.setPen(shadowColor);
274  tmp.drawText(a, flags, msg);
275  }
276 
277  if (font.hasOutline())
278  {
279  // QPainter::drawText() treats the Y coordinate as the top of
280  // the text (when Qt::AlignTop is used). However,
281  // QPainterPath::addText() treats the Y coordinate as the base
282  // line of the text. To translate from the top to the base
283  // line, we need to add QFontMetrics::ascent().
284  int adjX = 0;
285  int adjY = fm.ascent();
286 
287  outlineColor.setAlpha(outlineAlpha);
288  tmp.setPen(outlineColor);
289 
290  path.translate(adjX + textOffsetX, adjY + textOffsetY);
291  QPen pen = tmp.pen();
292  pen.setWidth(outlineSize * 2 + 1);
293  pen.setCapStyle(Qt::RoundCap);
294  pen.setJoinStyle(Qt::RoundJoin);
295  tmp.setPen(pen);
296  tmp.drawPath(path);
297 
298  path.translate(outlineSize, outlineSize);
299  }
300 
301  tmp.setPen(QPen(font.GetBrush(), 0));
302  tmp.setBrush(font.GetBrush());
303  tmp.drawText(textOffsetX, textOffsetY, r.width(), r.height(),
304  flags, msg);
305  tmp.end();
306  im->Assign(pm);
307 }
308 
309 void MythPainter::DrawRectPriv(MythImage *im, const QRect &area, int radius,
310  int ellipse,
311  const QBrush &fillBrush, const QPen &linePen)
312 {
313  if (!im)
314  return;
315 
316  QImage image(QSize(area.width(), area.height()), QImage::Format_ARGB32);
317  image.fill(0x00000000);
318  QPainter painter(&image);
319  painter.setRenderHint(QPainter::Antialiasing);
320  painter.setPen(linePen);
321  painter.setBrush(fillBrush);
322 
323  if ((area.width() / 2) < radius)
324  radius = area.width() / 2;
325 
326  if ((area.height() / 2) < radius)
327  radius = area.height() / 2;
328 
329  int lineWidth = linePen.width();
330  QRect r(lineWidth, lineWidth,
331  area.width() - (lineWidth * 2), area.height() - (lineWidth * 2));
332 
333  if (ellipse)
334  painter.drawEllipse(r);
335  else if (radius == 0)
336  painter.drawRect(r);
337  else
338  painter.drawRoundedRect(r, (qreal)radius, qreal(radius));
339 
340  painter.end();
341  im->Assign(image);
342 }
343 
345  int flags, const QRect &r,
346  const MythFontProperties &font)
347 {
348  QString incoming = font.GetHash() + QString::number(r.width()) +
349  QString::number(r.height()) +
350  QString::number(flags) +
351  QString::number(font.color().rgba()) + msg;
352 
353  MythImage *im = nullptr;
354  if (m_stringToImageMap.contains(incoming))
355  {
356  m_stringExpireList.remove(incoming);
357  m_stringExpireList.push_back(incoming);
358  im = m_stringToImageMap[incoming];
359  if (im)
360  im->IncrRef();
361  }
362  else
363  {
364  im = GetFormatImage();
365  im->SetFileName(QString("GetImageFromString: %1").arg(msg));
366  DrawTextPriv(im, msg, flags, r, font);
367 
368  im->IncrRef();
369  m_softwareCacheSize += im->bytesPerLine() * im->height();
370  m_stringToImageMap[incoming] = im;
371  m_stringExpireList.push_back(incoming);
373  }
374  return im;
375 }
376 
378  const FormatVector &formats,
379  const MythFontProperties &font,
380  QRect &canvas, QRect &dest)
381 {
382  QString incoming = QString::number(canvas.x()) +
383  QString::number(canvas.y()) +
384  QString::number(canvas.width()) +
385  QString::number(canvas.height()) +
386  QString::number(dest.width()) +
387  QString::number(dest.height()) +
388  font.GetHash();
389 
390  foreach (auto layout, layouts)
391  incoming += layout->text();
392 
393  MythImage *im = nullptr;
394  if (m_stringToImageMap.contains(incoming))
395  {
396  m_stringExpireList.remove(incoming);
397  m_stringExpireList.push_back(incoming);
398  im = m_stringToImageMap[incoming];
399  if (im)
400  im->IncrRef();
401  }
402  else
403  {
404  im = GetFormatImage();
405  im->SetFileName("GetImageFromTextLayout");
406 
407  QImage pm(canvas.size(), QImage::Format_ARGB32_Premultiplied);
408  pm.fill(0);
409 
410  QPainter painter(&pm);
411  if (!painter.isActive())
412  {
413  LOG(VB_GENERAL, LOG_ERR, "MythPainter::GetImageFromTextLayout: "
414  "Invalid canvas.");
415  return im;
416  }
417 
418  QRect clip;
419  clip.setSize(canvas.size());
420 
421  QFont tmpfont = font.face();
422  tmpfont.setStyleStrategy(QFont::OpenGLCompatible);
423  painter.setFont(tmpfont);
424  painter.setRenderHint(QPainter::Antialiasing);
425 
426  if (font.hasShadow())
427  {
428  QRect shadowRect;
429  QPoint shadowOffset;
430  QColor shadowColor;
431  int shadowAlpha = 255;
432 
433  font.GetShadow(shadowOffset, shadowColor, shadowAlpha);
434  shadowColor.setAlpha(shadowAlpha);
435 
436  MythPoint shadow(shadowOffset);
437  shadow.NormPoint(); // scale it to screen resolution
438 
439  shadowRect = canvas;
440  shadowRect.translate(shadow.x(), shadow.y());
441 
442  painter.setPen(shadowColor);
443  foreach (auto layout, layouts)
444  layout->draw(&painter, shadowRect.topLeft(), formats, clip);
445  }
446 
447  painter.setPen(QPen(font.GetBrush(), 0));
448  foreach (auto layout, layouts)
449  {
450 #if QT_VERSION >= QT_VERSION_CHECK(5,6,0)
451  layout->draw(&painter, canvas.topLeft(),
452  layout->formats(), clip);
453 #else
454  layout->draw(&painter, canvas.topLeft(), formats, clip);
455 #endif
456  }
457  painter.end();
458 
459  pm.setOffset(canvas.topLeft());
460  im->Assign(pm.copy(0, 0, dest.width(), dest.height()));
461 
462  im->IncrRef();
463  m_softwareCacheSize += im->bytesPerLine() * im->height();
464  m_stringToImageMap[incoming] = im;
465  m_stringExpireList.push_back(incoming);
467  }
468  return im;
469 }
470 
471 MythImage* MythPainter::GetImageFromRect(const QRect &area, int radius,
472  int ellipse,
473  const QBrush &fillBrush,
474  const QPen &linePen)
475 {
476  if (area.width() <= 0 || area.height() <= 0)
477  return nullptr;
478 
479  uint64_t hash1 = ((0xfff & (uint64_t)area.width())) +
480  ((0xfff & (uint64_t)area.height()) << 12) +
481  ((0xff & (uint64_t)fillBrush.style()) << 24) +
482  ((0xff & (uint64_t)linePen.width()) << 32) +
483  ((0xff & (uint64_t)radius) << 40) +
484  ((0xff & (uint64_t)linePen.style()) << 48) +
485  ((0xff & (uint64_t)ellipse) << 56);
486  uint64_t hash2 = ((0xffffffff & (uint64_t)linePen.color().rgba())) +
487  ((0xffffffff & (uint64_t)fillBrush.color().rgba()) << 32);
488 
489  QString incoming("R");
490  if (fillBrush.style() == Qt::LinearGradientPattern && fillBrush.gradient())
491  {
492  const auto *gradient = static_cast<const QLinearGradient*>(fillBrush.gradient());
493  if (gradient)
494  {
495  incoming = QString::number(
496  ((0xfff & (uint64_t)gradient->start().x())) +
497  ((0xfff & (uint64_t)gradient->start().y()) << 12) +
498  ((0xfff & (uint64_t)gradient->finalStop().x()) << 24) +
499  ((0xfff & (uint64_t)gradient->finalStop().y()) << 36));
500  QGradientStops stops = gradient->stops();
501  foreach (auto & stop, stops)
502  {
503  incoming += QString::number(
504  ((0xfff * (uint64_t)(stop.first * 100))) +
505  ((uint64_t)stop.second.rgba() << 12));
506  }
507  }
508  }
509 
510  incoming += QString::number(hash1) + QString::number(hash2);
511 
512  MythImage *im = nullptr;
513  if (m_stringToImageMap.contains(incoming))
514  {
515  m_stringExpireList.remove(incoming);
516  m_stringExpireList.push_back(incoming);
517  im = m_stringToImageMap[incoming];
518  if (im)
519  im->IncrRef();
520  }
521  else
522  {
523  im = GetFormatImage();
524  im->SetFileName("GetImageFromRect");
525  DrawRectPriv(im, area, radius, ellipse, fillBrush, linePen);
526 
527  im->IncrRef();
528  m_softwareCacheSize += (im->bytesPerLine() * im->height());
529  m_stringToImageMap[incoming] = im;
530  m_stringExpireList.push_back(incoming);
532  }
533  return im;
534 }
535 
537 {
538  QMutexLocker locker(&m_allocationLock);
539  MythImage *result = GetFormatImagePriv();
540  result->SetFileName("GetFormatImage");
541  m_allocatedImages.insert(result);
542  return result;
543 }
544 
546 {
547  QMutexLocker locker(&m_allocationLock);
549  m_allocatedImages.remove(im);
550 }
551 
553 {
554  if (im && !im->GetParent())
555  {
556  QMutexLocker locker(&m_allocationLock);
557  m_allocatedImages.insert(im);
558  im->SetParent(this);
559  }
560 }
561 
562 void MythPainter::ExpireImages(int64_t max)
563 {
564  bool recompute = false;
565  while (!m_stringExpireList.empty())
566  {
567  if (m_softwareCacheSize < max)
568  break;
569 
570  QString oldmsg = m_stringExpireList.front();
571  m_stringExpireList.pop_front();
572 
573  QMap<QString, MythImage*>::iterator it =
574  m_stringToImageMap.find(oldmsg);
575  if (it == m_stringToImageMap.end())
576  {
577  recompute = true;
578  continue;
579  }
580  MythImage *oldim = *it;
581  it = m_stringToImageMap.erase(it);
582 
583  if (oldim)
584  {
585  m_softwareCacheSize -= oldim->bytesPerLine() * oldim->height();
586  if (m_softwareCacheSize < 0)
587  {
589  recompute = true;
590  }
591  oldim->DecrRef();
592  }
593  }
594  if (recompute)
595  {
597  foreach (auto & img, m_stringToImageMap)
598  m_softwareCacheSize += img->bytesPerLine() * img->height();
599  }
600 }
601 
602 // the following assume graphics hardware operates natively at 32bpp
603 void MythPainter::SetMaximumCacheSizes(int hardware, int software)
604 {
605  const int64_t kOneMeg = 1024 * 1024;
606  m_maxHardwareCacheSize = kOneMeg * hardware;
607  m_maxSoftwareCacheSize = kOneMeg * software;
608 
609  bool err = false;
610  if (m_maxHardwareCacheSize < 0)
611  {
613  err = true;
614  }
615  if (m_maxSoftwareCacheSize < 0)
616  {
617  m_maxSoftwareCacheSize = kOneMeg * 48;
618  err = true;
619  }
620 
621  LOG((err) ? VB_GENERAL : VB_GUI, (err) ? LOG_ERR : LOG_INFO,
622  QString("MythPainter cache sizes: Hardware %1MB, Software %2MB")
623  .arg(m_maxHardwareCacheSize / kOneMeg)
624  .arg(m_maxSoftwareCacheSize / kOneMeg));
625 }
int m_maxHardwareCacheSize
Definition: mythpainter.h:130
virtual void Teardown(void)
Definition: mythpainter.cpp:27
QColor color(void) const
MythImage * GetImageFromTextLayout(const LayoutVector &layouts, const FormatVector &formats, const MythFontProperties &font, QRect &canvas, QRect &dest)
static void DrawRectPriv(MythImage *im, const QRect &area, int radius, int ellipse, const QBrush &fillBrush, const QPen &linePen)
int64_t m_softwareCacheSize
Definition: mythpainter.h:133
QVector< QTextLayout * > LayoutVector
Definition: mythpainter.h:29
void DeleteFormatImage(MythImage *im)
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
QSet< MythImage * > m_allocatedImages
Definition: mythpainter.h:137
virtual void DrawEllipse(const QRect &area, const QBrush &fillBrush, const QPen &linePen, int alpha)
void GetShadow(QPoint &offset, QColor &color, int &alpha) const
int64_t m_maxSoftwareCacheSize
Definition: mythpainter.h:134
QString GetHash(void) const
virtual MythImage * GetFormatImagePriv(void)=0
Creates a reference counted image, call DecrRef() to delete.
std::list< QString > m_stringExpireList
Definition: mythpainter.h:140
virtual void DrawRoundRect(const QRect &area, int cornerRadius, const QBrush &fillBrush, const QPen &linePen, int alpha)
const char * formats[8]
Definition: vbilut.cpp:190
static guint32 * tmp
Definition: goom_core.c:35
QMutex m_allocationLock
Definition: mythpainter.h:136
static void DrawTextPriv(MythImage *im, const QString &msg, int flags, const QRect &r, const MythFontProperties &font)
MythPainter * GetParent(void)
Definition: mythimage.h:43
MythImage * GetImageFromRect(const QRect &area, int radius, int ellipse, const QBrush &fillBrush, const QPen &linePen)
QVector< QTextLayout::FormatRange > FormatVector
Definition: mythpainter.h:30
QFont face(void) const
virtual void PushTransformation(const UIEffects &zoom, QPointF center=QPointF())
virtual void SetClipRegion(const QRegion &clipRegion)
Definition: mythpainter.cpp:49
int IncrRef(void) override
Increments reference count.
Definition: mythimage.cpp:47
virtual void DrawRect(const QRect &area, const QBrush &fillBrush, const QPen &linePen, int alpha)
QMap< QString, MythImage * > m_stringToImageMap
Definition: mythpainter.h:139
void ExpireImages(int64_t max=0)
virtual void Clear(QPaintDevice *device, const QRegion &region)
Definition: mythpainter.cpp:53
virtual void DrawText(const QRect &r, const QString &msg, int flags, const MythFontProperties &font, int alpha, const QRect &boundRect)
Definition: mythpainter.cpp:75
virtual void DeleteFormatImagePriv(MythImage *im)=0
MythImage * GetImageFromString(const QString &msg, int flags, const QRect &r, const MythFontProperties &font)
void SetFileName(QString fname)
Definition: mythimage.h:89
void GetOutline(QColor &color, int &size, int &alpha) const
virtual void DrawImage(const QRect &dest, MythImage *im, const QRect &src, int alpha)=0
bool hasOutline(void) const
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
virtual void DrawTextLayout(const QRect &canvasRect, const LayoutVector &layouts, const FormatVector &formats, const MythFontProperties &font, int alpha, const QRect &destRect)
void CheckFormatImage(MythImage *im)
QBrush GetBrush(void) const
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
Definition: mythrect.h:85
void Assign(const QImage &img)
Definition: mythimage.cpp:80
bool hasShadow(void) const
void SetParent(MythPainter *parent)
Definition: mythimage.h:44
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
void NormPoint(void)
Definition: mythrect.cpp:399
virtual void SetClipRect(const QRect &clipRect)
Definition: mythpainter.cpp:45
void SetMaximumCacheSizes(int hardware, int software)