FontRenderer.cpp revision b969a0de65730b071d846f8302e751e2637e6dbe
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "OpenGLRenderer"
18
19#include <SkUtils.h>
20
21#include <cutils/properties.h>
22
23#include <utils/Log.h>
24
25#include "Caches.h"
26#include "Debug.h"
27#include "FontRenderer.h"
28#include "Rect.h"
29
30namespace android {
31namespace uirenderer {
32
33///////////////////////////////////////////////////////////////////////////////
34// FontRenderer
35///////////////////////////////////////////////////////////////////////////////
36
37static bool sLogFontRendererCreate = true;
38
39FontRenderer::FontRenderer() :
40        mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) {
41
42    if (sLogFontRendererCreate) {
43        INIT_LOGD("Creating FontRenderer");
44    }
45
46    mGammaTable = NULL;
47    mInitialized = false;
48    mMaxNumberOfQuads = 1024;
49    mCurrentQuadIndex = 0;
50    mLastQuadIndex = 0;
51
52    mTextMesh = NULL;
53    mCurrentCacheTexture = NULL;
54
55    mLinearFiltering = false;
56
57    mIndexBufferID = 0;
58
59    mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH;
60    mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT;
61    mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH;
62    mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT;
63
64    char property[PROPERTY_VALUE_MAX];
65    if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) {
66        mSmallCacheWidth = atoi(property);
67    }
68
69    if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) {
70        mSmallCacheHeight = atoi(property);
71    }
72
73    if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) {
74        mLargeCacheWidth = atoi(property);
75    }
76
77    if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) {
78        mLargeCacheHeight = atoi(property);
79    }
80
81    uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
82    mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth;
83    mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight;
84    mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth;
85    mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight;
86
87    if (sLogFontRendererCreate) {
88        INIT_LOGD("  Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
89                mSmallCacheWidth, mSmallCacheHeight,
90                mLargeCacheWidth, mLargeCacheHeight >> 1,
91                mLargeCacheWidth, mLargeCacheHeight >> 1,
92                mLargeCacheWidth, mLargeCacheHeight);
93    }
94
95    sLogFontRendererCreate = false;
96}
97
98FontRenderer::~FontRenderer() {
99    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
100        delete mCacheTextures[i];
101    }
102    mCacheTextures.clear();
103
104    if (mInitialized) {
105        // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
106        Caches::getInstance().unbindIndicesBuffer();
107        glDeleteBuffers(1, &mIndexBufferID);
108
109        delete[] mTextMesh;
110    }
111
112    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
113    while (it.next()) {
114        delete it.value();
115    }
116    mActiveFonts.clear();
117}
118
119void FontRenderer::flushAllAndInvalidate() {
120    if (mCurrentQuadIndex != 0) {
121        issueDrawCommand();
122    }
123
124    LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
125    while (it.next()) {
126        it.value()->invalidateTextureCache();
127    }
128
129    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
130        mCacheTextures[i]->init();
131    }
132
133#if DEBUG_FONT_RENDERER
134    uint16_t totalGlyphs = 0;
135    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
136        totalGlyphs += mCacheTextures[i]->getGlyphCount();
137        // Erase caches, just as a debugging facility
138        if (mCacheTextures[i]->getTexture()) {
139            memset(mCacheTextures[i]->getTexture(), 0,
140                    mCacheTextures[i]->getWidth() * mCacheTextures[i]->getHeight());
141        }
142    }
143    ALOGD("Flushing caches: glyphs cached = %d", totalGlyphs);
144#endif
145}
146
147void FontRenderer::flushLargeCaches() {
148    // Start from 1; don't deallocate smallest/default texture
149    for (uint32_t i = 1; i < mCacheTextures.size(); i++) {
150        CacheTexture* cacheTexture = mCacheTextures[i];
151        if (cacheTexture->getTexture()) {
152            cacheTexture->init();
153            LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts);
154            while (it.next()) {
155                it.value()->invalidateTextureCache(cacheTexture);
156            }
157            cacheTexture->releaseTexture();
158        }
159    }
160}
161
162CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph,
163        uint32_t* startX, uint32_t* startY) {
164    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
165        if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) {
166            return mCacheTextures[i];
167        }
168    }
169    // Could not fit glyph into current cache textures
170    return NULL;
171}
172
173void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
174        uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) {
175    checkInit();
176    cachedGlyph->mIsValid = false;
177    // If the glyph is too tall, don't cache it
178    if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 >
179                mCacheTextures[mCacheTextures.size() - 1]->getHeight()) {
180        ALOGE("Font size too large to fit in cache. width, height = %i, %i",
181                (int) glyph.fWidth, (int) glyph.fHeight);
182        return;
183    }
184
185    // Now copy the bitmap into the cache texture
186    uint32_t startX = 0;
187    uint32_t startY = 0;
188
189    CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
190
191    if (!cacheTexture) {
192        if (!precaching) {
193            // If the new glyph didn't fit and we are not just trying to precache it,
194            // clear out the cache and try again
195            flushAllAndInvalidate();
196            cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY);
197        }
198
199        if (!cacheTexture) {
200            // either the glyph didn't fit or we're precaching and will cache it when we draw
201            return;
202        }
203    }
204
205    cachedGlyph->mCacheTexture = cacheTexture;
206
207    *retOriginX = startX;
208    *retOriginY = startY;
209
210    uint32_t endX = startX + glyph.fWidth;
211    uint32_t endY = startY + glyph.fHeight;
212
213    uint32_t cacheWidth = cacheTexture->getWidth();
214
215    if (!cacheTexture->getTexture()) {
216        Caches::getInstance().activeTexture(0);
217        // Large-glyph texture memory is allocated only as needed
218        cacheTexture->allocateTexture();
219    }
220
221    // Tells us whether the glyphs is B&W (1 bit per pixel)
222    // or anti-aliased (8 bits per pixel)
223    SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
224
225    uint8_t* cacheBuffer = cacheTexture->getTexture();
226    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
227
228    // Zero out the borders
229    for (cacheX = startX - TEXTURE_BORDER_SIZE; cacheX < endX + TEXTURE_BORDER_SIZE; cacheX++) {
230        cacheBuffer[(startY - TEXTURE_BORDER_SIZE) * cacheWidth + cacheX] = 0;
231        cacheBuffer[(endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + cacheX] = 0;
232    }
233
234    for (cacheY = startY - TEXTURE_BORDER_SIZE + 1;
235            cacheY < endY + TEXTURE_BORDER_SIZE - 1; cacheY++) {
236        cacheBuffer[cacheY * cacheWidth + startX - TEXTURE_BORDER_SIZE] = 0;
237        cacheBuffer[cacheY * cacheWidth + endX + TEXTURE_BORDER_SIZE - 1] = 0;
238    }
239
240    // Copy the glyph image, taking the mask format into account
241    uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage;
242    int stride = glyph.rowBytes();
243
244    switch (format) {
245        case SkMask::kA8_Format: {
246            if (mGammaTable) {
247                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
248                    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
249                        uint8_t tempCol = bitmapBuffer[bY + bX];
250                        cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
251                    }
252                }
253            } else {
254                for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) {
255                    memcpy(&cacheBuffer[cacheY * cacheWidth + startX], &bitmapBuffer[bY],
256                            glyph.fWidth);
257                }
258            }
259            break;
260        }
261        case SkMask::kBW_Format: {
262            static const uint8_t COLORS[2] = { 0, 255 };
263
264            for (cacheY = startY; cacheY < endY; cacheY++) {
265                cacheX = startX;
266                int rowBytes = stride;
267                uint8_t* buffer = bitmapBuffer;
268
269                while (--rowBytes >= 0) {
270                    uint8_t b = *buffer++;
271                    for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) {
272                        cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1];
273                    }
274                }
275
276                bitmapBuffer += stride;
277            }
278            break;
279        }
280        default:
281            ALOGW("Unkown glyph format: 0x%x", format);
282            break;
283    }
284
285    cachedGlyph->mIsValid = true;
286}
287
288CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) {
289    CacheTexture* cacheTexture = new CacheTexture(width, height);
290
291    if (allocate) {
292        Caches::getInstance().activeTexture(0);
293        cacheTexture->allocateTexture();
294    }
295
296    return cacheTexture;
297}
298
299void FontRenderer::initTextTexture() {
300    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
301        delete mCacheTextures[i];
302    }
303    mCacheTextures.clear();
304
305    mUploadTexture = false;
306    mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true));
307    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
308    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false));
309    mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false));
310    mCurrentCacheTexture = mCacheTextures[0];
311}
312
313// Avoid having to reallocate memory and render quad by quad
314void FontRenderer::initVertexArrayBuffers() {
315    uint32_t numIndices = mMaxNumberOfQuads * 6;
316    uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t);
317    uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
318
319    // Four verts, two triangles , six indices per quad
320    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
321        int i6 = i * 6;
322        int i4 = i * 4;
323
324        indexBufferData[i6 + 0] = i4 + 0;
325        indexBufferData[i6 + 1] = i4 + 1;
326        indexBufferData[i6 + 2] = i4 + 2;
327
328        indexBufferData[i6 + 3] = i4 + 0;
329        indexBufferData[i6 + 4] = i4 + 2;
330        indexBufferData[i6 + 5] = i4 + 3;
331    }
332
333    glGenBuffers(1, &mIndexBufferID);
334    Caches::getInstance().bindIndicesBuffer(mIndexBufferID);
335    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW);
336
337    free(indexBufferData);
338
339    uint32_t coordSize = 2;
340    uint32_t uvSize = 2;
341    uint32_t vertsPerQuad = 4;
342    uint32_t vertexBufferSize = mMaxNumberOfQuads * vertsPerQuad * coordSize * uvSize;
343    mTextMesh = new float[vertexBufferSize];
344}
345
346// We don't want to allocate anything unless we actually draw text
347void FontRenderer::checkInit() {
348    if (mInitialized) {
349        return;
350    }
351
352    initTextTexture();
353    initVertexArrayBuffers();
354
355    mInitialized = true;
356}
357
358void FontRenderer::updateDrawParams() {
359    if (mCurrentQuadIndex != mLastQuadIndex) {
360        mDrawOffsets.add((uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6));
361        mDrawCounts.add(mCurrentQuadIndex - mLastQuadIndex);
362        mDrawCacheTextures.add(mCurrentCacheTexture);
363        mLastQuadIndex = mCurrentQuadIndex;
364    }
365}
366
367void FontRenderer::checkTextureUpdate() {
368    if (!mUploadTexture) {
369        return;
370    }
371
372    Caches& caches = Caches::getInstance();
373    GLuint lastTextureId = 0;
374    // Iterate over all the cache textures and see which ones need to be updated
375    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
376        CacheTexture* cacheTexture = mCacheTextures[i];
377        if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
378            // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer
379            // of data. So expand the dirty rect to the encompassing horizontal stripe.
380            const Rect* dirtyRect = cacheTexture->getDirtyRect();
381            uint32_t x = 0;
382            uint32_t y = dirtyRect->top;
383            uint32_t width = cacheTexture->getWidth();
384            uint32_t height = dirtyRect->getHeight();
385            void* textureData = cacheTexture->getTexture() + y * width;
386
387            if (cacheTexture->getTextureId() != lastTextureId) {
388                lastTextureId = cacheTexture->getTextureId();
389                caches.activeTexture(0);
390                glBindTexture(GL_TEXTURE_2D, lastTextureId);
391            }
392#if DEBUG_FONT_RENDERER
393            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
394                    i, x, y, width, height);
395#endif
396            glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
397                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
398            cacheTexture->setDirty(false);
399        }
400    }
401
402    mUploadTexture = false;
403}
404
405void FontRenderer::issueDrawCommand() {
406    updateDrawParams();
407    checkTextureUpdate();
408
409    Caches& caches = Caches::getInstance();
410    caches.bindIndicesBuffer(mIndexBufferID);
411    if (!mDrawn) {
412        float* buffer = mTextMesh;
413        int offset = 2;
414
415        bool force = caches.unbindMeshBuffer();
416        caches.bindPositionVertexPointer(force, buffer);
417        caches.bindTexCoordsVertexPointer(force, buffer + offset);
418    }
419
420    for (uint32_t i = 0; i < mDrawOffsets.size(); i++) {
421        uint16_t* offset = mDrawOffsets[i];
422        uint32_t count = mDrawCounts[i];
423        CacheTexture* texture = mDrawCacheTextures[i];
424
425        caches.activeTexture(0);
426        glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
427
428        texture->setLinearFiltering(mLinearFiltering, false);
429
430        glDrawElements(GL_TRIANGLES, count * 6, GL_UNSIGNED_SHORT, offset);
431    }
432
433    mDrawn = true;
434
435    mCurrentQuadIndex = 0;
436    mLastQuadIndex = 0;
437    mDrawOffsets.clear();
438    mDrawCounts.clear();
439    mDrawCacheTextures.clear();
440}
441
442void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
443        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
444        float x4, float y4, float u4, float v4, CacheTexture* texture) {
445    if (texture != mCurrentCacheTexture) {
446        updateDrawParams();
447        // Now use the new texture id
448        mCurrentCacheTexture = texture;
449    }
450
451    const uint32_t vertsPerQuad = 4;
452    const uint32_t floatsPerVert = 4;
453    float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
454
455    (*currentPos++) = x1;
456    (*currentPos++) = y1;
457    (*currentPos++) = u1;
458    (*currentPos++) = v1;
459
460    (*currentPos++) = x2;
461    (*currentPos++) = y2;
462    (*currentPos++) = u2;
463    (*currentPos++) = v2;
464
465    (*currentPos++) = x3;
466    (*currentPos++) = y3;
467    (*currentPos++) = u3;
468    (*currentPos++) = v3;
469
470    (*currentPos++) = x4;
471    (*currentPos++) = y4;
472    (*currentPos++) = u4;
473    (*currentPos++) = v4;
474
475    mCurrentQuadIndex++;
476}
477
478void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
479        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
480        float x4, float y4, float u4, float v4, CacheTexture* texture) {
481
482    if (mClip &&
483            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
484        return;
485    }
486
487    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
488
489    if (mBounds) {
490        mBounds->left = fmin(mBounds->left, x1);
491        mBounds->top = fmin(mBounds->top, y3);
492        mBounds->right = fmax(mBounds->right, x3);
493        mBounds->bottom = fmax(mBounds->bottom, y1);
494    }
495
496    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
497        issueDrawCommand();
498    }
499}
500
501void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
502        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
503        float x4, float y4, float u4, float v4, CacheTexture* texture) {
504
505    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
506
507    if (mBounds) {
508        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
509        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
510        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
511        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
512    }
513
514    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
515        issueDrawCommand();
516    }
517}
518
519void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
520    mCurrentFont = Font::create(this, paint, matrix);
521}
522
523FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
524        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
525    checkInit();
526
527    if (!mCurrentFont) {
528        DropShadow image;
529        image.width = 0;
530        image.height = 0;
531        image.image = NULL;
532        image.penX = 0;
533        image.penY = 0;
534        return image;
535    }
536
537    mDrawn = false;
538    mClip = NULL;
539    mBounds = NULL;
540
541    Rect bounds;
542    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
543
544    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
545    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
546    uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
547
548    for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
549        dataBuffer[i] = 0;
550    }
551
552    int penX = radius - bounds.left;
553    int penY = radius - bounds.bottom;
554
555    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
556            Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
557    blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
558
559    DropShadow image;
560    image.width = paddedWidth;
561    image.height = paddedHeight;
562    image.image = dataBuffer;
563    image.penX = penX;
564    image.penY = penY;
565
566    return image;
567}
568
569void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
570    checkInit();
571
572    mDrawn = false;
573    mBounds = bounds;
574    mClip = clip;
575}
576
577void FontRenderer::finishRender() {
578    mBounds = NULL;
579    mClip = NULL;
580
581    if (mCurrentQuadIndex != 0) {
582        issueDrawCommand();
583    }
584}
585
586void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
587    Font* font = Font::create(this, paint, matrix);
588    font->precache(paint, text, numGlyphs);
589}
590
591bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
592        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
593        const float* positions, Rect* bounds) {
594    if (!mCurrentFont) {
595        ALOGE("No font set");
596        return false;
597    }
598
599    initRender(clip, bounds);
600    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
601    finishRender();
602
603    return mDrawn;
604}
605
606bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
607        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
608        float hOffset, float vOffset, Rect* bounds) {
609    if (!mCurrentFont) {
610        ALOGE("No font set");
611        return false;
612    }
613
614    initRender(clip, bounds);
615    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
616    finishRender();
617
618    return mDrawn;
619}
620
621void FontRenderer::removeFont(const Font* font) {
622    mActiveFonts.remove(font->getDescription());
623
624    if (mCurrentFont == font) {
625        mCurrentFont = NULL;
626    }
627}
628
629void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
630    // Compute gaussian weights for the blur
631    // e is the euler's number
632    float e = 2.718281828459045f;
633    float pi = 3.1415926535897932f;
634    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
635    // x is of the form [-radius .. 0 .. radius]
636    // and sigma varies with radius.
637    // Based on some experimental radius values and sigma's
638    // we approximately fit sigma = f(radius) as
639    // sigma = radius * 0.3  + 0.6
640    // The larger the radius gets, the more our gaussian blur
641    // will resemble a box blur since with large sigma
642    // the gaussian curve begins to lose its shape
643    float sigma = 0.3f * (float) radius + 0.6f;
644
645    // Now compute the coefficints
646    // We will store some redundant values to save some math during
647    // the blur calculations
648    // precompute some values
649    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
650    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
651
652    float normalizeFactor = 0.0f;
653    for (int32_t r = -radius; r <= radius; r ++) {
654        float floatR = (float) r;
655        weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
656        normalizeFactor += weights[r + radius];
657    }
658
659    //Now we need to normalize the weights because all our coefficients need to add up to one
660    normalizeFactor = 1.0f / normalizeFactor;
661    for (int32_t r = -radius; r <= radius; r ++) {
662        weights[r + radius] *= normalizeFactor;
663    }
664}
665
666void FontRenderer::horizontalBlur(float* weights, int32_t radius,
667        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
668    float blurredPixel = 0.0f;
669    float currentPixel = 0.0f;
670
671    for (int32_t y = 0; y < height; y ++) {
672
673        const uint8_t* input = source + y * width;
674        uint8_t* output = dest + y * width;
675
676        for (int32_t x = 0; x < width; x ++) {
677            blurredPixel = 0.0f;
678            const float* gPtr = weights;
679            // Optimization for non-border pixels
680            if (x > radius && x < (width - radius)) {
681                const uint8_t *i = input + (x - radius);
682                for (int r = -radius; r <= radius; r ++) {
683                    currentPixel = (float) (*i);
684                    blurredPixel += currentPixel * gPtr[0];
685                    gPtr++;
686                    i++;
687                }
688            } else {
689                for (int32_t r = -radius; r <= radius; r ++) {
690                    // Stepping left and right away from the pixel
691                    int validW = x + r;
692                    if (validW < 0) {
693                        validW = 0;
694                    }
695                    if (validW > width - 1) {
696                        validW = width - 1;
697                    }
698
699                    currentPixel = (float) input[validW];
700                    blurredPixel += currentPixel * gPtr[0];
701                    gPtr++;
702                }
703            }
704            *output = (uint8_t)blurredPixel;
705            output ++;
706        }
707    }
708}
709
710void FontRenderer::verticalBlur(float* weights, int32_t radius,
711        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
712    float blurredPixel = 0.0f;
713    float currentPixel = 0.0f;
714
715    for (int32_t y = 0; y < height; y ++) {
716        uint8_t* output = dest + y * width;
717
718        for (int32_t x = 0; x < width; x ++) {
719            blurredPixel = 0.0f;
720            const float* gPtr = weights;
721            const uint8_t* input = source + x;
722            // Optimization for non-border pixels
723            if (y > radius && y < (height - radius)) {
724                const uint8_t *i = input + ((y - radius) * width);
725                for (int32_t r = -radius; r <= radius; r ++) {
726                    currentPixel = (float)(*i);
727                    blurredPixel += currentPixel * gPtr[0];
728                    gPtr++;
729                    i += width;
730                }
731            } else {
732                for (int32_t r = -radius; r <= radius; r ++) {
733                    int validH = y + r;
734                    // Clamp to zero and width
735                    if (validH < 0) {
736                        validH = 0;
737                    }
738                    if (validH > height - 1) {
739                        validH = height - 1;
740                    }
741
742                    const uint8_t *i = input + validH * width;
743                    currentPixel = (float) (*i);
744                    blurredPixel += currentPixel * gPtr[0];
745                    gPtr++;
746                }
747            }
748            *output = (uint8_t) blurredPixel;
749            output++;
750        }
751    }
752}
753
754
755void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
756    float *gaussian = new float[2 * radius + 1];
757    computeGaussianWeights(gaussian, radius);
758
759    uint8_t* scratch = new uint8_t[width * height];
760
761    horizontalBlur(gaussian, radius, image, scratch, width, height);
762    verticalBlur(gaussian, radius, scratch, image, width, height);
763
764    delete[] gaussian;
765    delete[] scratch;
766}
767
768}; // namespace uirenderer
769}; // namespace android
770