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