Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtextureglyphcache.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <qmath.h>
6
8#include "private/qfontengine_p.h"
9#include "private/qnumeric_p.h"
10
11#include <QtGui/qpainterpath.h>
12
14
15// #define CACHE_DEBUG
16
17// out-of-line to avoid vtable duplication, breaking e.g. RTTI
18QTextureGlyphCache::~QTextureGlyphCache()
19{
20}
21
22int QTextureGlyphCache::calculateSubPixelPositionCount(glyph_t glyph) const
23{
24 // Test 12 different subpixel positions since it factors into 3*4 so it gives
25 // the coverage we need.
26
27 const int NumSubpixelPositions = 12;
28
29 QImage images[NumSubpixelPositions];
30 int numImages = 0;
31 for (int i = 0; i < NumSubpixelPositions; ++i) {
32 QImage img = textureMapForGlyph(glyph, QFixedPoint(QFixed::fromReal(i / 12.0), 0));
33
34 if (numImages == 0) {
35 QPainterPath path;
36 QFixedPoint point;
37 m_current_fontengine->addGlyphsToPath(&glyph, &point, 1, &path, QTextItem::RenderFlags());
38
39 // Glyph is space, return 0 to indicate that we need to keep trying
40 if (path.isEmpty())
41 break;
42
43 images[numImages++] = std::move(img);
44 } else {
45 bool found = false;
46 for (int j = 0; j < numImages; ++j) {
47 if (images[j] == img) {
48 found = true;
49 break;
50 }
51 }
52 if (!found)
53 images[numImages++] = std::move(img);
54 }
55 }
56
57 return numImages;
58}
59
60bool QTextureGlyphCache::populate(QFontEngine *fontEngine,
61 qsizetype numGlyphs,
62 const glyph_t *glyphs,
63 const QFixedPoint *positions,
64 QPainter::RenderHints renderHints,
65 bool includeGlyphCacheScale)
66{
67#ifdef CACHE_DEBUG
68 printf("Populating with %lld glyphs\n", static_cast<long long>(numGlyphs));
69 qDebug() << " -> current transformation: " << m_transform;
70#endif
71
72 m_current_fontengine = fontEngine;
73 const int padding = glyphPadding();
74 const int paddingDoubled = padding * 2;
75
76 bool supportsSubPixelPositions = fontEngine->supportsSubPixelPositions();
77 bool verticalSubPixelPositions = fontEngine->supportsVerticalSubPixelPositions()
78 && (renderHints & QPainter::VerticalSubpixelPositioning) != 0;
79 if (fontEngine->m_subPixelPositionCount == 0) {
80 if (!supportsSubPixelPositions) {
81 fontEngine->m_subPixelPositionCount = 1;
82 } else {
83 qsizetype i = 0;
84 while (fontEngine->m_subPixelPositionCount == 0 && i < numGlyphs)
85 fontEngine->m_subPixelPositionCount = calculateSubPixelPositionCount(glyphs[i++]);
86 }
87 }
88
89 if (m_cx == 0 && m_cy == 0) {
90 m_cx = padding;
91 m_cy = padding;
92 }
93
94 qreal glyphCacheScaleX = transform().m11();
95 qreal glyphCacheScaleY = transform().m22();
96
97 QHash<GlyphAndSubPixelPosition, Coord> listItemCoordinates;
98 int rowHeight = 0;
99
100 // check each glyph for its metrics and get the required rowHeight.
101 for (qsizetype i = 0; i < numGlyphs; ++i) {
102 const glyph_t glyph = glyphs[i];
103
104 QFixedPoint subPixelPosition;
105 if (supportsSubPixelPositions) {
106 QFixedPoint pos = positions != nullptr ? positions[i] : QFixedPoint();
107 if (includeGlyphCacheScale) {
108 pos = QFixedPoint(QFixed::fromReal(pos.x.toReal() * glyphCacheScaleX),
109 QFixed::fromReal(pos.y.toReal() * glyphCacheScaleY));
110 }
111 subPixelPosition = fontEngine->subPixelPositionFor(pos);
112 if (!verticalSubPixelPositions)
113 subPixelPosition.y = 0;
114 }
115
116 if (coords.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition)))
117 continue;
118 if (listItemCoordinates.contains(GlyphAndSubPixelPosition(glyph, subPixelPosition)))
119 continue;
120
121 glyph_metrics_t metrics = fontEngine->alphaMapBoundingBox(glyph, subPixelPosition, m_transform, m_format);
122
123#ifdef CACHE_DEBUG
124 printf("(%4x): w=%.2f, h=%.2f, xoff=%.2f, yoff=%.2f, x=%.2f, y=%.2f\n",
125 glyph,
126 metrics.width.toReal(),
127 metrics.height.toReal(),
128 metrics.xoff.toReal(),
129 metrics.yoff.toReal(),
130 metrics.x.toReal(),
131 metrics.y.toReal());
132#endif
133 GlyphAndSubPixelPosition key(glyph, subPixelPosition);
134 int glyph_width = metrics.width.ceil().toInt();
135 int glyph_height = metrics.height.ceil().toInt();
136 if (glyph_height == 0 || glyph_width == 0) {
137 // Avoid multiple calls to boundingBox() for non-printable characters
138 Coord c = { 0, 0, 0, 0, 0, 0 };
139 coords.insert(key, c);
140 continue;
141 }
142 // align to 8-bit boundary
143 if (m_format == QFontEngine::Format_Mono)
144 glyph_width = (glyph_width+7)&~7;
145
146 Coord c = { 0, 0, // will be filled in later
147 glyph_width,
148 glyph_height, // texture coords
149 metrics.x.truncate(),
150 -metrics.y.truncate() }; // baseline for horizontal scripts
151
152 listItemCoordinates.insert(key, c);
153 rowHeight = qMax(rowHeight, glyph_height);
154 }
155 if (listItemCoordinates.isEmpty())
156 return true;
157
158 rowHeight += paddingDoubled;
159
160 if (m_w == 0) {
161 if (fontEngine->maxCharWidth() <= QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH)
163 else
164 m_w = qNextPowerOfTwo(qCeil(fontEngine->maxCharWidth()) - 1);
165 }
166
167 // now actually use the coords and paint the wanted glyps into cache.
168 QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = listItemCoordinates.begin();
169 int requiredWidth = m_w;
170 while (iter != listItemCoordinates.end()) {
171 Coord c = iter.value();
172
173 m_currentRowHeight = qMax(m_currentRowHeight, c.h);
174
175 if (m_cx + c.w + padding > requiredWidth) {
176 int new_width = requiredWidth*2;
177 while (new_width < m_cx + c.w + padding)
178 new_width *= 2;
179 if (new_width <= maxTextureWidth()) {
180 requiredWidth = new_width;
181 } else {
182 // no room on the current line, start new glyph strip
183 m_cx = padding;
184 m_cy += m_currentRowHeight + paddingDoubled;
185 m_currentRowHeight = c.h; // New row
186 }
187 }
188
189 if (maxTextureHeight() > 0 && m_cy + c.h + padding > maxTextureHeight()) {
190 // We can't make a cache of the required size, so we bail out
191 return false;
192 }
193
194 c.x = m_cx;
195 c.y = m_cy;
196
197 coords.insert(iter.key(), c);
198 m_pendingGlyphs.insert(iter.key(), c);
199
200 m_cx += c.w + paddingDoubled;
201 ++iter;
202 }
203 return true;
204
205}
206
207void QTextureGlyphCache::fillInPendingGlyphs()
208{
209 if (!hasPendingGlyphs())
210 return;
211
212 int requiredHeight = m_h;
213 int requiredWidth = m_w; // Use a minimum size to avoid a lot of initial reallocations
214 const int padding = glyphPadding();
215 {
216 QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin();
217 while (iter != m_pendingGlyphs.end()) {
218 Coord c = iter.value();
219 requiredHeight = qMax(requiredHeight, c.y + c.h + padding);
220 requiredWidth = qMax(requiredWidth, c.x + c.w + padding);
221 ++iter;
222 }
223 }
224
225 if (isNull() || requiredHeight > m_h || requiredWidth > m_w) {
226 if (isNull())
227 createCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1));
228 else
229 resizeCache(qNextPowerOfTwo(requiredWidth - 1), qNextPowerOfTwo(requiredHeight - 1));
230 }
231
232 beginFillTexture();
233 {
234 QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin();
235 while (iter != m_pendingGlyphs.end()) {
236 GlyphAndSubPixelPosition key = iter.key();
237 fillTexture(iter.value(), key.glyph, key.subPixelPosition);
238
239 ++iter;
240 }
241 }
242 endFillTexture();
243
244 m_pendingGlyphs.clear();
245}
246
247QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition) const
248{
249 switch (m_format) {
250 case QFontEngine::Format_A32:
251 return m_current_fontengine->alphaRGBMapForGlyph(g, subPixelPosition, m_transform);
252 case QFontEngine::Format_ARGB:
253 return m_current_fontengine->bitmapForGlyph(g, subPixelPosition, m_transform, color());
254 default:
255 return m_current_fontengine->alphaMapForGlyph(g, subPixelPosition, m_transform);
256 }
257}
258
259/************************************************************************
260 * QImageTextureGlyphCache
261 */
262
263// out-of-line to avoid vtable duplication, breaking e.g. RTTI
264QImageTextureGlyphCache::~QImageTextureGlyphCache()
265{
266}
267
268void QImageTextureGlyphCache::resizeTextureData(int width, int height)
269{
270 m_image = m_image.copy(0, 0, width, height);
271 // Regions not part of the copy are initialized to 0, and that is just what
272 // we need.
273}
274
275void QImageTextureGlyphCache::createTextureData(int width, int height)
276{
277 switch (m_format) {
278 case QFontEngine::Format_Mono:
279 m_image = QImage(width, height, QImage::Format_Mono);
280 break;
281 case QFontEngine::Format_A8:
282 m_image = QImage(width, height, QImage::Format_Alpha8);
283 break;
284 case QFontEngine::Format_A32:
285 m_image = QImage(width, height, QImage::Format_RGB32);
286 break;
287 case QFontEngine::Format_ARGB:
288 m_image = QImage(width, height, QImage::Format_ARGB32_Premultiplied);
289 break;
290 default:
291 Q_UNREACHABLE();
292 }
293
294 // Regions not touched by the glyphs must be initialized to 0. (such
295 // locations may in fact be sampled with styled (shifted) text materials)
296 // When resizing, the QImage copy() does this implicitly but the initial
297 // contents must be zeroed out explicitly here.
298 m_image.fill(0);
299}
300
301void QImageTextureGlyphCache::fillTexture(const Coord &c,
302 glyph_t g,
303 const QFixedPoint &subPixelPosition)
304{
305 QImage mask = textureMapForGlyph(g, subPixelPosition);
306 if (mask.isNull())
307 return;
308
309#ifdef CACHE_DEBUG
310 printf("fillTexture of %dx%d at %d,%d in the cache of %dx%d\n", c.w, c.h, c.x, c.y, m_image.width(), m_image.height());
311 if (mask.width() > c.w || mask.height() > c.h) {
312 printf(" ERROR; mask is bigger than reserved space! %dx%d instead of %dx%d\n", mask.width(), mask.height(), c.w,c.h);
313 return;
314 }
315#endif
316 if (m_format == QFontEngine::Format_A32
317 || m_format == QFontEngine::Format_ARGB) {
318 QImage ref(m_image.bits() + (c.x * 4 + c.y * m_image.bytesPerLine()),
319 qMin(mask.width(), c.w), qMin(mask.height(), c.h), m_image.bytesPerLine(),
320 m_image.format());
321 QPainter p(&ref);
322 p.setCompositionMode(QPainter::CompositionMode_Source);
323 p.fillRect(0, 0, c.w, c.h, QColor(0,0,0,0)); // TODO optimize this
324 p.drawImage(0, 0, mask);
325 p.end();
326 } else if (m_format == QFontEngine::Format_Mono) {
327 if (mask.depth() > 1) {
328 // TODO optimize this
329 mask.convertTo(QImage::Format_Alpha8);
330 mask.reinterpretAsFormat(QImage::Format_Grayscale8);
331 mask.invertPixels();
332 mask.convertTo(QImage::Format_Mono, Qt::ThresholdDither);
333 }
334
335 int mw = qMin(mask.width(), c.w);
336 int mh = qMin(mask.height(), c.h);
337 uchar *d = m_image.bits();
338 qsizetype dbpl = m_image.bytesPerLine();
339
340 for (int y = 0; y < c.h; ++y) {
341 uchar *dest = d + (c.y + y) *dbpl + c.x/8;
342
343 if (y < mh) {
344 const uchar *src = mask.constScanLine(y);
345 for (int x = 0; x < c.w/8; ++x) {
346 if (x < (mw+7)/8)
347 dest[x] = src[x];
348 else
349 dest[x] = 0;
350 }
351 } else {
352 for (int x = 0; x < c.w/8; ++x)
353 dest[x] = 0;
354 }
355 }
356 } else { // A8
357 int mw = qMin(mask.width(), c.w);
358 int mh = qMin(mask.height(), c.h);
359 uchar *d = m_image.bits();
360 qsizetype dbpl = m_image.bytesPerLine();
361
362 if (mask.depth() == 1) {
363 for (int y = 0; y < c.h; ++y) {
364 uchar *dest = d + (c.y + y) *dbpl + c.x;
365 if (y < mh) {
366 const uchar *src = mask.constScanLine(y);
367 for (int x = 0; x < c.w; ++x) {
368 if (x < mw)
369 dest[x] = (src[x >> 3] & (1 << (7 - (x & 7)))) > 0 ? 255 : 0;
370 }
371 }
372 }
373 } else if (mask.depth() == 8) {
374 for (int y = 0; y < c.h; ++y) {
375 uchar *dest = d + (c.y + y) *dbpl + c.x;
376 if (y < mh) {
377 const uchar *src = mask.constScanLine(y);
378 for (int x = 0; x < c.w; ++x) {
379 if (x < mw)
380 dest[x] = src[x];
381 }
382 }
383 }
384 }
385 }
386
387#ifdef CACHE_DEBUG
388// QPainter p(&m_image);
389// p.drawLine(
390 int margin = m_current_fontengine ? m_current_fontengine->glyphMargin(m_format) : 0;
391 QPoint base(c.x + margin, c.y + margin + c.baseLineY-1);
392 if (m_image.rect().contains(base))
393 m_image.setPixel(base, 255);
394 m_image.save(QString::fromLatin1("cache-%1.png").arg(qint64(this)));
395#endif
396}
397
398QT_END_NAMESPACE
Combined button and popup list for selecting options.
#define QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH