Skip to content

Commit

Permalink
Refactor the UI Painters.
Browse files Browse the repository at this point in the history
- Move all shape painting out of MythUIShape and into the painter
classes. This means MythUIShape no longer caches the rendered image and
the individual painter classes are free to optimise/accelerate rendering
as appropriate.

- Clean up the MythPainter interface on the basis of using QBrush and
QPen explicitly in all cases.

- Update MythGuideGrid for API changes (and to a lesser extent
MythUIType)

- Consolidate all text, rectangle, rounded rectangle and ellipse drawing
and cacheing in the base MythPainter class. This essentially means the
base class now handles all CPU based rendering and cacheing with the
derived classes free to implement hardware cacheing (i.e. OpenGL/D3D
textures) and accelerated rendering.

- Use accelerated rendering for basic square rectangles with no gradient
for OpenGL and Direct3D9.

All told, this greatly simplifies the code, should make it a little more
comprehensible, ensures the correct classes are doing the painting and
implements the beginnings of properly accelerated rendering for some
shapes.

A customised theme (using only basic rectangles) should now give, for
example, accelerated OpenGL ES rendering with a small memory footprint
on embedded devices.
  • Loading branch information
Mark Kendall committed Mar 8, 2011
1 parent 671a3b0 commit e949f5f
Show file tree
Hide file tree
Showing 19 changed files with 513 additions and 1,600 deletions.
297 changes: 293 additions & 4 deletions mythtv/libs/libmythui/mythpainter.cpp
@@ -1,9 +1,8 @@

// Own header
#include "mythpainter.h"
#include <stdint.h>

// QT headers
#include <QRect>
#include <QPainter>

// libmythbase headers
#include "mythverbose.h"
Expand All @@ -12,6 +11,9 @@
#include "mythfontproperties.h"
#include "mythimage.h"

// Own header
#include "mythpainter.h"

int MythPainter::m_MaxCacheSize = 1024 * 1024 * 64;

MythPainter::~MythPainter(void)
Expand Down Expand Up @@ -56,10 +58,279 @@ void MythPainter::DrawImage(const QPoint &topLeft, MythImage *im, int alpha)
DrawImage(topLeft.x(), topLeft.y(), im, alpha);
}

void MythPainter::DrawText(const QRect &r, const QString &msg,
int flags, const MythFontProperties &font,
int alpha, const QRect &boundRect)
{
MythImage *im = GetImageFromString(msg, flags, r, font);
if (!im)
return;

QRect destRect(boundRect);
QRect srcRect(0,0,r.width(),r.height());
if (!boundRect.isEmpty() && boundRect != r)
{
int x = 0;
int y = 0;
int width = boundRect.width();
int height = boundRect.height();

if (boundRect.x() > r.x())
{
x = boundRect.x()-r.x();
}
else if (r.x() > boundRect.x())
{
destRect.setX(r.x());
width = (boundRect.x() + boundRect.width()) - r.x();
}

if (boundRect.y() > r.y())
{
y = boundRect.y()-r.y();
}
else if (r.y() > boundRect.y())
{
destRect.setY(r.y());
height = (boundRect.y() + boundRect.height()) - r.y();
}

if (width <= 0 || height <= 0)
return;

srcRect.setRect(x,y,width,height);
}

DrawImage(destRect, im, srcRect, alpha);
}

void MythPainter::DrawRect(const QRect &area, const QBrush &fillBrush,
const QPen &linePen, int alpha)
{
MythImage *im = GetImageFromRect(area, 0, 0, fillBrush, linePen);
if (im)
DrawImage(area.x(), area.y(), im, alpha);
}

void MythPainter::DrawRoundRect(const QRect &area, int cornerRadius,
const QBrush &fillBrush, const QPen &linePen,
int alpha)
{
MythImage *im = GetImageFromRect(area, cornerRadius, 0, fillBrush, linePen);
if (im)
DrawImage(area.x(), area.y(), im, alpha);
}

void MythPainter::DrawEllipse(const QRect &area, const QBrush &fillBrush,
const QPen &linePen, int alpha)
{
MythImage *im = GetImageFromRect(area, 0, 1, fillBrush, linePen);
if (im)
DrawImage(area.x(), area.y(), im, alpha);
}

void MythPainter::DrawTextPriv(MythImage *im, const QString &msg, int flags,
const QRect &r, const MythFontProperties &font)
{
if (!im)
return;

QPoint drawOffset;
font.GetOffset(drawOffset);

QImage pm(r.size(), QImage::Format_ARGB32);
QColor fillcolor = font.color();
if (font.hasOutline())
{
QColor outlineColor;
int outlineSize, outlineAlpha;

font.GetOutline(outlineColor, outlineSize, outlineAlpha);

fillcolor = outlineColor;
}
fillcolor.setAlpha(0);
pm.fill(fillcolor.rgba());

QPainter tmp(&pm);
QFont tmpfont = font.face();
tmpfont.setStyleStrategy(QFont::OpenGLCompatible);
tmp.setFont(tmpfont);

if (font.hasShadow())
{
QPoint shadowOffset;
QColor shadowColor;
int shadowAlpha;

font.GetShadow(shadowOffset, shadowColor, shadowAlpha);

QRect a = QRect(0, 0, r.width(), r.height());
a.translate(shadowOffset.x() + drawOffset.x(),
shadowOffset.y() + drawOffset.y());

shadowColor.setAlpha(shadowAlpha);
tmp.setPen(shadowColor);
tmp.drawText(a, flags, msg);
}

if (font.hasOutline())
{
QColor outlineColor;
int outlineSize, outlineAlpha;

font.GetOutline(outlineColor, outlineSize, outlineAlpha);

/* FIXME: use outlineAlpha */
int outalpha = 16;

QRect a = QRect(0, 0, r.width(), r.height());
a.translate(-outlineSize + drawOffset.x(),
-outlineSize + drawOffset.y());

outlineColor.setAlpha(outalpha);
tmp.setPen(outlineColor);
tmp.drawText(a, flags, msg);

for (int i = (0 - outlineSize + 1); i <= outlineSize; i++)
{
a.translate(1, 0);
tmp.drawText(a, flags, msg);
}

for (int i = (0 - outlineSize + 1); i <= outlineSize; i++)
{
a.translate(0, 1);
tmp.drawText(a, flags, msg);
}

for (int i = (0 - outlineSize + 1); i <= outlineSize; i++)
{
a.translate(-1, 0);
tmp.drawText(a, flags, msg);
}

for (int i = (0 - outlineSize + 1); i <= outlineSize; i++)
{
a.translate(0, -1);
tmp.drawText(a, flags, msg);
}
}

tmp.setPen(QPen(font.GetBrush(), 0));
tmp.drawText(drawOffset.x(), drawOffset.y(), r.width(), r.height(),
flags, msg);
tmp.end();
im->Assign(pm);
}

void MythPainter::DrawRectPriv(MythImage *im, const QRect &area, int radius,
int ellipse,
const QBrush &fillBrush, const QPen &linePen)
{
if (!im)
return;

QImage image(QSize(area.width(), area.height()), QImage::Format_ARGB32);
image.fill(0x00000000);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(linePen);
painter.setBrush(fillBrush);

if ((area.width() / 2) < radius)
radius = area.width() / 2;

if ((area.height() / 2) < radius)
radius = area.height() / 2;

int lineWidth = linePen.width();
QRect r(lineWidth, lineWidth,
area.width() - (lineWidth * 2), area.height() - (lineWidth * 2));

if (ellipse)
painter.drawEllipse(r);
else if (radius == 0)
painter.drawRect(r);
else
painter.drawRoundedRect(r, (qreal)radius, qreal(radius));

painter.end();
im->Assign(image);
}

MythImage *MythPainter::GetImageFromString(const QString &msg,
int flags, const QRect &r,
const MythFontProperties &font)
{
QString incoming = font.GetHash() + QString::number(r.width()) +
QString::number(r.height()) +
QString::number(flags) +
QString::number(font.color().rgba()) + msg;

if (m_StringToImageMap.contains(incoming))
{
m_StringExpireList.remove(incoming);
m_StringExpireList.push_back(incoming);
return m_StringToImageMap[incoming];
}

MythImage *im = GetFormatImage();
if (im)
{
DrawTextPriv(im, msg, flags, r, font);
m_StringToImageMap[incoming] = im;
m_StringExpireList.push_back(incoming);
ExpireImages(m_ItemCacheSize);
}
return im;
}

MythImage* MythPainter::GetImageFromRect(const QRect &area, int radius,
int ellipse,
const QBrush &fillBrush,
const QPen &linePen)
{
if (area.width() <= 0 || area.height() <= 0)
return NULL;

uint64_t hash1 = ((0xfff & (uint64_t)area.left())) +
((0xfff & (uint64_t)area.top()) << 12) +
((0xfff & (uint64_t)area.width()) << 24) +
((0xfff & (uint64_t)area.height()) << 36) +
((0xfff & (uint64_t)fillBrush.style()) << 48) +
((0xf & (uint64_t)linePen.width()) << 60);
uint64_t hash2 = ((0xfff & (uint64_t)radius)) +
((0xfff & (uint64_t)linePen.style()) << 12) +
((0xfff & (uint64_t)ellipse) << 24);
uint64_t hash3 = ((0xffffffff & (uint64_t)linePen.color().rgba())) +
((0xffffffff & (uint64_t)fillBrush.color().rgba()) << 32);

QString incoming = QString("RECT") + QString::number(hash1) +
QString::number(hash2) + QString::number(hash3);

if (m_StringToImageMap.contains(incoming))
{
m_StringExpireList.remove(incoming);
m_StringExpireList.push_back(incoming);
return m_StringToImageMap[incoming];
}

MythImage *im = GetFormatImage();
if (im)
{
DrawRectPriv(im, area, radius, ellipse, fillBrush, linePen);
m_StringToImageMap[incoming] = im;
m_StringExpireList.push_back(incoming);
ExpireImages(m_ItemCacheSize);
}
return im;
}

MythImage *MythPainter::GetFormatImage()
{
m_allocationLock.lock();
MythImage *result = new MythImage(this);
MythImage *result = GetFormatImagePriv();
m_allocatedImages.append(result);
m_allocationLock.unlock();
return result;
Expand All @@ -86,6 +357,24 @@ void MythPainter::CheckFormatImage(MythImage *im)
}
}

void MythPainter::ExpireImages(uint max)
{
while (m_StringExpireList.size() > max)
{
QString oldmsg = m_StringExpireList.front();
m_StringExpireList.pop_front();

MythImage *oldim = NULL;
if (m_StringToImageMap.contains(oldmsg))
oldim = m_StringToImageMap[oldmsg];

m_StringToImageMap.remove(oldmsg);

if (oldim)
oldim->DownRef();
}
}

// the following assume graphics hardware operates natively at 32bpp
void MythPainter::IncreaseCacheSize(QSize size)
{
Expand Down

0 comments on commit e949f5f

Please sign in to comment.