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#include <QPainterPath>
9
10// libmythbase headers
11#include "libmythbase/compat.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{
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 for (auto *image : std::as_const(m_allocatedImages))
41 image->SetParent(nullptr);
42 m_allocatedImages.clear();
43}
44
45void MythPainter::SetClipRect(const QRect /*clipRect*/)
46{
47}
48
49void MythPainter::SetClipRegion(const QRegion & /*clipRegion*/)
50{
51}
52
53void MythPainter::Clear(QPaintDevice */*device*/, const QRegion &/*region*/)
54{
55}
56
57void 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
70void MythPainter::DrawImage(const QPoint topLeft, MythImage *im, int alpha)
71{
72 DrawImage(topLeft.x(), topLeft.y(), im, alpha);
73}
74
75void 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
122void 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
156void 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
167void 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
179void 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
190void MythPainter::DrawTextPriv(MythImage *im, const QString &msg, int flags,
191 const QRect r, const MythFontProperties &font)
192{
193 if (!im)
194 return;
195
196 QColor outlineColor;
197 int outlineSize = 0;
198 int outlineAlpha = 255;
199 if (font.hasOutline())
200 font.GetOutline(outlineColor, outlineSize, outlineAlpha);
201
202 QPoint shadowOffset(0, 0);
203 QColor shadowColor;
204 int shadowAlpha = 255;
205 if (font.hasShadow())
206 font.GetShadow(shadowOffset, shadowColor, shadowAlpha);
207
208 QFontMetrics fm(font.face());
209 int totalHeight = fm.height() + outlineSize +
210 std::max(outlineSize, std::abs(shadowOffset.y()));
211
212 // initialPaddingX is the number of pixels from the left of the
213 // input QRect to the left of the actual text. It is always 0
214 // because we don't add padding to the text rectangle.
215 int initialPaddingX = 0;
216
217 // initialPaddingY is the number of pixels from the top of the
218 // input QRect to the top of the actual text. It may be nonzero
219 // because of extra vertical padding.
220 int initialPaddingY = (r.height() - totalHeight) / 2;
221 // Hack. Normally we vertically center the text due to some
222 // (solvable) issues in the SubtitleScreen code - the text rect
223 // and the background rect are both created with PAD_WIDTH extra
224 // padding, and to honor Qt::AlignTop, the text rect needs to be
225 // without padding. This doesn't work for Qt::TextWordWrap, since
226 // the first line will be vertically centered with subsequence
227 // lines below. So if Qt::TextWordWrap is set, we do top
228 // alignment.
229 if (flags & Qt::TextWordWrap)
230 initialPaddingY = 0;
231
232 // textOffsetX is the number of pixels from r.left() to the left
233 // edge of the core text. This assumes that flags contains
234 // Qt::AlignLeft.
235 int textOffsetX =
236 initialPaddingX + std::max(outlineSize, -shadowOffset.x());
237
238 // textOffsetY is the number of pixels from r.top() to the top
239 // edge of the core text. This assumes that flags contains
240 // Qt::AlignTop.
241 int textOffsetY =
242 initialPaddingY + std::max(outlineSize, -shadowOffset.y());
243
244 QImage pm(r.size(), QImage::Format_ARGB32);
245 QColor fillcolor = font.color();
246 if (font.hasOutline())
247 fillcolor = outlineColor;
248 fillcolor.setAlpha(0);
249 pm.fill(fillcolor.rgba());
250
251 QPainter tmp(&pm);
252 QFont tmpfont = font.face();
253 tmp.setFont(tmpfont);
254
255 QPainterPath path;
256 if (font.hasOutline())
257 path.addText(0, 0, tmpfont, msg);
258
259 if (font.hasShadow())
260 {
261 QRect a = QRect(0, 0, r.width(), r.height());
262 a.translate(shadowOffset.x() + textOffsetX,
263 shadowOffset.y() + textOffsetY);
264
265 shadowColor.setAlpha(shadowAlpha);
266 tmp.setPen(shadowColor);
267 tmp.drawText(a, flags, msg);
268 }
269
270 if (font.hasOutline())
271 {
272 // QPainter::drawText() treats the Y coordinate as the top of
273 // the text (when Qt::AlignTop is used). However,
274 // QPainterPath::addText() treats the Y coordinate as the base
275 // line of the text. To translate from the top to the base
276 // line, we need to add QFontMetrics::ascent().
277 int adjX = 0;
278 int adjY = fm.ascent();
279
280 outlineColor.setAlpha(outlineAlpha);
281 tmp.setPen(outlineColor);
282
283 path.translate(adjX + textOffsetX, adjY + textOffsetY);
284 QPen pen = tmp.pen();
285 pen.setWidth((outlineSize * 2) + 1);
286 pen.setCapStyle(Qt::RoundCap);
287 pen.setJoinStyle(Qt::RoundJoin);
288 tmp.setPen(pen);
289 tmp.drawPath(path);
290
291 path.translate(outlineSize, outlineSize);
292 }
293
294 tmp.setPen(QPen(font.GetBrush(), 0));
295 tmp.setBrush(font.GetBrush());
296 tmp.drawText(textOffsetX, textOffsetY, r.width(), r.height(),
297 flags, msg);
298 tmp.end();
299 im->Assign(pm);
300}
301
302void MythPainter::DrawRectPriv(MythImage *im, const QRect area, int radius,
303 int ellipse,
304 const QBrush &fillBrush, const QPen &linePen)
305{
306 if (!im)
307 return;
308
309 QImage image(QSize(area.width(), area.height()), QImage::Format_ARGB32);
310 image.fill(0x00000000);
311 QPainter painter(&image);
312 painter.setRenderHint(QPainter::Antialiasing);
313 painter.setPen(linePen);
314 painter.setBrush(fillBrush);
315
316 radius = std::min(area.width() / 2, radius);
317 radius = std::min(area.height() / 2, radius);
318
319 int lineWidth = linePen.width();
320 QRect r(lineWidth, lineWidth,
321 area.width() - (lineWidth * 2), area.height() - (lineWidth * 2));
322
323 if (ellipse)
324 painter.drawEllipse(r);
325 else if (radius == 0)
326 painter.drawRect(r);
327 else
328 painter.drawRoundedRect(r, (qreal)radius, qreal(radius));
329
330 painter.end();
331 im->Assign(image);
332}
333
335 int flags, const QRect r,
336 const MythFontProperties &font)
337{
338 QString incoming = font.GetHash() + QString::number(r.width()) +
339 QString::number(r.height()) +
340 QString::number(flags) +
341 QString::number(font.color().rgba()) + msg;
342
343 MythImage *im = nullptr;
344 if (m_stringToImageMap.contains(incoming))
345 {
346 m_stringExpireList.remove(incoming);
347 m_stringExpireList.push_back(incoming);
348 im = m_stringToImageMap[incoming];
349 if (im)
350 im->IncrRef();
351 }
352 else
353 {
354 im = GetFormatImage();
355 im->SetFileName(QString("GetImageFromString: %1").arg(msg));
356 DrawTextPriv(im, msg, flags, r, font);
357
358 im->IncrRef();
360 m_stringToImageMap[incoming] = im;
361 m_stringExpireList.push_back(incoming);
363 }
364 return im;
365}
366
368 const FormatVector &formats,
369 const MythFontProperties &font,
370 QRect &canvas, QRect &dest)
371{
372 QString incoming = QString::number(canvas.x()) +
373 QString::number(canvas.y()) +
374 QString::number(canvas.width()) +
375 QString::number(canvas.height()) +
376 QString::number(dest.width()) +
377 QString::number(dest.height()) +
378 font.GetHash();
379
380 for (auto *layout : std::as_const(layouts))
381 incoming += layout->text();
382
383 MythImage *im = nullptr;
384 if (m_stringToImageMap.contains(incoming))
385 {
386 m_stringExpireList.remove(incoming);
387 m_stringExpireList.push_back(incoming);
388 im = m_stringToImageMap[incoming];
389 if (im)
390 im->IncrRef();
391 }
392 else
393 {
394 im = GetFormatImage();
395 im->SetFileName("GetImageFromTextLayout");
396
397 QImage pm(canvas.size(), QImage::Format_ARGB32_Premultiplied);
398 pm.fill(0);
399
400 QPainter painter(&pm);
401 if (!painter.isActive())
402 {
403 LOG(VB_GENERAL, LOG_ERR, "MythPainter::GetImageFromTextLayout: "
404 "Invalid canvas.");
405 return im;
406 }
407
408 QRect clip;
409 clip.setSize(canvas.size());
410
411 QFont tmpfont = font.face();
412 painter.setFont(tmpfont);
413 painter.setRenderHint(QPainter::Antialiasing);
414
415 if (font.hasShadow())
416 {
417 QRect shadowRect;
418 QPoint shadowOffset;
419 QColor shadowColor;
420 int shadowAlpha = 255;
421
422 font.GetShadow(shadowOffset, shadowColor, shadowAlpha);
423 shadowColor.setAlpha(shadowAlpha);
424
425 MythPoint shadow(shadowOffset);
426 shadow.NormPoint(); // scale it to screen resolution
427
428 shadowRect = canvas;
429 shadowRect.translate(shadow.x(), shadow.y());
430
431 painter.setPen(shadowColor);
432 for (auto *layout : std::as_const(layouts))
433 layout->draw(&painter, shadowRect.topLeft(), formats, clip);
434 }
435
436 painter.setPen(QPen(font.GetBrush(), 0));
437 for (auto *layout : std::as_const(layouts))
438 {
439 layout->draw(&painter, canvas.topLeft(),
440 layout->formats(), clip);
441 }
442 painter.end();
443
444 pm.setOffset(canvas.topLeft());
445 im->Assign(pm.copy(0, 0, dest.width(), dest.height()));
446
447 im->IncrRef();
449 m_stringToImageMap[incoming] = im;
450 m_stringExpireList.push_back(incoming);
452 }
453 return im;
454}
455
456MythImage* MythPainter::GetImageFromRect(const QRect area, int radius,
457 int ellipse,
458 const QBrush &fillBrush,
459 const QPen &linePen)
460{
461 if (area.width() <= 0 || area.height() <= 0)
462 return nullptr;
463
464 uint64_t hash1 = ((0xfff & (uint64_t)area.width())) +
465 ((0xfff & (uint64_t)area.height()) << 12) +
466 ((0xff & (uint64_t)fillBrush.style()) << 24) +
467 ((0xff & (uint64_t)linePen.width()) << 32) +
468 ((0xff & (uint64_t)radius) << 40) +
469 ((0xff & (uint64_t)linePen.style()) << 48) +
470 ((0xff & (uint64_t)ellipse) << 56);
471 uint64_t hash2 = ((0xffffffff & (uint64_t)linePen.color().rgba())) +
472 ((0xffffffff & (uint64_t)fillBrush.color().rgba()) << 32);
473
474 QString incoming("R");
475 if (fillBrush.style() == Qt::LinearGradientPattern && fillBrush.gradient())
476 {
477 // The Q*Gradient classes are not polymorohic, and therefore
478 // dynamic_cast can't be used here.
479 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast)
480 const auto *gradient = static_cast<const QLinearGradient*>(fillBrush.gradient());
481 if (gradient)
482 {
483 incoming = QString::number(
484 ((0xfff & (uint64_t)gradient->start().x())) +
485 ((0xfff & (uint64_t)gradient->start().y()) << 12) +
486 ((0xfff & (uint64_t)gradient->finalStop().x()) << 24) +
487 ((0xfff & (uint64_t)gradient->finalStop().y()) << 36));
488 QGradientStops stops = gradient->stops();
489 for (const auto & stop : std::as_const(stops))
490 {
491 incoming += QString::number(
492 ((0xfff * (uint64_t)(stop.first * 100))) +
493 ((uint64_t)stop.second.rgba() << 12));
494 }
495 }
496 }
497
498 incoming += QString::number(hash1) + QString::number(hash2);
499
500 MythImage *im = nullptr;
501 if (m_stringToImageMap.contains(incoming))
502 {
503 m_stringExpireList.remove(incoming);
504 m_stringExpireList.push_back(incoming);
505 im = m_stringToImageMap[incoming];
506 if (im)
507 im->IncrRef();
508 }
509 else
510 {
511 im = GetFormatImage();
512 im->SetFileName("GetImageFromRect");
513 DrawRectPriv(im, area, radius, ellipse, fillBrush, linePen);
514
515 im->IncrRef();
517 m_stringToImageMap[incoming] = im;
518 m_stringExpireList.push_back(incoming);
520 }
521 return im;
522}
523
525{
526 QMutexLocker locker(&m_allocationLock);
527 MythImage *result = GetFormatImagePriv();
528 result->SetFileName("GetFormatImage");
529 m_allocatedImages.insert(result);
530 return result;
531}
532
534{
535 QMutexLocker locker(&m_allocationLock);
537 m_allocatedImages.remove(im);
538}
539
541{
542 if (im && !im->GetParent())
543 {
544 QMutexLocker locker(&m_allocationLock);
545 m_allocatedImages.insert(im);
546 im->SetParent(this);
547 }
548}
549
551{
552 bool recompute = false;
553 while (!m_stringExpireList.empty())
554 {
555 if (m_softwareCacheSize < max)
556 break;
557
558 QString oldmsg = m_stringExpireList.front();
559 m_stringExpireList.pop_front();
560
561 QMap<QString, MythImage*>::iterator it =
562 m_stringToImageMap.find(oldmsg);
563 if (it == m_stringToImageMap.end())
564 {
565 recompute = true;
566 continue;
567 }
568 MythImage *oldim = *it;
569 it = m_stringToImageMap.erase(it);
570
571 if (oldim)
572 {
573 m_softwareCacheSize -= oldim->GetSize();
574 if (m_softwareCacheSize < 0)
575 {
577 recompute = true;
578 }
579 oldim->DecrRef();
580 }
581 }
582 if (recompute)
583 {
585 for (auto *img : std::as_const(m_stringToImageMap))
586 m_softwareCacheSize += img->GetSize();
587 }
588}
589
590// the following assume graphics hardware operates natively at 32bpp
591void MythPainter::SetMaximumCacheSizes(int hardware, int software)
592{
593 static constexpr int64_t kOneMeg = 1LL * 1024 * 1024;
594 m_maxHardwareCacheSize = kOneMeg * hardware;
595 m_maxSoftwareCacheSize = kOneMeg * software;
596
597 bool err = false;
599 {
601 err = true;
602 }
604 {
605 m_maxSoftwareCacheSize = kOneMeg * 48;
606 err = true;
607 }
608
609 LOG((err) ? VB_GENERAL : VB_GUI, (err) ? LOG_ERR : LOG_INFO,
610 QString("MythPainter cache sizes: Hardware %1MB, Software %2MB")
611 .arg(m_maxHardwareCacheSize / kOneMeg)
612 .arg(m_maxSoftwareCacheSize / kOneMeg));
613}
QBrush GetBrush(void) const
bool hasOutline(void) const
bool hasShadow(void) const
QColor color(void) const
void GetShadow(QPoint &offset, QColor &color, int &alpha) const
void GetOutline(QColor &color, int &size, int &alpha) const
QFont face(void) const
QString GetHash(void) const
void SetFileName(QString fname)
Definition: mythimage.h:90
void Assign(const QImage &img)
Definition: mythimage.cpp:77
MythPainter * GetParent(void)
Definition: mythimage.h:44
int64_t GetSize(void)
Definition: mythimage.h:69
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:52
int IncrRef(void) override
Increments reference count.
Definition: mythimage.cpp:44
void SetParent(MythPainter *parent)
Definition: mythimage.h:45
void DeleteFormatImage(MythImage *im)
MythImage * GetImageFromTextLayout(const LayoutVector &layouts, const FormatVector &formats, const MythFontProperties &font, QRect &canvas, QRect &dest)
static void DrawRectPriv(MythImage *im, QRect area, int radius, int ellipse, const QBrush &fillBrush, const QPen &linePen)
int64_t m_softwareCacheSize
Definition: mythpainter.h:140
void ExpireImages(int64_t max=0)
virtual void DeleteFormatImagePriv(MythImage *im)=0
virtual void DrawText(QRect r, const QString &msg, int flags, const MythFontProperties &font, int alpha, QRect boundRect)
Definition: mythpainter.cpp:75
virtual void DrawRect(QRect area, const QBrush &fillBrush, const QPen &linePen, int alpha)
virtual MythImage * GetFormatImagePriv(void)=0
Creates a reference counted image, call DecrRef() to delete.
QMap< QString, MythImage * > m_stringToImageMap
Definition: mythpainter.h:146
virtual void DrawTextLayout(QRect canvasRect, const LayoutVector &layouts, const FormatVector &formats, const MythFontProperties &font, int alpha, QRect destRect)
virtual void SetClipRect(QRect clipRect)
Definition: mythpainter.cpp:45
virtual void DrawImage(QRect dest, MythImage *im, QRect src, int alpha)=0
MythImage * GetImageFromRect(QRect area, int radius, int ellipse, const QBrush &fillBrush, const QPen &linePen)
QSet< MythImage * > m_allocatedImages
Definition: mythpainter.h:144
static void DrawTextPriv(MythImage *im, const QString &msg, int flags, QRect r, const MythFontProperties &font)
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
QMutex m_allocationLock
Definition: mythpainter.h:143
virtual void Clear(QPaintDevice *device, const QRegion &region)
Definition: mythpainter.cpp:53
void CheckFormatImage(MythImage *im)
std::list< QString > m_stringExpireList
Definition: mythpainter.h:147
void SetMaximumCacheSizes(int hardware, int software)
MythImage * GetImageFromString(const QString &msg, int flags, QRect r, const MythFontProperties &font)
virtual void DrawRoundRect(QRect area, int cornerRadius, const QBrush &fillBrush, const QPen &linePen, int alpha)
virtual void DrawEllipse(QRect area, const QBrush &fillBrush, const QPen &linePen, int alpha)
int64_t m_maxSoftwareCacheSize
Definition: mythpainter.h:141
virtual void SetClipRegion(const QRegion &clipRegion)
Definition: mythpainter.cpp:49
int m_maxHardwareCacheSize
Definition: mythpainter.h:137
virtual void Teardown(void)
Definition: mythpainter.cpp:27
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
Definition: mythrect.h:89
void NormPoint(void)
Definition: mythrect.cpp:471
static guint32 * tmp
Definition: goom_core.cpp:26
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
QVector< QTextLayout::FormatRange > FormatVector
Definition: mythpainter.h:31
QVector< QTextLayout * > LayoutVector
Definition: mythpainter.h:30
const std::array< const std::string, 8 > formats
Definition: vbilut.cpp:189