定义:
QPainter 类在小部件和其它绘画设备上履行低级描绘。
与 QPaintDevice 和 QPaintEngine 类一起,QPainter 构成了 Qt 绘画系统的基础。 QPainter 是用于执行绘图操作的类。 QPaintDevice 表示可以使用 QPainter 绘制的设备。 QPaintEngine 提供了painter用来在不同类型的设备上绘制的接口。 如果painter 处于活动状态,device() 返回painter 在其上绘制的绘制设备,paintEngine() 返回painter 当前正在操作的绘制引擎。
isActive() 函数指示painter是否处于活动状态。 painter由 begin() 函数和接受 QPaintDevice 参数的构造函数激活。 end() 函数和析构函数将其停用。
官方例子:
void SimpleExampleWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setPen(Qt::blue);
painter.setFont(QFont("Arial", 30));
painter.drawText(rect(), Qt::AlignCenter, "Qt");
}
源码分析:
1.explicit QPainter(QPaintDevice *);
QPainter::QPainter(QPaintDevice *pd)
: d_ptr(0)
{
Q_ASSERT(pd != 0);
if (!QPainterPrivate::attachPainterPrivate(this, pd)) {
d_ptr.reset(new QPainterPrivate(this));
begin(pd);
}
Q_ASSERT(d_ptr);
}
bool QPainterPrivate::attachPainterPrivate(QPainter *q, QPaintDevice *pdev)
{
Q_ASSERT(q);
Q_ASSERT(pdev);
QPainter *sp = pdev->sharedPainter();
if (!sp)
return false;
// Save the current state of the shared painter and assign
// the current d_ptr to the shared painter's d_ptr.
sp->save();
if (!sp->d_ptr->d_ptrs) {
// Allocate space for 4 d-pointers (enough for up to 4 sub-sequent
// redirections within the same paintEvent(), which should be enough
// in 99% of all cases). E.g: A renders B which renders C which renders D.
sp->d_ptr->d_ptrs_size = 4;
sp->d_ptr->d_ptrs = (QPainterPrivate **)malloc(4 * sizeof(QPainterPrivate *));
Q_CHECK_PTR(sp->d_ptr->d_ptrs);
} else if (sp->d_ptr->refcount - 1 == sp->d_ptr->d_ptrs_size) {
// However, to support corner cases we grow the array dynamically if needed.
sp->d_ptr->d_ptrs_size <<= 1;
const int newSize = sp->d_ptr->d_ptrs_size * sizeof(QPainterPrivate *);
sp->d_ptr->d_ptrs = q_check_ptr((QPainterPrivate **)realloc(sp->d_ptr->d_ptrs, newSize));
}
sp->d_ptr->d_ptrs[++sp->d_ptr->refcount - 2] = q->d_ptr.data();
q->d_ptr.take();
q->d_ptr.reset(sp->d_ptr.data());
Q_ASSERT(q->d_ptr->state);
// Now initialize the painter with correct widget properties.
q->d_ptr->initFrom(pdev);
QPoint offset;
pdev->redirected(&offset);
offset += q->d_ptr->engine->coordinateOffset();
// Update system rect.
q->d_ptr->state->ww = q->d_ptr->state->vw = pdev->width();
q->d_ptr->state->wh = q->d_ptr->state->vh = pdev->height();
// Update matrix.
if (q->d_ptr->state->WxF) {
q->d_ptr->state->redirectionMatrix = q->d_ptr->state->matrix;
q->d_ptr->state->redirectionMatrix *= q->d_ptr->hidpiScaleTransform().inverted();
q->d_ptr->state->redirectionMatrix.translate(-offset.x(), -offset.y());
q->d_ptr->state->worldMatrix = QTransform();
q->d_ptr->state->WxF = false;
} else {
q->d_ptr->state->redirectionMatrix = QTransform::fromTranslate(-offset.x(), -offset.y());
}
q->d_ptr->updateMatrix();
QPaintEnginePrivate *enginePrivate = q->d_ptr->engine->d_func();
if (enginePrivate->currentClipDevice == pdev) {
enginePrivate->systemStateChanged();
return true;
}
// Update system transform and clip.
enginePrivate->currentClipDevice = pdev;
enginePrivate->setSystemTransform(q->d_ptr->state->matrix);
return true;
}
attach QPaintDevice到 QPainter.
2.void setPen(const QColor &color);
void QPainter::setPen(const QColor &color)
{
#ifdef QT_DEBUG_DRAW
if (qt_show_painter_debug_output)
printf("QPainter::setPen(), color=%04x\n", color.rgb());
#endif
Q_D(QPainter);
if (!d->engine) {
qWarning("QPainter::setPen: Painter not active");
return;
}
QPen pen(color.isValid() ? color : QColor(Qt::black));
if (d->state->pen == pen)
return;
d->state->pen = pen;
if (d->extended)
d->extended->penChanged();
else
d->state->dirtyFlags |= QPaintEngine::DirtyPen;
}
3.void setFont(const QFont &f);
void QPainter::setFont(const QFont &font)
{
Q_D(QPainter);
#ifdef QT_DEBUG_DRAW
if (qt_show_painter_debug_output)
printf("QPainter::setFont(), family=%s, pointSize=%d\n", font.family().toLatin1().constData(), font.pointSize());
#endif
if (!d->engine) {
qWarning("QPainter::setFont: Painter not active");
return;
}
d->state->font = QFont(font.resolve(d->state->deviceFont), device());
if (!d->extended)
d->state->dirtyFlags |= QPaintEngine::DirtyFont;
}
4. void drawText(const QRectF &r, int flags, const QString &text, QRectF *br = nullptr);
void QPainter::drawText(const QRectF &r, int flags, const QString &str, QRectF *br)
{
#ifdef QT_DEBUG_DRAW
if (qt_show_painter_debug_output)
printf("QPainter::drawText(), r=[%.2f,%.2f,%.2f,%.2f], flags=%d, str='%s'\n",
r.x(), r.y(), r.width(), r.height(), flags, str.toLatin1().constData());
#endif
Q_D(QPainter);
if (!d->engine || str.length() == 0 || pen().style() == Qt::NoPen)
return;
if (!d->extended)
d->updateState(d->state);
qt_format_text(d->state->font, r, flags, 0, str, br, 0, 0, 0, this);
}
void qt_format_text(const QFont &fnt, const QRectF &_r,
int tf, const QTextOption *option, const QString& str, QRectF *brect,
int tabstops, int *ta, int tabarraylen,
QPainter *painter)
{
Q_ASSERT( !((tf & ~Qt::TextDontPrint)!=0 && option!=0) ); // we either have an option or flags
if (option) {
tf |= option->alignment();
if (option->wrapMode() != QTextOption::NoWrap)
tf |= Qt::TextWordWrap;
if (option->flags() & QTextOption::IncludeTrailingSpaces)
tf |= Qt::TextIncludeTrailingSpaces;
if (option->tabStopDistance() >= 0 || !option->tabArray().isEmpty())
tf |= Qt::TextExpandTabs;
}
// we need to copy r here to protect against the case (&r == brect).
QRectF r(_r);
bool dontclip = (tf & Qt::TextDontClip);
bool wordwrap = (tf & Qt::TextWordWrap) || (tf & Qt::TextWrapAnywhere);
bool singleline = (tf & Qt::TextSingleLine);
bool showmnemonic = (tf & Qt::TextShowMnemonic);
bool hidemnmemonic = (tf & Qt::TextHideMnemonic);
Qt::LayoutDirection layout_direction;
if (tf & Qt::TextForceLeftToRight)
layout_direction = Qt::LeftToRight;
else if (tf & Qt::TextForceRightToLeft)
layout_direction = Qt::RightToLeft;
else if (option)
layout_direction = option->textDirection();
else if (painter)
layout_direction = painter->layoutDirection();
else
layout_direction = Qt::LeftToRight;
tf = QGuiApplicationPrivate::visualAlignment(layout_direction, QFlag(tf));
bool isRightToLeft = layout_direction == Qt::RightToLeft;
bool expandtabs = ((tf & Qt::TextExpandTabs) &&
(((tf & Qt::AlignLeft) && !isRightToLeft) ||
((tf & Qt::AlignRight) && isRightToLeft)));
if (!painter)
tf |= Qt::TextDontPrint;
uint maxUnderlines = 0;
QFontMetricsF fm(fnt);
QString text = str;
int offset = 0;
start_lengthVariant:
bool hasMoreLengthVariants = false;
// compatible behaviour to the old implementation. Replace
// tabs by spaces
int old_offset = offset;
for (; offset < text.length(); offset++) {
QChar chr = text.at(offset);
if (chr == QLatin1Char('\r') || (singleline && chr == QLatin1Char('\n'))) {
text[offset] = QLatin1Char(' ');
} else if (chr == QLatin1Char('\n')) {
text[offset] = QChar::LineSeparator;
} else if (chr == QLatin1Char('&')) {
++maxUnderlines;
} else if (chr == QLatin1Char('\t')) {
if (!expandtabs) {
text[offset] = QLatin1Char(' ');
} else if (!tabarraylen && !tabstops) {
tabstops = qRound(fm.horizontalAdvance(QLatin1Char('x'))*8);
}
} else if (chr == QChar(ushort(0x9c))) {
// string with multiple length variants
hasMoreLengthVariants = true;
break;
}
}
QVector underlineFormats;
int length = offset - old_offset;
if ((hidemnmemonic || showmnemonic) && maxUnderlines > 0) {
QChar *cout = text.data() + old_offset;
QChar *cout0 = cout;
QChar *cin = cout;
int l = length;
while (l) {
if (*cin == QLatin1Char('&')) {
++cin;
--length;
--l;
if (!l)
break;
if (*cin != QLatin1Char('&') && !hidemnmemonic && !(tf & Qt::TextDontPrint)) {
QTextLayout::FormatRange range;
range.start = cout - cout0;
range.length = 1;
range.format.setFontUnderline(true);
underlineFormats.append(range);
}
#ifdef Q_OS_MAC
} else if (hidemnmemonic && *cin == QLatin1Char('(') && l >= 4 &&
cin[1] == QLatin1Char('&') && cin[2] != QLatin1Char('&') &&
cin[3] == QLatin1Char(')')) {
int n = 0;
while ((cout - n) > cout0 && (cout - n - 1)->isSpace())
++n;
cout -= n;
cin += 4;
length -= n + 4;
l -= 4;
continue;
#endif //Q_OS_MAC
}
*cout = *cin;
++cout;
++cin;
--l;
}
}
qreal height = 0;
qreal width = 0;
QString finalText = text.mid(old_offset, length);
QStackTextEngine engine(finalText, fnt);
if (option) {
engine.option = *option;
}
if (engine.option.tabStopDistance() < 0 && tabstops > 0)
engine.option.setTabStopDistance(tabstops);
if (engine.option.tabs().isEmpty() && ta) {
QList tabs;
tabs.reserve(tabarraylen);
for (int i = 0; i < tabarraylen; i++)
tabs.append(qreal(ta[i]));
engine.option.setTabArray(tabs);
}
engine.option.setTextDirection(layout_direction);
if (tf & Qt::AlignJustify)
engine.option.setAlignment(Qt::AlignJustify);
else
engine.option.setAlignment(Qt::AlignLeft); // do not do alignment twice
if (!option && (tf & Qt::TextWrapAnywhere))
engine.option.setWrapMode(QTextOption::WrapAnywhere);
if (tf & Qt::TextJustificationForced)
engine.forceJustification = true;
QTextLayout textLayout(&engine);
textLayout.setCacheEnabled(true);
textLayout.setFormats(underlineFormats);
if (finalText.isEmpty()) {
height = fm.height();
width = 0;
tf |= Qt::TextDontPrint;
} else {
qreal lineWidth = 0x01000000;
if (wordwrap || (tf & Qt::TextJustificationForced))
lineWidth = qMax(0, r.width());
if(!wordwrap)
tf |= Qt::TextIncludeTrailingSpaces;
textLayout.beginLayout();
qreal leading = fm.leading();
height = -leading;
while (1) {
QTextLine l = textLayout.createLine();
if (!l.isValid())
break;
l.setLineWidth(lineWidth);
height += leading;
// Make sure lines are positioned on whole pixels
height = qCeil(height);
l.setPosition(QPointF(0., height));
height += textLayout.engine()->lines[l.lineNumber()].height().toReal();
width = qMax(width, l.naturalTextWidth());
if (!dontclip && !brect && height >= r.height())
break;
}
textLayout.endLayout();
}
qreal yoff = 0;
qreal xoff = 0;
if (tf & Qt::AlignBottom)
yoff = r.height() - height;
else if (tf & Qt::AlignVCenter)
yoff = (r.height() - height)/2;
if (tf & Qt::AlignRight)
xoff = r.width() - width;
else if (tf & Qt::AlignHCenter)
xoff = (r.width() - width)/2;
QRectF bounds = QRectF(r.x() + xoff, r.y() + yoff, width, height);
if (hasMoreLengthVariants && !(tf & Qt::TextLongestVariant) && !r.contains(bounds)) {
offset++;
goto start_lengthVariant;
}
if (brect)
*brect = bounds;
if (!(tf & Qt::TextDontPrint)) {
bool restore = false;
if (!dontclip && !r.contains(bounds)) {
restore = true;
painter->save();
painter->setClipRect(r, Qt::IntersectClip);
}
for (int i = 0; i < textLayout.lineCount(); i++) {
QTextLine line = textLayout.lineAt(i);
QTextEngine *eng = textLayout.engine();
eng->enableDelayDecorations();
qreal advance = line.horizontalAdvance();
xoff = 0;
if (tf & Qt::AlignRight) {
xoff = r.width() - advance -
eng->leadingSpaceWidth(eng->lines[line.lineNumber()]).toReal();
}
else if (tf & Qt::AlignHCenter)
xoff = (r.width() - advance) / 2;
line.draw(painter, QPointF(r.x() + xoff, r.y() + yoff));
eng->drawDecorations(painter);
}
if (restore) {
painter->restore();
}
}
}
设置好画笔和字体后,最后用QTextLine画文字。