Add ObjectMode coordinate mode to QGradient

The ObjectBoundingMode coordinate mode of QGradient allows specifying
the gradient coordinates relative to the object being painted. But if
the gradient brush also has a transformation, that transformation is
applied in the logical, not object, coordinate space. That behavior is
counterintuitive. However, changing it now would break existing
code. Instead, we introduce a new coordinate mode enum with the
expected behavior, and document the old one as deprecated.

This prepares to fix the bugs below in qtsvg, by making
it possible to specify the same behavior in Qt as SVG has.

[ChangeLog][QtGui][QGradient] Add ObjectMode coordinate mode
[ChangeLog][Important Behavior Changes] QDataStream version bumped up to 18 to account for changes in the serialization of QGradient.

Task-number: QTBUG-59978
Task-number: QTBUG-67995
Change-Id: I8820a2555359812f3e1a46e37d6ac2cc29a2091d
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
Eirik Aavitsland 2017-08-04 16:22:23 +02:00
parent 99d4f0026f
commit f5fe9fc5a4
8 changed files with 57 additions and 26 deletions

View File

@ -98,7 +98,7 @@ public:
Qt_5_9 = Qt_5_8, Qt_5_9 = Qt_5_8,
Qt_5_10 = Qt_5_9, Qt_5_10 = Qt_5_9,
Qt_5_11 = Qt_5_10, Qt_5_11 = Qt_5_10,
Qt_5_12 = Qt_5_11, Qt_5_12 = 18,
#if QT_VERSION >= 0x050d00 #if QT_VERSION >= 0x050d00
#error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion #error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion
#endif #endif

View File

@ -1077,7 +1077,10 @@ QDataStream &operator<<(QDataStream &s, const QBrush &b)
s << type_as_int; s << type_as_int;
if (s.version() >= QDataStream::Qt_4_3) { if (s.version() >= QDataStream::Qt_4_3) {
s << int(gradient->spread()); s << int(gradient->spread());
s << int(gradient->coordinateMode()); QGradient::CoordinateMode co_mode = gradient->coordinateMode();
if (s.version() < QDataStream::Qt_5_12 && co_mode == QGradient::ObjectMode)
co_mode = QGradient::ObjectBoundingMode;
s << int(co_mode);
} }
if (s.version() >= QDataStream::Qt_4_5) if (s.version() >= QDataStream::Qt_4_5)
@ -1562,14 +1565,19 @@ QGradientStops QGradient::stops() const
\value LogicalMode This is the default mode. The gradient coordinates \value LogicalMode This is the default mode. The gradient coordinates
are specified logical space just like the object coordinates. are specified logical space just like the object coordinates.
\value ObjectMode In this mode the gradient coordinates are
relative to the bounding rectangle of the object being drawn, with
(0,0) in the top left corner, and (1,1) in the bottom right corner
of the object's bounding rectangle. This value was added in Qt
5.12.
\value StretchToDeviceMode In this mode the gradient coordinates \value StretchToDeviceMode In this mode the gradient coordinates
are relative to the bounding rectangle of the paint device, are relative to the bounding rectangle of the paint device,
with (0,0) in the top left corner, and (1,1) in the bottom right with (0,0) in the top left corner, and (1,1) in the bottom right
corner of the paint device. corner of the paint device.
\value ObjectBoundingMode In this mode the gradient coordinates are \value ObjectBoundingMode This mode is the same as ObjectMode, except that
relative to the bounding rectangle of the object being drawn, with the {QBrush::transform()} {brush transform}, if any, is applied relative to
(0,0) in the top left corner, and (1,1) in the bottom right corner the logical space instead of the object space. This enum value is
of the object's bounding rectangle. deprecated and should not be used in new code.
*/ */
/*! /*!

View File

@ -194,7 +194,8 @@ public:
enum CoordinateMode { enum CoordinateMode {
LogicalMode, LogicalMode,
StretchToDeviceMode, StretchToDeviceMode,
ObjectBoundingMode ObjectBoundingMode,
ObjectMode
}; };
Q_ENUM(CoordinateMode) Q_ENUM(CoordinateMode)

View File

@ -74,10 +74,11 @@ QPainterState *QEmulationPaintEngine::createState(QPainterState *orig) const
static inline void combineXForm(QBrush *brush, const QRectF &r) static inline void combineXForm(QBrush *brush, const QRectF &r)
{ {
QTransform t = brush->transform(); QTransform t(r.width(), 0, 0, r.height(), r.x(), r.y());
t.translate(r.x(), r.y()); if (brush->gradient()->coordinateMode() == QGradient::ObjectMode)
t.scale(r.width(), r.height()); brush->setTransform(brush->transform() * t);
brush->setTransform(t); else
brush->setTransform(t * brush->transform());
} }
void QEmulationPaintEngine::fill(const QVectorPath &path, const QBrush &brush) void QEmulationPaintEngine::fill(const QVectorPath &path, const QBrush &brush)
@ -96,7 +97,7 @@ void QEmulationPaintEngine::fill(const QVectorPath &path, const QBrush &brush)
if (coMode > QGradient::LogicalMode) { if (coMode > QGradient::LogicalMode) {
QBrush copy = brush; QBrush copy = brush;
const QPaintDevice *d = real_engine->painter()->device(); const QPaintDevice *d = real_engine->painter()->device();
QRectF r = (coMode == QGradient::ObjectBoundingMode) ? path.controlPointRect() : QRectF(0, 0, d->width(), d->height()); QRectF r = (coMode == QGradient::StretchToDeviceMode) ? QRectF(0, 0, d->width(), d->height()) : path.controlPointRect();
combineXForm(&copy, r); combineXForm(&copy, r);
real_engine->fill(path, copy); real_engine->fill(path, copy);
return; return;
@ -132,7 +133,7 @@ void QEmulationPaintEngine::stroke(const QVectorPath &path, const QPen &pen)
QGradient::CoordinateMode coMode = brush.gradient()->coordinateMode(); QGradient::CoordinateMode coMode = brush.gradient()->coordinateMode();
if (coMode > QGradient::LogicalMode) { if (coMode > QGradient::LogicalMode) {
const QPaintDevice *d = real_engine->painter()->device(); const QPaintDevice *d = real_engine->painter()->device();
QRectF r = (coMode == QGradient::ObjectBoundingMode) ? path.controlPointRect() : QRectF(0, 0, d->width(), d->height()); QRectF r = (coMode == QGradient::StretchToDeviceMode) ? QRectF(0, 0, d->width(), d->height()) : path.controlPointRect();
combineXForm(&brush, r); combineXForm(&brush, r);
copy.setBrush(brush); copy.setBrush(brush);
real_engine->stroke(path, copy); real_engine->stroke(path, copy);
@ -174,9 +175,9 @@ void QEmulationPaintEngine::drawTextItem(const QPointF &p, const QTextItem &text
QBrush copy = s->pen.brush(); QBrush copy = s->pen.brush();
const QPaintDevice *d = real_engine->painter()->device(); const QPaintDevice *d = real_engine->painter()->device();
const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
QRectF r = (g.coordinateMode() == QGradient::ObjectBoundingMode) ? QRectF r = (g.coordinateMode() == QGradient::StretchToDeviceMode) ?
QRectF(p.x(), p.y() - ti.ascent.toReal(), ti.width.toReal(), (ti.ascent + ti.descent + 1).toReal()) : QRectF(0, 0, d->width(), d->height()) :
QRectF(0, 0, d->width(), d->height()); QRectF(p.x(), p.y() - ti.ascent.toReal(), ti.width.toReal(), (ti.ascent + ti.descent + 1).toReal());
combineXForm(&copy, r); combineXForm(&copy, r);
g.setCoordinateMode(QGradient::LogicalMode); g.setCoordinateMode(QGradient::LogicalMode);
QBrush brush(g); QBrush brush(g);

View File

@ -530,7 +530,10 @@ static inline QBrush stretchGradientToUserSpace(const QBrush &brush, const QRect
g.setCoordinateMode(QGradient::LogicalMode); g.setCoordinateMode(QGradient::LogicalMode);
QBrush b(g); QBrush b(g);
b.setTransform(gradientToUser * b.transform()); if (brush.gradient()->coordinateMode() == QGradient::ObjectMode)
b.setTransform(b.transform() * gradientToUser);
else
b.setTransform(gradientToUser * b.transform());
return b; return b;
} }
@ -569,7 +572,7 @@ void QPainterPrivate::drawStretchedGradient(const QPainterPath &path, DrawOperat
} else { } else {
needsFill = true; needsFill = true;
if (brushMode == QGradient::ObjectBoundingMode) { if (brushMode == QGradient::ObjectBoundingMode || brushMode == QGradient::ObjectMode) {
Q_ASSERT(engine->hasFeature(QPaintEngine::PatternTransform)); Q_ASSERT(engine->hasFeature(QPaintEngine::PatternTransform));
boundingRect = path.boundingRect(); boundingRect = path.boundingRect();
q->setBrush(stretchGradientToUserSpace(brush, boundingRect)); q->setBrush(stretchGradientToUserSpace(brush, boundingRect));
@ -613,11 +616,11 @@ void QPainterPrivate::drawStretchedGradient(const QPainterPath &path, DrawOperat
changedBrush = true; changedBrush = true;
} }
if (penMode == QGradient::ObjectBoundingMode) { if (penMode == QGradient::ObjectBoundingMode || penMode == QGradient::ObjectMode) {
Q_ASSERT(engine->hasFeature(QPaintEngine::PatternTransform)); Q_ASSERT(engine->hasFeature(QPaintEngine::PatternTransform));
// avoid computing the bounding rect twice // avoid computing the bounding rect twice
if (!needsFill || brushMode != QGradient::ObjectBoundingMode) if (!needsFill || (brushMode != QGradient::ObjectBoundingMode && brushMode != QGradient::ObjectMode))
boundingRect = path.boundingRect(); boundingRect = path.boundingRect();
QPen p = pen; QPen p = pen;
@ -849,8 +852,8 @@ void QPainterPrivate::updateEmulationSpecifier(QPainterState *s)
gradientStretch |= (brushMode == QGradient::StretchToDeviceMode); gradientStretch |= (brushMode == QGradient::StretchToDeviceMode);
gradientStretch |= (penMode == QGradient::StretchToDeviceMode); gradientStretch |= (penMode == QGradient::StretchToDeviceMode);
objectBoundingMode |= (brushMode == QGradient::ObjectBoundingMode); objectBoundingMode |= (brushMode == QGradient::ObjectBoundingMode || brushMode == QGradient::ObjectMode);
objectBoundingMode |= (penMode == QGradient::ObjectBoundingMode); objectBoundingMode |= (penMode == QGradient::ObjectBoundingMode || penMode == QGradient::ObjectMode);
} }
if (gradientStretch) if (gradientStretch)
s->emulationSpecifier |= QGradient_StretchToDevice; s->emulationSpecifier |= QGradient_StretchToDevice;
@ -6857,7 +6860,8 @@ static inline bool needsResolving(const QBrush &brush)
Qt::BrushStyle s = brush.style(); Qt::BrushStyle s = brush.style();
return ((s == Qt::LinearGradientPattern || s == Qt::RadialGradientPattern || return ((s == Qt::LinearGradientPattern || s == Qt::RadialGradientPattern ||
s == Qt::ConicalGradientPattern) && s == Qt::ConicalGradientPattern) &&
brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode); (brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode ||
brush.gradient()->coordinateMode() == QGradient::ObjectMode));
} }
/*! /*!

View File

@ -2403,7 +2403,8 @@ static inline void fillRegion(QPainter *painter, const QRegion &rgn, const QBrus
#endif #endif
} else if (brush.gradient() } else if (brush.gradient()
&& brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode) { && (brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode
|| brush.gradient()->coordinateMode() == QGradient::ObjectMode)) {
painter->save(); painter->save();
painter->setClipRegion(rgn); painter->setClipRegion(rgn);
painter->fillRect(0, 0, painter->device()->width(), painter->device()->height(), brush); painter->fillRect(0, 0, painter->device()->width(), painter->device()->height(), brush);

View File

@ -106,7 +106,8 @@ const char *PaintCommands::spreadMethodTable[] = {
const char *PaintCommands::coordinateMethodTable[] = { const char *PaintCommands::coordinateMethodTable[] = {
"LogicalMode", "LogicalMode",
"StretchToDeviceMode", "StretchToDeviceMode",
"ObjectBoundingMode" "ObjectBoundingMode",
"ObjectMode"
}; };
const char *PaintCommands::sizeModeTable[] = { const char *PaintCommands::sizeModeTable[] = {
@ -2394,7 +2395,7 @@ void PaintCommands::command_gradient_setSpread(QRegularExpressionMatch re)
void PaintCommands::command_gradient_setCoordinateMode(QRegularExpressionMatch re) void PaintCommands::command_gradient_setCoordinateMode(QRegularExpressionMatch re)
{ {
int coord = translateEnum(coordinateMethodTable, re.captured(1), 3); int coord = translateEnum(coordinateMethodTable, re.captured(1), 4);
if (m_verboseMode) if (m_verboseMode)
printf(" -(lance) gradient_setCoordinateMode %d=[%s]\n", coord, printf(" -(lance) gradient_setCoordinateMode %d=[%s]\n", coord,

View File

@ -62,7 +62,22 @@ repeat_block row
restore restore
end_block block end_block block
save
translate 400 0 translate 400 0
brushRotate 30.0 brushRotate 30.0
brushScale 1.5 .5 brushScale 1.5 .5
repeat_block block repeat_block block
restore
drawText 80 400 "BRUSH XFORM, OBJECT BOUNDING MODE"
drawText 500 400 "BRUSH XFORM, OBJECT MODE"
translate 0 400
brushTranslate 0.5 0.5
brushRotate 180.0
brushTranslate -0.5 -0.5
repeat_block block
translate 400 0
gradient_setCoordinateMode ObjectMode
repeat_block block