summaryrefslogtreecommitdiffstats
path: root/src/gui/painting
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2021-01-15 09:11:07 +0100
committerEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>2021-01-25 15:56:50 +0100
commitd79a9b1a4f694a227ce62ccab9b44685a9755916 (patch)
treee943780120ee3abcf987ecd0c05440f9ed5a9b7b /src/gui/painting
parent80e87ca05c2b421dbca9fc8f9a8f290a3eabc206 (diff)
Implement vertical subpixel positioning where available
For some use cases, vertical subpixel positioning may be useful, as it allows you to vertically align text with other painting primitives. This does impose an overhead, so we make it opt-int with a render hint on the painter. Note that this is only supported on Freetype currently. It might be possible to support on older macOS versions, prior to Mojave (which has disabled subpixel positioning entirely), but since it would have limited usefulness and Freetype is cross-platform anyway, I skipped that. Note: This drive-by-fixes an issue with subpixel positioning where glyphs would always be offset by 1/64, because we added the aliasing offset *after* we had determined the closest subpixel position. The idea of this, as far as I can understand, is rather to snap to nearest subpixel position upwards, not to add an offset to all glyphs, so it should be added before finding the correct position. It had a subtle visual effect when animating the position. It might be that we could get rid of it entirely, as I haven't been able to reproduce any issues with that, but I have moved it instead, to match what I believe the intention was. [ChangeLog][QtGui][Text] Added render hint flag QPainter::VerticalSubpixelPositioning which will position text at subpixel positions vertically whenever supported. In absence of this, text position will be rounded vertically as before. Fixes: QTBUG-35682 Change-Id: I8ce7a72a64e5a0924dac7c244e3e07c2938bfd09 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'src/gui/painting')
-rw-r--r--src/gui/painting/qfixed_p.h4
-rw-r--r--src/gui/painting/qpaintengine.cpp2
-rw-r--r--src/gui/painting/qpaintengine_raster.cpp26
-rw-r--r--src/gui/painting/qpainter.cpp10
-rw-r--r--src/gui/painting/qpainter.h1
-rw-r--r--src/gui/painting/qtextureglyphcache.cpp25
-rw-r--r--src/gui/painting/qtextureglyphcache_p.h29
7 files changed, 73 insertions, 24 deletions
diff --git a/src/gui/painting/qfixed_p.h b/src/gui/painting/qfixed_p.h
index 90d214fe96..7580069153 100644
--- a/src/gui/painting/qfixed_p.h
+++ b/src/gui/painting/qfixed_p.h
@@ -196,6 +196,10 @@ struct QFixedPoint {
constexpr static QFixedPoint fromPointF(const QPointF &p) {
return QFixedPoint(QFixed::fromReal(p.x()), QFixed::fromReal(p.y()));
}
+ constexpr inline bool operator==(const QFixedPoint &other) const
+ {
+ return x == other.x && y == other.y;
+ }
};
Q_DECLARE_TYPEINFO(QFixedPoint, Q_PRIMITIVE_TYPE);
diff --git a/src/gui/painting/qpaintengine.cpp b/src/gui/painting/qpaintengine.cpp
index 5a0d268c1e..42606a93f0 100644
--- a/src/gui/painting/qpaintengine.cpp
+++ b/src/gui/painting/qpaintengine.cpp
@@ -766,7 +766,7 @@ void QPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
bool((painter()->renderHints() & QPainter::TextAntialiasing)
&& !(painter()->font().styleStrategy() & QFont::NoAntialias)));
for (int i = 0; i < ti.glyphs.numGlyphs; ++i) {
- QImage glyph = ti.fontEngine->bitmapForGlyph(glyphs[i], QFixed(), QTransform());
+ QImage glyph = ti.fontEngine->bitmapForGlyph(glyphs[i], QFixedPoint(), QTransform());
painter()->drawImage(positions[i].x.toReal(), positions[i].y.toReal(), glyph);
}
painter()->restore();
diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp
index ca1c4f831e..26f8de5b8b 100644
--- a/src/gui/painting/qpaintengine_raster.cpp
+++ b/src/gui/painting/qpaintengine_raster.cpp
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
@@ -2812,6 +2812,9 @@ bool QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs,
Q_D(QRasterPaintEngine);
QRasterPaintEngineState *s = state();
+ bool verticalSubPixelPositions = fontEngine->supportsVerticalSubPixelPositions()
+ && (s->renderHints & QPainter::VerticalSubpixelPositioning) != 0;
+
if (fontEngine->hasInternalCaching()) {
QFontEngine::GlyphFormat neededFormat =
painter()->device()->devType() == QInternal::Widget
@@ -2822,7 +2825,9 @@ bool QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs,
neededFormat = QFontEngine::Format_Mono;
for (int i = 0; i < numGlyphs; i++) {
- QFixed spp = fontEngine->subPixelPositionForX(positions[i].x);
+ QFixedPoint spp = fontEngine->subPixelPositionFor(positions[i]);
+ if (!verticalSubPixelPositions)
+ spp.y = 0;
const QFontEngine::Glyph *alphaMap = fontEngine->glyphData(glyphs[i], spp, neededFormat, s->matrix);
if (!alphaMap)
@@ -2847,9 +2852,13 @@ bool QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs,
Q_UNREACHABLE();
};
+ QFixed y = verticalSubPixelPositions
+ ? qFloor(positions[i].y)
+ : qRound(positions[i].y);
+
alphaPenBlt(alphaMap->data, bytesPerLine, depth,
qFloor(positions[i].x) + alphaMap->x,
- qRound(positions[i].y) - alphaMap->y,
+ qFloor(y) - alphaMap->y,
alphaMap->width, alphaMap->height,
fontEngine->expectsGammaCorrectedBlending());
}
@@ -2864,7 +2873,7 @@ bool QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs,
fontEngine->setGlyphCache(nullptr, cache);
}
- cache->populate(fontEngine, numGlyphs, glyphs, positions);
+ cache->populate(fontEngine, numGlyphs, glyphs, positions, s->renderHints);
cache->fillInPendingGlyphs();
const QImage &image = cache->image();
@@ -2881,15 +2890,20 @@ bool QRasterPaintEngine::drawCachedGlyphs(int numGlyphs, const glyph_t *glyphs,
int margin = fontEngine->glyphMargin(glyphFormat);
const uchar *bits = image.bits();
for (int i=0; i<numGlyphs; ++i) {
+ QFixedPoint subPixelPosition = fontEngine->subPixelPositionFor(positions[i]);
+ if (!verticalSubPixelPositions)
+ subPixelPosition.y = 0;
- QFixed subPixelPosition = fontEngine->subPixelPositionForX(positions[i].x);
QTextureGlyphCache::GlyphAndSubPixelPosition glyph(glyphs[i], subPixelPosition);
const QTextureGlyphCache::Coord &c = cache->coords[glyph];
if (c.isNull())
continue;
int x = qFloor(positions[i].x) + c.baseLineX - margin;
- int y = qRound(positions[i].y) - c.baseLineY - margin;
+ int y = (verticalSubPixelPositions
+ ? qFloor(positions[i].y)
+ : qRound(positions[i].y));
+ y -= c.baseLineY + margin;
// printf("drawing [%d %d %d %d] baseline [%d %d], glyph: %d, to: %d %d, pos: %d %d\n",
// c.x, c.y,
diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp
index f965761567..38ce9bf604 100644
--- a/src/gui/painting/qpainter.cpp
+++ b/src/gui/painting/qpainter.cpp
@@ -1429,6 +1429,16 @@ void QPainterPrivate::updateState(QPainterState *newState)
a smooth pixmap transformation algorithm (such as bilinear) rather
than nearest neighbor.
+ \value VerticalSubpixelPositioning Allow text to be positioned at fractions
+ of pixels vertically as well as horizontally, if this is supported by the
+ font engine. This is currently supported by Freetype on all platforms when
+ the hinting preference is QFont::PreferNoHinting, and also on macOS. For
+ most use cases this will not improve visual quality, but may increase memory
+ consumption and some reduction in text rendering performance. Therefore, enabling
+ this is not recommended unless the use case requires it. One such use case could
+ be aligning glyphs with other visual primitives.
+ This value was added in Qt 6.1.
+
\value LosslessImageRendering Use a lossless image rendering, whenever possible.
Currently, this hint is only used when QPainter is employed to output a PDF
file through QPrinter or QPdfWriter, where drawImage()/drawPixmap() calls
diff --git a/src/gui/painting/qpainter.h b/src/gui/painting/qpainter.h
index 68f5fc421b..8d471e938d 100644
--- a/src/gui/painting/qpainter.h
+++ b/src/gui/painting/qpainter.h
@@ -87,6 +87,7 @@ public:
Antialiasing = 0x01,
TextAntialiasing = 0x02,
SmoothPixmapTransform = 0x04,
+ VerticalSubpixelPositioning = 0x08,
LosslessImageRendering = 0x40,
};
Q_FLAG(RenderHint)
diff --git a/src/gui/painting/qtextureglyphcache.cpp b/src/gui/painting/qtextureglyphcache.cpp
index 3728060caf..dfb937ed84 100644
--- a/src/gui/painting/qtextureglyphcache.cpp
+++ b/src/gui/painting/qtextureglyphcache.cpp
@@ -64,7 +64,7 @@ int QTextureGlyphCache::calculateSubPixelPositionCount(glyph_t glyph) const
QImage images[NumSubpixelPositions];
int numImages = 0;
for (int i = 0; i < NumSubpixelPositions; ++i) {
- QImage img = textureMapForGlyph(glyph, QFixed::fromReal(i / 12.0));
+ QImage img = textureMapForGlyph(glyph, QFixedPoint(QFixed::fromReal(i / 12.0), 0));
if (numImages == 0) {
QPainterPath path;
@@ -92,8 +92,11 @@ int QTextureGlyphCache::calculateSubPixelPositionCount(glyph_t glyph) const
return numImages;
}
-bool QTextureGlyphCache::populate(QFontEngine *fontEngine, int numGlyphs, const glyph_t *glyphs,
- const QFixedPoint *positions)
+bool QTextureGlyphCache::populate(QFontEngine *fontEngine,
+ int numGlyphs,
+ const glyph_t *glyphs,
+ const QFixedPoint *positions,
+ QPainter::RenderHints renderHints)
{
#ifdef CACHE_DEBUG
printf("Populating with %d glyphs\n", numGlyphs);
@@ -105,6 +108,8 @@ bool QTextureGlyphCache::populate(QFontEngine *fontEngine, int numGlyphs, const
const int paddingDoubled = padding * 2;
bool supportsSubPixelPositions = fontEngine->supportsSubPixelPositions();
+ bool verticalSubPixelPositions = fontEngine->supportsVerticalSubPixelPositions()
+ && (renderHints & QPainter::VerticalSubpixelPositioning) != 0;
if (fontEngine->m_subPixelPositionCount == 0) {
if (!supportsSubPixelPositions) {
fontEngine->m_subPixelPositionCount = 1;
@@ -127,10 +132,12 @@ bool QTextureGlyphCache::populate(QFontEngine *fontEngine, int numGlyphs, const
for (int i=0; i < numGlyphs; ++i) {
const glyph_t glyph = glyphs[i];
- QFixed subPixelPosition;
+ QFixedPoint subPixelPosition;
if (supportsSubPixelPositions) {
- QFixed x = positions != nullptr ? positions[i].x : QFixed();
- subPixelPosition = fontEngine->subPixelPositionForX(x);
+ QFixedPoint pos = positions != nullptr ? positions[i] : QFixedPoint();
+ subPixelPosition = fontEngine->subPixelPositionFor(pos);
+ if (!verticalSubPixelPositions)
+ subPixelPosition.y = 0;
}
if (coords.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition)))
@@ -263,7 +270,7 @@ void QTextureGlyphCache::fillInPendingGlyphs()
m_pendingGlyphs.clear();
}
-QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, QFixed subPixelPosition) const
+QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition) const
{
switch (m_format) {
case QFontEngine::Format_A32:
@@ -317,7 +324,9 @@ void QImageTextureGlyphCache::createTextureData(int width, int height)
m_image.fill(0);
}
-void QImageTextureGlyphCache::fillTexture(const Coord &c, glyph_t g, QFixed subPixelPosition)
+void QImageTextureGlyphCache::fillTexture(const Coord &c,
+ glyph_t g,
+ const QFixedPoint &subPixelPosition)
{
QImage mask = textureMapForGlyph(g, subPixelPosition);
diff --git a/src/gui/painting/qtextureglyphcache_p.h b/src/gui/painting/qtextureglyphcache_p.h
index e34687d816..a401778a63 100644
--- a/src/gui/painting/qtextureglyphcache_p.h
+++ b/src/gui/painting/qtextureglyphcache_p.h
@@ -83,7 +83,8 @@ public:
struct GlyphAndSubPixelPosition
{
- GlyphAndSubPixelPosition(glyph_t g, QFixed spp) : glyph(g), subPixelPosition(spp) {}
+ GlyphAndSubPixelPosition(glyph_t g, const QFixedPoint &spp)
+ : glyph(g), subPixelPosition(spp) {}
bool operator==(const GlyphAndSubPixelPosition &other) const
{
@@ -91,7 +92,7 @@ public:
}
glyph_t glyph;
- QFixed subPixelPosition;
+ QFixedPoint subPixelPosition;
};
struct Coord {
@@ -109,8 +110,11 @@ public:
}
};
- bool populate(QFontEngine *fontEngine, int numGlyphs, const glyph_t *glyphs,
- const QFixedPoint *positions);
+ bool populate(QFontEngine *fontEngine,
+ int numGlyphs,
+ const glyph_t *glyphs,
+ const QFixedPoint *positions,
+ QPainter::RenderHints renderHints = QPainter::RenderHints());
bool hasPendingGlyphs() const { return !m_pendingGlyphs.isEmpty(); }
void fillInPendingGlyphs();
@@ -119,7 +123,9 @@ public:
virtual int glyphPadding() const { return 0; }
virtual void beginFillTexture() { }
- virtual void fillTexture(const Coord &coord, glyph_t glyph, QFixed subPixelPosition) = 0;
+ virtual void fillTexture(const Coord &coord,
+ glyph_t glyph,
+ const QFixedPoint &subPixelPosition) = 0;
virtual void endFillTexture() { }
inline void createCache(int width, int height) {
@@ -141,7 +147,7 @@ public:
virtual int maxTextureWidth() const { return QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH; }
virtual int maxTextureHeight() const { return -1; }
- QImage textureMapForGlyph(glyph_t g, QFixed subPixelPosition) const;
+ QImage textureMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition) const;
protected:
int calculateSubPixelPositionCount(glyph_t) const;
@@ -156,9 +162,12 @@ protected:
int m_currentRowHeight; // Height of last row
};
-inline size_t qHash(const QTextureGlyphCache::GlyphAndSubPixelPosition &g)
+inline size_t qHash(const QTextureGlyphCache::GlyphAndSubPixelPosition &g, size_t seed = 0)
{
- return (g.glyph << 8) | (g.subPixelPosition * 10).round().toInt();
+ return qHashMulti(seed,
+ g.glyph,
+ g.subPixelPosition.x.value(),
+ g.subPixelPosition.y.value());
}
@@ -171,7 +180,9 @@ public:
virtual void createTextureData(int width, int height) override;
virtual void resizeTextureData(int width, int height) override;
- virtual void fillTexture(const Coord &c, glyph_t glyph, QFixed subPixelPosition) override;
+ virtual void fillTexture(const Coord &c,
+ glyph_t glyph,
+ const QFixedPoint &subPixelPosition) override;
inline const QImage &image() const { return m_image; }