Add painter render hint for brush pattern transformation

[ChangeLog][QtGui][QPainter] In Qt 5, the predefined brush patterns
would always be transformed along with the object being painted. In Qt
6.0 onwards, they would or would not, depending on the
SmoothPixmapTransformation render hint. Instead of this somewhat
surprising behavior, make the default be untransformed
(i.e. cosmetic), which makes sense when it comes to dpr scaling. For
the cases where one wants scaling, a new render hint is introduced to
enable that: NonCosmeticPatternBrushes.

Change-Id: I2208c7a28af9056d7ab97a529b66bf2d502c3c4f
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
This commit is contained in:
Eirik Aavitsland 2022-05-20 16:47:42 +02:00
parent 5e48a51608
commit 5adaa8d868
10 changed files with 134 additions and 23 deletions

View File

@ -814,6 +814,8 @@ bool QPicture::exec(QPainter *painter, QDataStream &s, int nrecords)
bool(ul & QPainter::Antialiasing));
painter->setRenderHint(QPainter::SmoothPixmapTransform,
bool(ul & QPainter::SmoothPixmapTransform));
painter->setRenderHint(QPainter::NonCosmeticBrushPatterns,
bool(ul & QPainter::NonCosmeticBrushPatterns));
break;
case QPicturePrivate::PdcSetCompositionMode:
s >> ul;

View File

@ -3581,20 +3581,20 @@ static const CompositionFunctionFP *functionForModeFP = qt_functionForModeFP_C;
static TextureBlendType getBlendType(const QSpanData *data)
{
TextureBlendType ft;
if (data->txop <= QTransform::TxTranslate)
if (data->texture.type == QTextureData::Tiled || data->texture.type == QTextureData::Pattern)
if (data->texture.type == QTextureData::Pattern)
ft = BlendTiled;
else if (data->txop <= QTransform::TxTranslate)
if (data->texture.type == QTextureData::Tiled)
ft = BlendTiled;
else
ft = BlendUntransformed;
else if (data->bilinear)
if (data->texture.type == QTextureData::Tiled || data->texture.type == QTextureData::Pattern)
if (data->texture.type == QTextureData::Tiled)
ft = BlendTransformedBilinearTiled;
else
ft = BlendTransformedBilinear;
else
if (data->texture.type == QTextureData::Pattern)
ft = BlendTiled;
else if (data->texture.type == QTextureData::Tiled)
if (data->texture.type == QTextureData::Tiled)
ft = BlendTransformedTiled;
else
ft = BlendTransformed;

View File

@ -345,7 +345,7 @@ struct QSpanData
void init(QRasterBuffer *rb, const QRasterPaintEngine *pe);
void setup(const QBrush &brush, int alpha, QPainter::CompositionMode compositionMode);
void setup(const QBrush &brush, int alpha, QPainter::CompositionMode compositionMode, bool isCosmetic);
void setupMatrix(const QTransform &matrix, int bilinear);
void initTexture(const QImage *image, int alpha, QTextureData::Type = QTextureData::Plain, const QRect &sourceRect = QRect());
void adjustSpanMethods();

View File

@ -426,12 +426,12 @@ bool QRasterPaintEngine::begin(QPaintDevice *device)
d->rasterizer->setClipRect(d->deviceRect);
s->penData.init(d->rasterBuffer.data(), this);
s->penData.setup(s->pen.brush(), s->intOpacity, s->composition_mode);
s->penData.setup(s->pen.brush(), s->intOpacity, s->composition_mode, s->flags.cosmetic_brush);
s->stroker = &d->basicStroker;
d->basicStroker.setClipRect(d->deviceRect);
s->brushData.init(d->rasterBuffer.data(), this);
s->brushData.setup(s->brush, s->intOpacity, s->composition_mode);
s->brushData.setup(s->brush, s->intOpacity, s->composition_mode, s->flags.cosmetic_brush);
d->rasterBuffer->compositionMode = QPainter::CompositionMode_SourceOver;
@ -523,6 +523,7 @@ QRasterPaintEngineState::QRasterPaintEngineState()
flags.fast_text = true;
flags.tx_noshear = true;
flags.fast_images = true;
flags.cosmetic_brush = true;
clip = nullptr;
flags.has_clip_ownership = false;
@ -621,7 +622,8 @@ void QRasterPaintEngine::updatePen(const QPen &pen)
s->strokeFlags = 0;
s->penData.clip = d->clip();
s->penData.setup(pen_style == Qt::NoPen ? QBrush() : pen.brush(), s->intOpacity, s->composition_mode);
s->penData.setup(pen_style == Qt::NoPen ? QBrush() : pen.brush(), s->intOpacity,
s->composition_mode, s->flags.cosmetic_brush);
if (s->strokeFlags & QRasterPaintEngine::DirtyTransform
|| pen.brush().transform().type() >= QTransform::TxNone) {
@ -720,7 +722,7 @@ void QRasterPaintEngine::updateBrush(const QBrush &brush)
QRasterPaintEngineState *s = state();
// must set clip prior to setup, as setup uses it...
s->brushData.clip = d->clip();
s->brushData.setup(brush, s->intOpacity, s->composition_mode);
s->brushData.setup(brush, s->intOpacity, s->composition_mode, s->flags.cosmetic_brush);
if (s->fillFlags & DirtyTransform
|| brush.transform().type() >= QTransform::TxNone)
d_func()->updateMatrixData(&s->brushData, brush, d->brushMatrix());
@ -807,14 +809,16 @@ void QRasterPaintEngine::renderHintsChanged()
bool was_aa = s->flags.antialiased;
bool was_bilinear = s->flags.bilinear;
bool was_cosmetic_brush = s->flags.cosmetic_brush;
s->flags.antialiased = bool(s->renderHints & QPainter::Antialiasing);
s->flags.bilinear = bool(s->renderHints & QPainter::SmoothPixmapTransform);
s->flags.cosmetic_brush = !bool(s->renderHints & QPainter::NonCosmeticBrushPatterns);
if (was_aa != s->flags.antialiased)
s->strokeFlags |= DirtyHints;
if (was_bilinear != s->flags.bilinear) {
if (was_bilinear != s->flags.bilinear || was_cosmetic_brush != s->flags.cosmetic_brush) {
s->strokeFlags |= DirtyPen;
s->fillFlags |= DirtyBrush;
}
@ -4472,7 +4476,8 @@ void QSpanData::init(QRasterBuffer *rb, const QRasterPaintEngine *pe)
Q_GUI_EXPORT extern QImage qt_imageForBrush(int brushStyle, bool invert);
void QSpanData::setup(const QBrush &brush, int alpha, QPainter::CompositionMode compositionMode)
void QSpanData::setup(const QBrush &brush, int alpha, QPainter::CompositionMode compositionMode,
bool isCosmetic)
{
Qt::BrushStyle brushStyle = qbrush_style(brush);
cachedGradient.reset();
@ -4579,7 +4584,7 @@ void QSpanData::setup(const QBrush &brush, int alpha, QPainter::CompositionMode
if (!tempImage)
tempImage = new QImage();
*tempImage = rasterBuffer->colorizeBitmap(qt_imageForBrush(brushStyle, true), brush.color());
initTexture(tempImage, alpha, QTextureData::Pattern);
initTexture(tempImage, alpha, isCosmetic ? QTextureData::Pattern : QTextureData::Tiled);
break;
case Qt::TexturePattern:
type = Texture;

View File

@ -76,6 +76,7 @@ public:
uint fast_text : 1;
uint tx_noshear : 1;
uint fast_images : 1;
uint cosmetic_brush : 1;
};
union {

View File

@ -1412,6 +1412,12 @@ void QPainterPrivate::updateState(QPainterState *newState)
JPEG compression.
This value was added in Qt 5.13.
\value NonCosmeticBrushPatterns When painting with a brush with one of the predefined pattern
styles, transform the pattern too, along with the object being painted. The default is to treat
the pattern as cosmetic, so that the pattern pixels will map directly to device pixels,
independently of any active transformations.
This value was added in Qt 6.4.
\sa renderHints(), setRenderHint(), {QPainter#Rendering
Quality}{Rendering Quality}, {Concentric Circles Example}

View File

@ -54,6 +54,7 @@ public:
SmoothPixmapTransform = 0x04,
VerticalSubpixelPositioning = 0x08,
LosslessImageRendering = 0x40,
NonCosmeticBrushPatterns = 0x80
};
Q_ENUM(RenderHint)

View File

@ -0,0 +1,78 @@
# Version: 1
# CheckVsReference: 5%
#define basic block off screen
save
translate -1000 -1000
begin_block drawrects
setBrush green Dense4Pattern
drawRect 0 0 40 40
setBrush green DiagCrossPattern
drawRect 40 0 40 40
setBrush green VerPattern
brushRotate 30
drawRect 80 0 40 40
save
setPen brush 40 SolidLine FlatCap
setBrush NoBrush
drawLine 120 20 160 20
restore
end_block
restore
begin_block hintsuite
save
setRenderHint NonCosmeticBrushPatterns false
setRenderHint SmoothPixmapTransform false
translate 10 10
repeat_block drawrects
setRenderHint NonCosmeticBrushPatterns false
setRenderHint SmoothPixmapTransform true
translate 0 50
repeat_block drawrects
setRenderHint NonCosmeticBrushPatterns true
setRenderHint SmoothPixmapTransform false
translate 0 50
repeat_block drawrects
setRenderHint NonCosmeticBrushPatterns true
setRenderHint SmoothPixmapTransform true
translate 0 50
repeat_block drawrects
restore
end_block
save
translate 0 200
scale 2 2
repeat_block hintsuite
restore
save
translate 500 0
scale 1.5 2.5
rotate_y 60
repeat_block hintsuite
restore
translate 0 650
setBrush blue CrossPattern
setPen red
setRenderHint NonCosmeticBrushPatterns false
begin_block dots
save
drawRect 0 0 50 50
setBrushOrigin 12 0
drawRect 50 0 50 50
scale 2 1
drawRect 50 0 50 50
restore
end_block dots
setRenderHint NonCosmeticBrushPatterns true
translate 0 60
repeat_block dots

View File

@ -175,6 +175,12 @@ const char *PaintCommands::imageFormatTable[] = {
"RGBA32FPx4_Premultiplied",
};
const char *PaintCommands::renderHintTable[] = {
"Antialiasing",
"SmoothPixmapTransform",
"NonCosmeticBrushPatterns"
};
int PaintCommands::translateEnum(const char *table[], const QString &pattern, int limit)
{
QByteArray p = pattern.toLatin1().toLower();
@ -313,7 +319,7 @@ void PaintCommands::staticInit()
"pen_setCosmetic true");
DECL_PAINTCOMMAND("setRenderHint", command_setRenderHint,
"^setRenderHint\\s+([\\w_0-9]*)\\s*(\\w*)$",
"setRenderHint <Antialiasing|SmoothPixmapTransform> <true|false>",
"setRenderHint <hint> <true|false>",
"setRenderHint Antialiasing true");
DECL_PAINTCOMMAND("clearRenderHint", command_clearRenderHint,
"^clearRenderHint$",
@ -665,6 +671,7 @@ void PaintCommands::staticInit()
ADD_ENUMLIST("image formats", imageFormatTable);
ADD_ENUMLIST("coordinate modes", coordinateMethodTable);
ADD_ENUMLIST("size modes", sizeModeTable);
ADD_ENUMLIST("render hints", renderHintTable);
}
#undef DECL_PAINTCOMMAND
@ -2240,18 +2247,27 @@ void PaintCommands::command_setPen2(QRegularExpressionMatch re)
void PaintCommands::command_setRenderHint(QRegularExpressionMatch re)
{
QString hintString = re.captured(1).toLower();
bool on = re.captured(2).isEmpty() || re.captured(2).toLower() == "true";
if (hintString.contains("antialiasing")) {
if (m_verboseMode)
printf(" -(lance) setRenderHint Antialiasing\n");
QString setting = re.captured(2).toLower();
m_painter->setRenderHint(QPainter::Antialiasing, on);
bool on = setting.isEmpty() || setting == "true" || setting == "on";
QPainter::RenderHint hint;
int hintIdx = -1;
if (hintString.contains("antialiasing")) {
hintIdx = 0;
hint = QPainter::Antialiasing;
} else if (hintString.contains("smoothpixmaptransform")) {
hintIdx = 1;
hint = QPainter::SmoothPixmapTransform;
} else if (hintString.contains("noncosmeticbrushpatterns")) {
hintIdx = 2;
hint = QPainter::NonCosmeticBrushPatterns;
}
if (hintIdx >= 0) {
if (m_verboseMode)
printf(" -(lance) setRenderHint SmoothPixmapTransform\n");
m_painter->setRenderHint(QPainter::SmoothPixmapTransform, on);
printf(" -(lance) setRenderHint %s %s\n", renderHintTable[hintIdx], on ? "true" : "false");
m_painter->setRenderHint(hint, on);
} else {
fprintf(stderr, "ERROR(setRenderHint): unknown hint '%s'\n", qPrintable(hintString));
fprintf(stderr, "ERROR(setRenderHint): unknown hint '%s'\n", qPrintable(re.captured(1)));
}
}
@ -2260,6 +2276,7 @@ void PaintCommands::command_clearRenderHint(QRegularExpressionMatch /*re*/)
{
m_painter->setRenderHint(QPainter::Antialiasing, false);
m_painter->setRenderHint(QPainter::SmoothPixmapTransform, false);
m_painter->setRenderHint(QPainter::NonCosmeticBrushPatterns, false);
if (m_verboseMode)
printf(" -(lance) clearRenderHint\n");
}

View File

@ -280,6 +280,7 @@ private:
static const char *compositionModeTable[];
static const char *imageFormatTable[];
static const char *sizeModeTable[];
static const char *renderHintTable[];
static int translateEnum(const char *table[], const QString &pattern, int limit);
// utility