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