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