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