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 | |
6 | #include "qtextureglyphcache_p.h" |
7 | #include "private/qfontengine_p.h" |
8 | #include "private/qnumeric_p.h" |
9 | |
10 | #include <QtGui/qpainterpath.h> |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | // #define CACHE_DEBUG |
15 | |
16 | // out-of-line to avoid vtable duplication, breaking e.g. RTTI |
17 | QTextureGlyphCache::~QTextureGlyphCache() |
18 | { |
19 | } |
20 | |
21 | int 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(g: glyph, subPixelPosition: QFixedPoint(QFixed::fromReal(r: i / 12.0), 0)); |
32 | |
33 | if (numImages == 0) { |
34 | QPainterPath path; |
35 | QFixedPoint point; |
36 | m_current_fontengine->addGlyphsToPath(glyphs: &glyph, positions: &point, nglyphs: 1, path: &path, flags: 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 | |
59 | bool 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(glyph: 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(r: pos.x.toReal() * glyphCacheScaleX), |
108 | QFixed::fromReal(r: pos.y.toReal() * glyphCacheScaleY)); |
109 | } |
110 | subPixelPosition = fontEngine->subPixelPositionFor(position: pos); |
111 | if (!verticalSubPixelPositions) |
112 | subPixelPosition.y = 0; |
113 | } |
114 | |
115 | if (coords.contains(key: GlyphAndSubPixelPosition(glyph, subPixelPosition))) |
116 | continue; |
117 | if (listItemCoordinates.contains(key: GlyphAndSubPixelPosition(glyph, subPixelPosition))) |
118 | continue; |
119 | |
120 | glyph_metrics_t metrics = fontEngine->alphaMapBoundingBox(glyph, subPixelPosition, matrix: 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 = { .x: 0, .y: 0, .w: 0, .h: 0, .baseLineX: 0, .baseLineY: 0 }; |
138 | coords.insert(key, value: 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 = { .x: 0, .y: 0, // will be filled in later |
146 | .w: glyph_width, |
147 | .h: glyph_height, // texture coords |
148 | .baseLineX: metrics.x.truncate(), |
149 | .baseLineY: -metrics.y.truncate() }; // baseline for horizontal scripts |
150 | |
151 | listItemCoordinates.insert(key, value: c); |
152 | rowHeight = qMax(a: rowHeight, b: 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) |
161 | m_w = QT_DEFAULT_TEXTURE_GLYPH_CACHE_WIDTH; |
162 | else |
163 | m_w = qNextPowerOfTwo(v: qCeil(v: 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(a: m_currentRowHeight, b: 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(key: iter.key(), value: c); |
197 | m_pendingGlyphs.insert(key: iter.key(), value: c); |
198 | |
199 | m_cx += c.w + paddingDoubled; |
200 | ++iter; |
201 | } |
202 | return true; |
203 | |
204 | } |
205 | |
206 | void 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 | { |
214 | QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin(); |
215 | while (iter != m_pendingGlyphs.end()) { |
216 | Coord c = iter.value(); |
217 | requiredHeight = qMax(a: requiredHeight, b: c.y + c.h); |
218 | requiredWidth = qMax(a: requiredWidth, b: c.x + c.w); |
219 | ++iter; |
220 | } |
221 | } |
222 | |
223 | if (isNull() || requiredHeight > m_h || requiredWidth > m_w) { |
224 | if (isNull()) |
225 | createCache(width: qNextPowerOfTwo(v: requiredWidth - 1), height: qNextPowerOfTwo(v: requiredHeight - 1)); |
226 | else |
227 | resizeCache(width: qNextPowerOfTwo(v: requiredWidth - 1), height: qNextPowerOfTwo(v: requiredHeight - 1)); |
228 | } |
229 | |
230 | beginFillTexture(); |
231 | { |
232 | QHash<GlyphAndSubPixelPosition, Coord>::iterator iter = m_pendingGlyphs.begin(); |
233 | while (iter != m_pendingGlyphs.end()) { |
234 | GlyphAndSubPixelPosition key = iter.key(); |
235 | fillTexture(coord: iter.value(), glyph: key.glyph, subPixelPosition: key.subPixelPosition); |
236 | |
237 | ++iter; |
238 | } |
239 | } |
240 | endFillTexture(); |
241 | |
242 | m_pendingGlyphs.clear(); |
243 | } |
244 | |
245 | QImage QTextureGlyphCache::textureMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition) const |
246 | { |
247 | switch (m_format) { |
248 | case QFontEngine::Format_A32: |
249 | return m_current_fontengine->alphaRGBMapForGlyph(g, subPixelPosition, t: m_transform); |
250 | case QFontEngine::Format_ARGB: |
251 | return m_current_fontengine->bitmapForGlyph(g, subPixelPosition, t: m_transform, color: color()); |
252 | default: |
253 | return m_current_fontengine->alphaMapForGlyph(g, subPixelPosition, t: m_transform); |
254 | } |
255 | } |
256 | |
257 | /************************************************************************ |
258 | * QImageTextureGlyphCache |
259 | */ |
260 | |
261 | // out-of-line to avoid vtable duplication, breaking e.g. RTTI |
262 | QImageTextureGlyphCache::~QImageTextureGlyphCache() |
263 | { |
264 | } |
265 | |
266 | void QImageTextureGlyphCache::resizeTextureData(int width, int height) |
267 | { |
268 | m_image = m_image.copy(x: 0, y: 0, w: width, h: height); |
269 | // Regions not part of the copy are initialized to 0, and that is just what |
270 | // we need. |
271 | } |
272 | |
273 | void QImageTextureGlyphCache::createTextureData(int width, int height) |
274 | { |
275 | switch (m_format) { |
276 | case QFontEngine::Format_Mono: |
277 | m_image = QImage(width, height, QImage::Format_Mono); |
278 | break; |
279 | case QFontEngine::Format_A8: |
280 | m_image = QImage(width, height, QImage::Format_Alpha8); |
281 | break; |
282 | case QFontEngine::Format_A32: |
283 | m_image = QImage(width, height, QImage::Format_RGB32); |
284 | break; |
285 | case QFontEngine::Format_ARGB: |
286 | m_image = QImage(width, height, QImage::Format_ARGB32_Premultiplied); |
287 | break; |
288 | default: |
289 | Q_UNREACHABLE(); |
290 | } |
291 | |
292 | // Regions not touched by the glyphs must be initialized to 0. (such |
293 | // locations may in fact be sampled with styled (shifted) text materials) |
294 | // When resizing, the QImage copy() does this implicitly but the initial |
295 | // contents must be zeroed out explicitly here. |
296 | m_image.fill(pixel: 0); |
297 | } |
298 | |
299 | void QImageTextureGlyphCache::fillTexture(const Coord &c, |
300 | glyph_t g, |
301 | const QFixedPoint &subPixelPosition) |
302 | { |
303 | QImage mask = textureMapForGlyph(g, subPixelPosition); |
304 | |
305 | #ifdef CACHE_DEBUG |
306 | 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()); |
307 | if (mask.width() > c.w || mask.height() > c.h) { |
308 | printf(" ERROR; mask is bigger than reserved space! %dx%d instead of %dx%d\n" , mask.width(), mask.height(), c.w,c.h); |
309 | return; |
310 | } |
311 | #endif |
312 | Q_ASSERT(mask.width() <= c.w && mask.height() <= c.h); |
313 | |
314 | if (m_format == QFontEngine::Format_A32 |
315 | || m_format == QFontEngine::Format_ARGB) { |
316 | QImage ref(m_image.bits() + (c.x * 4 + c.y * m_image.bytesPerLine()), |
317 | qMin(a: mask.width(), b: c.w), qMin(a: mask.height(), b: c.h), m_image.bytesPerLine(), |
318 | m_image.format()); |
319 | QPainter p(&ref); |
320 | p.setCompositionMode(QPainter::CompositionMode_Source); |
321 | p.fillRect(x: 0, y: 0, w: c.w, h: c.h, b: QColor(0,0,0,0)); // TODO optimize this |
322 | p.drawImage(x: 0, y: 0, image: mask); |
323 | p.end(); |
324 | } else if (m_format == QFontEngine::Format_Mono) { |
325 | if (mask.depth() > 1) { |
326 | // TODO optimize this |
327 | mask.convertTo(f: QImage::Format_Alpha8); |
328 | mask.reinterpretAsFormat(f: QImage::Format_Grayscale8); |
329 | mask.invertPixels(); |
330 | mask.convertTo(f: QImage::Format_Mono, flags: Qt::ThresholdDither); |
331 | } |
332 | |
333 | int mw = qMin(a: mask.width(), b: c.w); |
334 | int mh = qMin(a: mask.height(), b: c.h); |
335 | uchar *d = m_image.bits(); |
336 | qsizetype dbpl = m_image.bytesPerLine(); |
337 | |
338 | for (int y = 0; y < c.h; ++y) { |
339 | uchar *dest = d + (c.y + y) *dbpl + c.x/8; |
340 | |
341 | if (y < mh) { |
342 | const uchar *src = mask.constScanLine(y); |
343 | for (int x = 0; x < c.w/8; ++x) { |
344 | if (x < (mw+7)/8) |
345 | dest[x] = src[x]; |
346 | else |
347 | dest[x] = 0; |
348 | } |
349 | } else { |
350 | for (int x = 0; x < c.w/8; ++x) |
351 | dest[x] = 0; |
352 | } |
353 | } |
354 | } else { // A8 |
355 | int mw = qMin(a: mask.width(), b: c.w); |
356 | int mh = qMin(a: mask.height(), b: c.h); |
357 | uchar *d = m_image.bits(); |
358 | qsizetype dbpl = m_image.bytesPerLine(); |
359 | |
360 | if (mask.depth() == 1) { |
361 | for (int y = 0; y < c.h; ++y) { |
362 | uchar *dest = d + (c.y + y) *dbpl + c.x; |
363 | if (y < mh) { |
364 | const uchar *src = mask.constScanLine(y); |
365 | for (int x = 0; x < c.w; ++x) { |
366 | if (x < mw) |
367 | dest[x] = (src[x >> 3] & (1 << (7 - (x & 7)))) > 0 ? 255 : 0; |
368 | } |
369 | } |
370 | } |
371 | } else if (mask.depth() == 8) { |
372 | for (int y = 0; y < c.h; ++y) { |
373 | uchar *dest = d + (c.y + y) *dbpl + c.x; |
374 | if (y < mh) { |
375 | const uchar *src = mask.constScanLine(y); |
376 | for (int x = 0; x < c.w; ++x) { |
377 | if (x < mw) |
378 | dest[x] = src[x]; |
379 | } |
380 | } |
381 | } |
382 | } |
383 | } |
384 | |
385 | #ifdef CACHE_DEBUG |
386 | // QPainter p(&m_image); |
387 | // p.drawLine( |
388 | int margin = m_current_fontengine ? m_current_fontengine->glyphMargin(m_format) : 0; |
389 | QPoint base(c.x + margin, c.y + margin + c.baseLineY-1); |
390 | if (m_image.rect().contains(base)) |
391 | m_image.setPixel(base, 255); |
392 | m_image.save(QString::fromLatin1("cache-%1.png" ).arg(qint64(this))); |
393 | #endif |
394 | } |
395 | |
396 | QT_END_NAMESPACE |
397 | |