FontRenderer.cpp revision ca79cf69d09efa0c327e9b1237d86a119aea5da7
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 <SkGlyph.h>
20#include <SkUtils.h>
21
22#include <cutils/properties.h>
23
24#include <utils/Log.h>
25
26#include "Caches.h"
27#include "Debug.h"
28#include "FontRenderer.h"
29#include "Rect.h"
30
31namespace android {
32namespace uirenderer {
33
34///////////////////////////////////////////////////////////////////////////////
35// FontRenderer
36///////////////////////////////////////////////////////////////////////////////
37
38static bool sLogFontRendererCreate = true;
39
40FontRenderer::FontRenderer() {
41    if (sLogFontRendererCreate) {
42        INIT_LOGD("Creating FontRenderer");
43    }
44
45    mGammaTable = NULL;
46    mInitialized = false;
47    mMaxNumberOfQuads = 1024;
48    mCurrentQuadIndex = 0;
49    mLastQuadIndex = 0;
50
51    mTextMesh = NULL;
52    mCurrentCacheTexture = NULL;
53
54    mLinearFiltering = false;
55
56    mIndexBufferID = 0;
57
58    mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH;
59    mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT;
60    mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH;
61    mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT;
62
63    char property[PROPERTY_VALUE_MAX];
64    if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, NULL) > 0) {
65        mSmallCacheWidth = atoi(property);
66    }
67
68    if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, NULL) > 0) {
69        mSmallCacheHeight = atoi(property);
70    }
71
72    if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, NULL) > 0) {
73        mLargeCacheWidth = atoi(property);
74    }
75
76    if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, NULL) > 0) {
77        mLargeCacheHeight = atoi(property);
78    }
79
80    uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize;
81    mSmallCacheWidth = mSmallCacheWidth > maxTextureSize ? maxTextureSize : mSmallCacheWidth;
82    mSmallCacheHeight = mSmallCacheHeight > maxTextureSize ? maxTextureSize : mSmallCacheHeight;
83    mLargeCacheWidth = mLargeCacheWidth > maxTextureSize ? maxTextureSize : mLargeCacheWidth;
84    mLargeCacheHeight = mLargeCacheHeight > maxTextureSize ? maxTextureSize : mLargeCacheHeight;
85
86    if (sLogFontRendererCreate) {
87        INIT_LOGD("  Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i",
88                mSmallCacheWidth, mSmallCacheHeight,
89                mLargeCacheWidth, mLargeCacheHeight >> 1,
90                mLargeCacheWidth, mLargeCacheHeight >> 1,
91                mLargeCacheWidth, mLargeCacheHeight);
92    }
93
94    sLogFontRendererCreate = false;
95}
96
97FontRenderer::~FontRenderer() {
98    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
99        delete mCacheTextures[i];
100    }
101    mCacheTextures.clear();
102
103    if (mInitialized) {
104        // Unbinding the buffer shouldn't be necessary but it crashes with some drivers
105        Caches::getInstance().unbindIndicesBuffer();
106        glDeleteBuffers(1, &mIndexBufferID);
107
108        delete[] mTextMesh;
109    }
110
111    Vector<Font*> fontsToDereference = mActiveFonts;
112    for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
113        delete fontsToDereference[i];
114    }
115}
116
117void FontRenderer::flushAllAndInvalidate() {
118    if (mCurrentQuadIndex != 0) {
119        issueDrawCommand();
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::updateDrawParams() {
324    if (mCurrentQuadIndex != mLastQuadIndex) {
325        mDrawOffsets.add((uint16_t*)(mLastQuadIndex * sizeof(uint16_t) * 6));
326        mDrawCounts.add(mCurrentQuadIndex - mLastQuadIndex);
327        mDrawCacheTextures.add(mCurrentCacheTexture);
328        mLastQuadIndex = mCurrentQuadIndex;
329    }
330}
331
332void FontRenderer::checkTextureUpdate() {
333    if (!mUploadTexture) {
334        return;
335    }
336
337    Caches& caches = Caches::getInstance();
338    GLuint lastTextureId = 0;
339    // Iterate over all the cache textures and see which ones need to be updated
340    for (uint32_t i = 0; i < mCacheTextures.size(); i++) {
341        CacheTexture* cacheTexture = mCacheTextures[i];
342        if (cacheTexture->isDirty() && cacheTexture->getTexture()) {
343            // Can't copy inner rect; glTexSubimage expects pointer to deal with entire buffer
344            // of data. So expand the dirty rect to the encompassing horizontal stripe.
345            const Rect* dirtyRect = cacheTexture->getDirtyRect();
346            uint32_t x = 0;
347            uint32_t y = dirtyRect->top;
348            uint32_t width = cacheTexture->getWidth();
349            uint32_t height = dirtyRect->getHeight();
350            void* textureData = cacheTexture->getTexture() + y * width;
351
352            if (cacheTexture->getTextureId() != lastTextureId) {
353                lastTextureId = cacheTexture->getTextureId();
354                caches.activeTexture(0);
355                glBindTexture(GL_TEXTURE_2D, lastTextureId);
356            }
357#if DEBUG_FONT_RENDERER
358            ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d",
359                    i, x, y, width, height);
360#endif
361            glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height,
362                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
363            cacheTexture->setDirty(false);
364        }
365    }
366
367    mUploadTexture = false;
368}
369
370void FontRenderer::issueDrawCommand() {
371    updateDrawParams();
372    checkTextureUpdate();
373
374    Caches& caches = Caches::getInstance();
375    caches.bindIndicesBuffer(mIndexBufferID);
376    if (!mDrawn) {
377        float* buffer = mTextMesh;
378        int offset = 2;
379
380        bool force = caches.unbindMeshBuffer();
381        caches.bindPositionVertexPointer(force, buffer);
382        caches.bindTexCoordsVertexPointer(force, buffer + offset);
383    }
384
385    for (uint32_t i = 0; i < mDrawOffsets.size(); i++) {
386        uint16_t* offset = mDrawOffsets[i];
387        uint32_t count = mDrawCounts[i];
388        CacheTexture* texture = mDrawCacheTextures[i];
389
390        caches.activeTexture(0);
391        glBindTexture(GL_TEXTURE_2D, texture->getTextureId());
392
393        texture->setLinearFiltering(mLinearFiltering, false);
394
395        glDrawElements(GL_TRIANGLES, count * 6, GL_UNSIGNED_SHORT, offset);
396    }
397
398    mDrawn = true;
399
400    mCurrentQuadIndex = 0;
401    mLastQuadIndex = 0;
402    mDrawOffsets.clear();
403    mDrawCounts.clear();
404    mDrawCacheTextures.clear();
405}
406
407void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
408        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
409        float x4, float y4, float u4, float v4, CacheTexture* texture) {
410    if (texture != mCurrentCacheTexture) {
411        updateDrawParams();
412        // Now use the new texture id
413        mCurrentCacheTexture = texture;
414    }
415
416    const uint32_t vertsPerQuad = 4;
417    const uint32_t floatsPerVert = 4;
418    float* currentPos = mTextMesh + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
419
420    (*currentPos++) = x1;
421    (*currentPos++) = y1;
422    (*currentPos++) = u1;
423    (*currentPos++) = v1;
424
425    (*currentPos++) = x2;
426    (*currentPos++) = y2;
427    (*currentPos++) = u2;
428    (*currentPos++) = v2;
429
430    (*currentPos++) = x3;
431    (*currentPos++) = y3;
432    (*currentPos++) = u3;
433    (*currentPos++) = v3;
434
435    (*currentPos++) = x4;
436    (*currentPos++) = y4;
437    (*currentPos++) = u4;
438    (*currentPos++) = v4;
439
440    mCurrentQuadIndex++;
441}
442
443void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
444        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
445        float x4, float y4, float u4, float v4, CacheTexture* texture) {
446
447    if (mClip &&
448            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
449        return;
450    }
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, x1);
456        mBounds->top = fmin(mBounds->top, y3);
457        mBounds->right = fmax(mBounds->right, x3);
458        mBounds->bottom = fmax(mBounds->bottom, y1);
459    }
460
461    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
462        issueDrawCommand();
463    }
464}
465
466void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
467        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
468        float x4, float y4, float u4, float v4, CacheTexture* texture) {
469
470    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
471
472    if (mBounds) {
473        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
474        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
475        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
476        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
477    }
478
479    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
480        issueDrawCommand();
481    }
482}
483
484void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
485    int flags = 0;
486    if (paint->isFakeBoldText()) {
487        flags |= Font::kFakeBold;
488    }
489
490    const float skewX = paint->getTextSkewX();
491    uint32_t italicStyle = *(uint32_t*) &skewX;
492    const float scaleXFloat = paint->getTextScaleX();
493    uint32_t scaleX = *(uint32_t*) &scaleXFloat;
494    SkPaint::Style style = paint->getStyle();
495    const float strokeWidthFloat = paint->getStrokeWidth();
496    uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
497    mCurrentFont = Font::create(this, fontId, fontSize, flags, italicStyle,
498            scaleX, style, strokeWidth);
499
500}
501
502FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
503        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
504    checkInit();
505
506    if (!mCurrentFont) {
507        DropShadow image;
508        image.width = 0;
509        image.height = 0;
510        image.image = NULL;
511        image.penX = 0;
512        image.penY = 0;
513        return image;
514    }
515
516    mDrawn = false;
517    mClip = NULL;
518    mBounds = NULL;
519
520    Rect bounds;
521    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
522
523    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
524    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
525    uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
526
527    for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
528        dataBuffer[i] = 0;
529    }
530
531    int penX = radius - bounds.left;
532    int penY = radius - bounds.bottom;
533
534    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
535            Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, NULL, positions);
536    blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
537
538    DropShadow image;
539    image.width = paddedWidth;
540    image.height = paddedHeight;
541    image.image = dataBuffer;
542    image.penX = penX;
543    image.penY = penY;
544
545    return image;
546}
547
548void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
549    checkInit();
550
551    mDrawn = false;
552    mBounds = bounds;
553    mClip = clip;
554}
555
556void FontRenderer::finishRender() {
557    mBounds = NULL;
558    mClip = NULL;
559
560    if (mCurrentQuadIndex != 0) {
561        issueDrawCommand();
562    }
563}
564
565void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs) {
566    int flags = 0;
567    if (paint->isFakeBoldText()) {
568        flags |= Font::kFakeBold;
569    }
570    const float skewX = paint->getTextSkewX();
571    uint32_t italicStyle = *(uint32_t*) &skewX;
572    const float scaleXFloat = paint->getTextScaleX();
573    uint32_t scaleX = *(uint32_t*) &scaleXFloat;
574    SkPaint::Style style = paint->getStyle();
575    const float strokeWidthFloat = paint->getStrokeWidth();
576    uint32_t strokeWidth = *(uint32_t*) &strokeWidthFloat;
577    float fontSize = paint->getTextSize();
578    Font* font = Font::create(this, SkTypeface::UniqueID(paint->getTypeface()),
579            fontSize, flags, italicStyle, scaleX, style, strokeWidth);
580
581    font->precache(paint, text, numGlyphs);
582}
583
584bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text,
585        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, Rect* bounds) {
586    if (!mCurrentFont) {
587        ALOGE("No font set");
588        return false;
589    }
590
591    initRender(clip, bounds);
592    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y);
593    finishRender();
594
595    return mDrawn;
596}
597
598bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
599        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
600        const float* positions, Rect* bounds) {
601    if (!mCurrentFont) {
602        ALOGE("No font set");
603        return false;
604    }
605
606    initRender(clip, bounds);
607    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
608    finishRender();
609
610    return mDrawn;
611}
612
613bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
614        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
615        float hOffset, float vOffset, Rect* bounds) {
616    if (!mCurrentFont) {
617        ALOGE("No font set");
618        return false;
619    }
620
621    initRender(clip, bounds);
622    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
623    finishRender();
624
625    return mDrawn;
626}
627
628void FontRenderer::removeFont(const Font* font) {
629    for (uint32_t ct = 0; ct < mActiveFonts.size(); ct++) {
630        if (mActiveFonts[ct] == font) {
631            mActiveFonts.removeAt(ct);
632            break;
633        }
634    }
635
636    if (mCurrentFont == font) {
637        mCurrentFont = NULL;
638    }
639}
640
641void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
642    // Compute gaussian weights for the blur
643    // e is the euler's number
644    float e = 2.718281828459045f;
645    float pi = 3.1415926535897932f;
646    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
647    // x is of the form [-radius .. 0 .. radius]
648    // and sigma varies with radius.
649    // Based on some experimental radius values and sigma's
650    // we approximately fit sigma = f(radius) as
651    // sigma = radius * 0.3  + 0.6
652    // The larger the radius gets, the more our gaussian blur
653    // will resemble a box blur since with large sigma
654    // the gaussian curve begins to lose its shape
655    float sigma = 0.3f * (float) radius + 0.6f;
656
657    // Now compute the coefficints
658    // We will store some redundant values to save some math during
659    // the blur calculations
660    // precompute some values
661    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
662    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
663
664    float normalizeFactor = 0.0f;
665    for (int32_t r = -radius; r <= radius; r ++) {
666        float floatR = (float) r;
667        weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
668        normalizeFactor += weights[r + radius];
669    }
670
671    //Now we need to normalize the weights because all our coefficients need to add up to one
672    normalizeFactor = 1.0f / normalizeFactor;
673    for (int32_t r = -radius; r <= radius; r ++) {
674        weights[r + radius] *= normalizeFactor;
675    }
676}
677
678void FontRenderer::horizontalBlur(float* weights, int32_t radius,
679        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
680    float blurredPixel = 0.0f;
681    float currentPixel = 0.0f;
682
683    for (int32_t y = 0; y < height; y ++) {
684
685        const uint8_t* input = source + y * width;
686        uint8_t* output = dest + y * width;
687
688        for (int32_t x = 0; x < width; x ++) {
689            blurredPixel = 0.0f;
690            const float* gPtr = weights;
691            // Optimization for non-border pixels
692            if (x > radius && x < (width - radius)) {
693                const uint8_t *i = input + (x - radius);
694                for (int r = -radius; r <= radius; r ++) {
695                    currentPixel = (float) (*i);
696                    blurredPixel += currentPixel * gPtr[0];
697                    gPtr++;
698                    i++;
699                }
700            } else {
701                for (int32_t r = -radius; r <= radius; r ++) {
702                    // Stepping left and right away from the pixel
703                    int validW = x + r;
704                    if (validW < 0) {
705                        validW = 0;
706                    }
707                    if (validW > width - 1) {
708                        validW = width - 1;
709                    }
710
711                    currentPixel = (float) input[validW];
712                    blurredPixel += currentPixel * gPtr[0];
713                    gPtr++;
714                }
715            }
716            *output = (uint8_t)blurredPixel;
717            output ++;
718        }
719    }
720}
721
722void FontRenderer::verticalBlur(float* weights, int32_t radius,
723        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
724    float blurredPixel = 0.0f;
725    float currentPixel = 0.0f;
726
727    for (int32_t y = 0; y < height; y ++) {
728        uint8_t* output = dest + y * width;
729
730        for (int32_t x = 0; x < width; x ++) {
731            blurredPixel = 0.0f;
732            const float* gPtr = weights;
733            const uint8_t* input = source + x;
734            // Optimization for non-border pixels
735            if (y > radius && y < (height - radius)) {
736                const uint8_t *i = input + ((y - radius) * width);
737                for (int32_t r = -radius; r <= radius; r ++) {
738                    currentPixel = (float)(*i);
739                    blurredPixel += currentPixel * gPtr[0];
740                    gPtr++;
741                    i += width;
742                }
743            } else {
744                for (int32_t r = -radius; r <= radius; r ++) {
745                    int validH = y + r;
746                    // Clamp to zero and width
747                    if (validH < 0) {
748                        validH = 0;
749                    }
750                    if (validH > height - 1) {
751                        validH = height - 1;
752                    }
753
754                    const uint8_t *i = input + validH * width;
755                    currentPixel = (float) (*i);
756                    blurredPixel += currentPixel * gPtr[0];
757                    gPtr++;
758                }
759            }
760            *output = (uint8_t) blurredPixel;
761            output++;
762        }
763    }
764}
765
766
767void FontRenderer::blurImage(uint8_t *image, int32_t width, int32_t height, int32_t radius) {
768    float *gaussian = new float[2 * radius + 1];
769    computeGaussianWeights(gaussian, radius);
770
771    uint8_t* scratch = new uint8_t[width * height];
772
773    horizontalBlur(gaussian, radius, image, scratch, width, height);
774    verticalBlur(gaussian, radius, scratch, image, width, height);
775
776    delete[] gaussian;
777    delete[] scratch;
778}
779
780}; // namespace uirenderer
781}; // namespace android
782